Board logo

标题: [文本处理] 【已解决】求助批处理辨别用户输入的内容是否符合某个规范 [打印本页]

作者: 蚯蚓传奇    时间: 2024-8-19 15:49     标题: 【已解决】求助批处理辨别用户输入的内容是否符合某个规范

本帖最后由 蚯蚓传奇 于 2024-8-21 12:04 编辑

我想请教一下怎么辨别用户输入一段日期是否符合规范
让用户输入一个日期,然后程序检查文本是否是按照**(月)/**(日)的格式写的
如果可以的话,我还想让这个程序拥有检查日期是否有错误的功能,比如1月不能有31号
最好只用批处理或者powershell,谢谢
作者: aloha20200628    时间: 2024-8-19 17:13

本帖最后由 aloha20200628 于 2024-8-19 17:17 编辑

回复 1# 蚯蚓传奇

以下代码存为 test.bat 运行...
备注》powershell兼容 ‘月/日’ 和 ‘月-日’ 多种格式分隔符,自动检查月日合法性(尤其是2月日期)
  1. @echo off &set/p "_md=输入日期(月值/日值):"
  2. for /f %%v in (
  3. 'powershell "('2024/%_md%' -as [DateTime]) -ne $null" '
  4. ) do if /i "%%v"=="true" (echo,输入合法) else (echo,输入非法)
  5. pause&exit/b
复制代码

作者: 蚯蚓传奇    时间: 2024-8-19 18:16

回复 2# aloha20200628


    感谢分享,我刚才试了这段代码,你判断日期是否合法的那段代码我已经采纳了,可是我主要是想让他判断格式是否符合**/**,不知道能不能实现
作者: aloha20200628    时间: 2024-8-19 21:04

本帖最后由 aloha20200628 于 2024-8-19 21:10 编辑

回复 3# 蚯蚓传奇

增添子过程 :xxx 用于检查日期输入格式是否满足 mm/dd,分隔符须为 /,mm 和 dd 都必须是两位数字...
  1. @echo off &setlocal &set/p "md=输入日期(月值/日值):"
  2. call :xxx "%md%"
  3. if %ok% equ 0 (echo,输入非法)&exit/b
  4. for /f %%v in (
  5. 'powershell "('2024/%md%' -as [DateTime]) -ne $null" '
  6. ) do if /i "%%v"=="true" (echo,输入合法) else (echo,输入非法)
  7. endlocal&pause&exit/b
  8. :xxx
  9. set "v=%~1" &set "ok=0" &set "n="
  10. if "%v:~4,1%" neq "" if "%v:~5%"=="" (
  11. for /f "delims=0123456789/" %%n in ("%~1") do set "n=1"
  12. if not defined n set "ok=1"
  13. )
  14. exit/b
复制代码

作者: WHY    时间: 2024-8-19 21:32

  1. set /p "dt=Input:"
  2. PowerShell "$out=0; [DateTime]::TryParseExact('%dt%', 'M/d', [Globalization.CultureInfo]'zh-CN', [Globalization.DateTimeStyles]::None, [ref]$out)"
复制代码

作者: ppll2030    时间: 2024-8-19 22:51

本帖最后由 ppll2030 于 2024-8-20 13:22 编辑

回复 3# 蚯蚓传奇


    在第一和第二行之间,加一句代码即可,有且仅对日期格式判断
  1. echo %_md%|findstr /x "[0-1][0-9]/[0-3][0-9]">nul||(echo 日期格式不正确&pause&exit /b)
复制代码
把日期对错也一并加入判断了。一次性完成日期对错和日期格式的判断。
  1. @echo off
  2. set/p "_md=输入日期(月值/日值):"
  3. echo %_md%|findstr /x "0[1-9]/0[1-9] 0[1-9]/[1-2][0-9] 0[13578]/3[0-1] 0[469]/30 1[0-2]/0[1-9] 1[0-2]/[1-2][0-9] 1[02]/3[0-1] 11/30">nul||set "v=不"
  4. echo 日期输入%v%正确!&pause&exit/b
复制代码

作者: 77七    时间: 2024-8-19 23:01

  1. @echo off
  2. set /p str=input:
  3. call :1
  4. if defined k (
  5. echo 合法
  6. ) else (
  7. echo 非法
  8. )
  9. pause
  10. exit
  11. :1
  12. setlocal
  13. for /f %%a in ('wmic os get localdatetime ^|findstr [0-9]') do set year=%%a&call set year=%%year:~0,4%%
  14. set /a _1=_3=_5=_7=_8=_10=_12=31,_4=_6=_9=_11=30
  15. rem "http://www.bathome.net/redirect.php?goto=findpost&ptid=3769&pid=24349"
  16. set/a num=!(year%%4) ^& !(!(year%%100)) ^| !(year%%400),1/num 2>nul && set _2=29|| set _2=28
  17. setlocal enabledelayedexpansion
  18. for /l %%l in (1,1,12) do for /l %%m in (1,1,!_%%l!) do (set l=0%%l&set m=0%%m&set #!l:~-2!/!m:~-2!=1)
  19. if defined #!str! (endlocal &endlocal &set k=1) else (endlocal &endlocal)
  20. exit /b
复制代码

作者: aloha20200628    时间: 2024-8-20 00:32

本帖最后由 aloha20200628 于 2024-8-24 22:32 编辑


参考6楼代码用 findstr 小正则卡位定界 mm/dd 格式输入,句式更为简洁,如此4楼代码可减肥‘到底’ ,给出如下两个等效版本...

第一版本》先行正则卡位定界,再行powershell合法性检查
  1. @echo off &setlocal &set/p "md=输入日期(月值/日值):"
  2. echo,%md%|findstr /rc:"[01][0-9]/[0-3][0-9]">nul||echo,非法&endlocal&pause&exit/b
  3. powershell "('2024/%md%' -as [DateTime]) -ne $null"|find /i "true">nul
  4. if errorlevel 1 (echo,非法) else (echo,合法)
  5. endlocal&pause&exit/b
复制代码
第二版本》先行powershell合法性检查,再行卡位定界
  1. @echo off &setlocal &set "w=非法" &set/p "md=输入日期(月值/日值):"
  2. powershell "('2024/%md%' -as [DateTime]) -ne $null"|find /i "true">nul
  3. if errorlevel 1 (set k=1) else if "%md:~2,1%"=="/" if "%md:~-3,1%"=="/" set "w=合法"
  4. echo,%w%&endlocal&pause&exit/b
复制代码

作者: 蚯蚓传奇    时间: 2024-8-20 08:39

回复 6# ppll2030


    谢谢分享,可以用,而且效率很高
作者: 77七    时间: 2024-8-20 10:59

本帖最后由 77七 于 2024-8-20 11:00 编辑

echo 一个未知的字符串|find (findstr)其实是一个很危险的操作,因为特殊字符影响会导致各种错误
  1. @echo off
  2. set "str=1&2"
  3. echo %str%|find "1" 1>nul && echo #1 闪退
  4. echo %str%|find "2" 1>nul && echo #2 闪退
  5. echo "%str%"|find "1" 1>nul && echo #3 判断成功
  6. echo "%str%"|find "2" 1>nul && echo #4 判断成功
  7. setlocal enabledelayedexpansion
  8. echo !str!|find "1" 1>nul && echo #5 报错,判断成功
  9. echo !str!|find "2" 1>nul && echo #6 报错,判断失败
  10. set "str=1&2""
  11. echo "%str%"|find "1" 1>nul && echo #7 整行的特殊符号都被双引号转义
  12. pause
复制代码

作者: WHY    时间: 2024-8-20 11:21

简单、方便、准确,让神马的 for 和 if 见鬼去吧。
  1. $myInput  = Read-Host 'Input';
  2. $formats  = [String[]]('MM/dd', 'M/d');
  3. $provider = [Globalization.CultureInfo]'zh-CN';
  4. $style    = [Globalization.DateTimeStyles]::None;
  5. $result   = [DateTime]0;
  6. [DateTime]::TryParseExact($myInput, $formats, $provider, $style, [ref]$Result);
  7. [Console]::ReadLine()
复制代码
https://learn.microsoft.com/zh-cn/dotnet/api/system.datetime.tryparseexact?view=net-8.0
作者: aloha20200628    时间: 2024-8-20 12:16

本帖最后由 aloha20200628 于 2024-8-20 17:06 编辑

回复 10# 77七

‘纯P老窖’ 还留了一款  set/p="..."<nul  此时正好派上用场...
echo,"..." 连双引号一并输出;而 set/p="..."<nul 只输出双引号包裹的内容,但先戴上双引号即可免遭 ‘雷劈’
  1. set/p="1&2"<nul|find "1">nul&&echo,#1
  2. set/p="1&2"<nul|find "2">nul&&echo,#2
复制代码

作者: aloha20200628    时间: 2024-8-20 12:44

回复 6# ppll2030
回复 9# 蚯蚓传奇

网查用正则匹配(同时也是检查)日期格式有多种杂乱版本,实测了一把6楼代码,发现有一处须予订正如下,否则会漏检 10/00,11/00,12/00 的错误:

   旧码》findstr /x "0[1-9]/0[1-9] 0[1-9]/[1-2][0-9] 0[13578]/3[0-1] 0[469]/30 1[0-2]/0[0-9] 1[0-2]/[1-2][0-9] 1[02]/3[0-1] 11/30"
   新码》findstr /x "0[1-9]/0[1-9] 0[1-9]/[1-2][0-9] 0[13578]/3[0-1] 0[469]/30 1[0-2]/0[1-9] 1[0-2]/[1-2][0-9] 1[02]/3[0-1] 11/30"

另注》仅用正则匹配不能兼顾平/闰年2月增减天数的变化,还须增添代码修正...

作者: 77七    时间: 2024-8-20 13:08

回复 12# aloha20200628


  set /p="..."<nul 对比echo "..."优点不多,缺点倒是多一些
作者: ppll2030    时间: 2024-8-20 13:20

回复 13# aloha20200628


    感谢大佬指正~!
大半夜想这些烧脑的东西,效率还是低了。
作者: qixiaobin0715    时间: 2024-8-21 09:00

本帖最后由 qixiaobin0715 于 2024-8-21 09:13 编辑

回复 6# ppll2030
看到你第2个代码,使用findstr一般表达式,头都晕了,感觉太繁琐了。高手!!
自己设想是不是用判断语句思路更清晰些,也很简单。谁知眼高手低,写起来也是很费劲,也一样繁琐。还是发出来让需要的人作为参考,即使不需要 ,也可作为判断语句学习时的参考:
  1. @echo off
  2. :o
  3. set/p "_md=Please Input the Date (MM/DD):"
  4. for /f "tokens=1* delims=/" %%i in ("%_md%") do (
  5.     if not "%%j"=="" (
  6.         if "%%i" geq "01" if "%%i" leq "12" if "%%j" geq "01" if "%%j" leq "31" set n=1
  7.         if defined n (
  8.             if "%%i" equ "02" (
  9.                 if "%%j" geq "30"    set n=
  10.             ) else (
  11.                 for %%k in (04 06 09 11) do if "%%i" equ "%%k" if "%%j" equ "31" set n=
  12.             )
  13.         )
  14.     )
  15. )
  16. if not defined n echo,Input Error&goto :o
  17. echo,Input Correct
  18. pause
复制代码
总是感觉哪里不对。1/1也是合法的。
作者: ppll2030    时间: 2024-8-21 09:17

本帖最后由 ppll2030 于 2024-8-21 09:20 编辑

回复 16# qixiaobin0715


    哈哈。不是高手,因为我看你的这段也是头大
我对if嵌套很不喜,套多了就脑子不够用,逻辑跟不上。;P
我是能不用就不用。

至于这个findstr,其实很简单的啊,就是正向匹配而已。用表达式把符合的情况都列出来即可。
这不,半夜里费脑子,还是被大佬揪出瑕疵了。
作者: ppll2030    时间: 2024-8-21 09:30

回复 16# qixiaobin0715


    大概看到问题了。你这个if只是对数值的大小比较啊,所以 1 geq 01 是成立的。01在数值比较里就是1啊
作者: qixiaobin0715    时间: 2024-8-21 09:45

本帖最后由 qixiaobin0715 于 2024-8-21 10:13 编辑

回复 18# ppll2030
加了一段代码,这样应当可以了,保留上面代码,可以对比参考:
  1. @echo off
  2. :o
  3. set/p "_md=Please Input the Date (MM/DD):"
  4. for /f "tokens=1* delims=/" %%i in ("%_md%") do (
  5.     if not "%%j"=="" (
  6.         if "%%i" geq "01" if "%%i" leq "12" if "%%j" geq "01" if "%%j" leq "31" set n=1
  7.         for /l %%k in (1,1,3) do (
  8.             if "%%k" equ "%%i" set n=
  9.             if "%%k" equ "%%j" set n=
  10.         )
  11.         if defined n (
  12.             if "%%i" equ "02" (
  13.                 if "%%j" geq "30"    set n=
  14.             ) else (
  15.                 for %%k in (04 06 09 11) do if "%%i" equ "%%k" if "%%j" equ "31" set n=
  16.             )
  17.         )
  18.     )
  19. )
  20. if not defined n echo,Input Error&goto :o
  21. echo,Input Correct
  22. pause
复制代码

作者: qixiaobin0715    时间: 2024-8-21 09:47

回复 18# ppll2030
非数值比较,加了引号就是字符串比较。
作者: qixiaobin0715    时间: 2024-8-21 10:04

回复 18# ppll2030
  1. if 1 equ 01 echo,good
  2. if "1" equ "01" echo,good
  3. if "3" gtr "111" echo,good
复制代码

作者: ppll2030    时间: 2024-8-21 10:35

回复 21# qixiaobin0715


    好吧。理解错了。字符串比较大小基本没用过。除了那个两字符串是否相符的“==” 回头补习一下。
作者: 77七    时间: 2024-8-21 10:41

回复 19# qixiaobin0715

大佬,我试了一下,非数字也正确
  
  1. Please Input the Date (MM/DD):11/1qqqq
  2. Input Correct
复制代码

作者: ppll2030    时间: 2024-8-21 10:45

回复 19# qixiaobin0715


    估计还要修改一下下

Please Input the Date (MM/DD):12-12
Input Error
Please Input the Date (MM/DD):12/12
Input Correct
请按任意键继续. . .
Please Input the Date (MM/DD):0/31
Input Correct
请按任意键继续. . .
Please Input the Date (MM/DD):10/31
Input Correct
请按任意键继续. . .
Please Input the Date (MM/DD):6/31
Input Correct
请按任意键继续. . .
Please Input the Date (MM/DD):4/31
Input Correct
请按任意键继续. . .
Please Input the Date (MM/DD):/31
Input Correct
请按任意键继续. . .
Please Input the Date (MM/DD):5/31
Input Correct
请按任意键继续. . .
作者: qixiaobin0715    时间: 2024-8-21 11:42

本帖最后由 qixiaobin0715 于 2024-8-21 12:21 编辑

回复 24# ppll2030
你的测试方法不对。是否是在代码最后加上一个goto循环?这样会有变量残值存在,有些结果会不一样。不信你可以试试。但确实存在非数字也能通过的问题。
你想用这种方法测试的话,在本次循环后,代码再次运行前,必须消减掉上一次运行时的变量残值,在你后加的goto循环前一行,也即是倒数第2行加上这段代码:
  1. set _md=
  2. set n=
复制代码

作者: qixiaobin0715    时间: 2024-8-21 11:57

回复 23# 77七
看样子用判断语句也是挺繁琐的,set /p下加一行:
  1. for /f "tokens=1* delims=0123456789" %%i in ("%_md%") do if not "%%i%%j"=="/" goto :p
复制代码
第19、20行之间加上循环标签:p
不知还有何bug
作者: aloha20200628    时间: 2024-8-21 12:39

回复 19# qixiaobin0715

用纯P一贯到底,还是应该先解决 ‘卡位和定界’,即检查输入长度是否=5,检查第三位是否为 /,检查定界符之外是否均为数字,其后再完成月值与日值的关联处理,否则会使处理逻辑复杂化...

另说》纯P内部数据处理也许只有字符串一种数据类型,因此以下三式返回值相同
   if 09 lss 1 (@echo,1) else @echo,0
   if "09" lss "1" (@echo,1) else @echo,0
   if 0q lss 1 (@echo,1) else @echo,0
作者: qixiaobin0715    时间: 2024-8-21 12:43

回复 27# aloha20200628
确实还有bug:0101/11
思路有问题,需要重写。
作者: qixiaobin0715    时间: 2024-8-21 16:08

重写代码,部分参考或者说借鉴27楼的思路,先按正确的格式定义月份日期,再进行判断:
  1. @echo off
  2. for /l %%i in (1,1,31) do (
  3.     if %%i lss 10 (
  4.         set @0%%i=1
  5.         set #0%%i=1
  6.     ) else (
  7.         set @%%i=1
  8.         if %%i leq 12 set #%%i=1
  9.     )
  10. )
  11. :o
  12. set/p "md=Please Input the Date (MM/DD):"
  13. if not "%md://=%"=="%md%" goto :p
  14. for /f "tokens=1* delims=/" %%i in ("%md%") do (
  15.     if defined #%%i if defined @%%j set n=1
  16.     if defined n (
  17.         if "%%i" equ "02" (
  18.             if "%%j" geq "30"    set n=
  19.         ) else (
  20.             for %%k in (04 06 09 11) do if "%%i" equ "%%k" if "%%j" equ "31" set n=
  21.         )
  22.     )
  23. )
  24. :p
  25. if not defined n echo,Input Error&goto :o
  26. echo,Input Correct
  27. pause
复制代码

作者: qixiaobin0715    时间: 2024-8-21 16:20

本帖最后由 qixiaobin0715 于 2024-8-21 16:22 编辑

代码第13行是为了过滤类似这样的输入合法通过:01//01
如果觉得无所谓,去掉也行。
越搞越复杂。
就当练手吧。
作者: 77七    时间: 2024-8-21 16:25

回复 30# qixiaobin0715


   月份日期分开定义,再拆开判断,不如直接全定义 #01/01 ...#12/31,然后结束了...
作者: qixiaobin0715    时间: 2024-8-21 16:28

本帖最后由 qixiaobin0715 于 2024-8-21 16:29 编辑

回复 31# 77七
想过,代码会缩减很多。但变量定义过多(至少366个),会影响效率。
上面说过了,主要是练练手。
作者: WHY    时间: 2024-8-21 21:16

本帖最后由 WHY 于 2024-8-21 21:25 编辑

回复 29# qixiaobin0715


    还要考虑一下,分隔符位于字符串开头的情况, for /f 会去掉字符串开头的分隔符。
比如输入:/03/12
作者: WHY    时间: 2024-8-21 21:17

个人观点:特殊字符是 CMD 的硬伤,用纯P来解此题,简直就是自找麻烦。
吐槽一下:“你认为有前途的,其他人或许认为就是辣鸡”,事实证明这句话是对的。
作者: buyiyang    时间: 2024-8-21 21:27

才5个字符,用批处理的话逐字符判断更可靠一些
  1. @echo off
  2. set/p "data=Please enter a date (MM/DD):"
  3. set "error=echo,Invalid date&pause&goto :eof"
  4. setlocal enabledelayedexpansion
  5. for /l %%i in (5,-1,0) do (
  6.     set "data%%i=!data:~%%i,1!"
  7.     if not "!data%%i!"=="" (
  8.         if %%i equ 5 (
  9.             %error%
  10.         ) else if %%i equ 2 (
  11.             if "!data%%i!" neq "/" %error%
  12.         ) else if "!data%%i!" lss "0" (
  13.             %error%
  14.         ) else if "!data%%i!" gtr "9" %error%
  15.     ) else (
  16.         if %%i equ 4 %error%
  17.     )
  18. )
  19. if %data0% equ 0 set "data0="
  20. if %data3% equ 0 set "data3="
  21. set mm=%data0%%data1%
  22. set dd=%data3%%data4%
  23. if %mm% gtr 12 %error%
  24. if %dd% gtr 31 %error%
  25. for /f "tokens=2" %%i in ('wmic os get codeset^,localdatetime') do (
  26.     set yyyy=%%i
  27.     set "yyyy=!yyyy:~0,4!"
  28.     )
  29. endlocal&set /a mm=%mm%,dd=%dd%,yyyy=%yyyy%
  30. set /a "gtm=!(mm-4)|!(mm-6)|!(mm-9)|!(mm-11)","leap=!(yyyy%%4)&!!(yyyy%%100)|!(yyyy%%400)","dlim=!(mm-2)*(28+leap)+gtm*30+(!!(mm-2)&!gtm)*31"
  31. if %dd% gtr %dlim% (%error%) else (echo,Valid date)
  32. pause&exit /b
复制代码

作者: buyiyang    时间: 2024-8-21 21:32

回复 27# aloha20200628


    因为09不是有效的八进制数字,试试0x9
作者: 77七    时间: 2024-8-22 08:34

回复 35# buyiyang


   
大佬,00/00的情况,没有判断,防不胜防
作者: qixiaobin0715    时间: 2024-8-22 09:04

本帖最后由 qixiaobin0715 于 2024-8-22 09:07 编辑

回复 34# WHY
我也知道这么一个问题,用cmd搞出如此复杂的代码,确实不合适。
开始看到这个帖子,主要是想籍此私下练习练习逻辑思维,等到着手写代码时,没想到会是这么复杂,自己心里没底,发出来让大家看看 ,真是漏洞百出,最后重写代码,还是有漏洞。
最后的代码是借鉴27楼的建议重写的,发上来前,在13行下面有一行代码,是用来判断第3个字符是否为“/”的,个人感觉后续代码能够处理此问题,就给删除了,看来还是有用的,最少能够修补你在33楼所说的漏洞。
还是要在29楼第13、14行之间,加上删除的那行代码:
  1. if not "%md:~2,1%"=="/" goto :p
复制代码

作者: qixiaobin0715    时间: 2024-8-22 10:48

回复 31# 77七
私下试了一把,全定义确实省事。
作者: 77七    时间: 2024-8-22 11:11

用临时文件,仅判断两位数+/+两位数,应该可以对付所有特殊字符了吧
  1. @echo off
  2. set /p str=input:
  3. setlocal enabledelayedexpansion
  4. >1.txt echo=#!str!
  5. endlocal
  6. findstr /rxc:"#[0-9][0-9]/[0-9][0-9]" 1.txt
  7. if not errorlevel 1 (
  8. echo ok
  9. )
  10. pause
复制代码

作者: aloha20200628    时间: 2024-8-22 11:15

本帖最后由 aloha20200628 于 2024-8-22 11:19 编辑


即便是批处这类简单脚本级的造码,也是讲究 ‘有效 》可靠 》高效 》简洁’ 四重关,新手过头关,老手要高攀,若每次出手都能 ‘会当凌绝顶’,真可谓功德圆满了... 共勉共勉

作者: qixiaobin0715    时间: 2024-8-22 12:46

本帖最后由 qixiaobin0715 于 2024-8-22 12:48 编辑

仔细的研究了findstr命令的一般表达式,确实功能极其有限,相当于极简版的正则。有点手痒痒,也来一个吧。匹配原则:先多后少,先一般后特殊。
  1. findstr /x "0[1-9]/[0-2][1-9] 1[0-2]/[0-2][1-9] 0[1-9]/[12]0 1[0-2]/[12]0 0[13-9]/30 1[0-2]/30 0[13578]/31 1[02]/31"
复制代码
表达式说明:
0[1-9]/[0-2][1-9]    ——    1、2组对应所有月份,日期在30以内的组合,不包括日期10、20、30,因为不易避开非法日期00,还是单独处理为好;
1[0-2]/[0-2][1-9]
0[1-9]/[12]0           ——    3、4组单独处理上面所说的未包括的日期10、20,也是对应所有月份;
1[0-2]/[12]0
0[13-9]/30              ——    5、6组对应日期为30的月份,2月除外;
1[0-2]/30
0[13578]/31            ——    最后2组对应日期为31的月份,包括1、3、5、7、8、10、12月;
1[02]/31
欢迎大家批评指正。
作者: buyiyang    时间: 2024-8-22 13:04

回复 37# 77七


    感谢测试,限制上限的时候忘记限制下限了,再补上 set /a "1/(!!mm&!!dd)" 2>nul ||(%error%) 就没有问题了。
作者: qixiaobin0715    时间: 2024-8-24 09:54

回复 36# buyiyang
细致!严谨!
作者: aloha20200628    时间: 2024-8-24 13:57

本帖最后由 aloha20200628 于 2024-8-24 23:07 编辑


再练一把 ‘纯P进行到底’ 的脑力操...
给一个纯P版本的日期输入合法性检查代码,不用外部方法,不用正则匹配
算法简明得益于采用 ‘字典变量’,尽可能考虑了各种防错对策》分隔符及其错位,日月数值溢出,日月相关,平闰年天数区分... 还是回归日月均为两位数的要求。
以下代码采用循环输入以便完成各种测试,直至空回车退出...
  1. @echo off &setlocal
  2. set/a "_1=31,_2=28,_3=31,_4=30,_5=31,_6=30,_7=31,_8=31,_9=30,_10=31,_11=30,_12=31,y=%date:~,4%%%4"
  3. if %y% equ 0 set "_2=29"
  4. :[md_loop]
  5. set "md=" &set "c=" &set/p "md=输入日期(月值/日值):"
  6. if not defined md endlocal&exit/b
  7. setlocal enabledelayedexpansion &set k=!md:"=!
  8. if "!k!" neq "!md!" echo,非法&endlocal&goto[md_loop]
  9. endlocal &if "%md:~5%" neq "" echo,非法&goto[md_loop]
  10. if "%md:~4,1%"=="" echo,非法&goto[md_loop]
  11. for /f "tokens=1,2 delims=/" %%1 in ("%md%") do set "m=%%1"&set "d=%%2"
  12. if "%md%"=="%m%" echo,非法&goto[md_loop]
  13. for /f "delims=0123456789" %%n in ("%m%%d%") do set "c=1"
  14. if defined c echo,非法&goto[md_loop]
  15. if not defined d echo,非法&goto[md_loop]
  16. if "%md:~2,1%" neq "/" echo,非法&goto[md_loop]
  17. if "%m:~0,1%" equ "0" if "%m:~1,1%" neq "" set "m=%m:~1%"
  18. set "_m=_%m%" &set "w=非法" &setlocal enabledelayedexpansion
  19. if "%d:~0,1%" equ "0" if "%d:~1,1%" neq "" set "d=%d:~1%"
  20. if %d% gtr 0 if %d% leq !%_m%! set "w=合法"
  21. echo,!w!&endlocal&goto[md_loop]
复制代码

作者: buyiyang    时间: 2024-8-24 17:13

本帖最后由 buyiyang 于 2024-8-24 17:47 编辑

回复 45# aloha20200628
1//1a
1/--1
1/+1
一个是分隔符被忽略的问题,一个是if比较时不能转换成有效数字的会按字符比较,数字比较时没有排除正负号。
忘了还有变量延迟下的两个特殊字符,
1/!1
1/!^1
作者: 77七    时间: 2024-8-24 18:20

本帖最后由 77七 于 2024-8-24 18:29 编辑
  1. @echo off
  2. for /f %%a in ('wmic os get localdatetime ^|findstr [0-9]') do set year=%%a&call set year=%%year:~0,4%%
  3. set /a _1=_3=_5=_7=_8=_10=_12=31,_4=_6=_9=_11=30
  4. set/a num=!(year%%4) ^& !(!(year%%100)) ^| !(year%%400),1/num 2>nul && set _2=29|| set _2=28
  5. :loop
  6. setlocal
  7. set /p n=input:
  8. set "n1=%n:~0,2%"
  9. set "n2=%n:~2,1%"
  10. set "n3=%n:~3,2%"
  11. set "n4=%n:~5%"
  12. setlocal enabledelayedexpansion
  13. set a=非法
  14. if "!n2!" equ "/" (
  15. if "!n4!" equ "" (
  16. for /l %%l in (1,1,12) do (
  17. set str=0%%l
  18. if "!n1!" equ "!str:~-2!" (
  19. for /l %%m in (1,1,!_%%l!) do (
  20. set str=0%%m
  21. if "!n3!" equ "!str:~-2!" (
  22. set a=合法
  23. )
  24. )
  25. )
  26. )
  27. )
  28. )
  29. endlocal & endlocal &echo %a%
  30. goto :loop
复制代码

判断 相应位置的字符串与 分隔符号 "/" 及限定范围的日期、月份是否有相等情况。初步测试,可以应付各种特殊字符。
作者: 77七    时间: 2024-8-24 18:53

简化一下
  1. @echo off
  2. for /f %%a in ('wmic os get localdatetime ^|findstr [0-9]') do set year=%%a&call set year=%%year:~0,4%%
  3. set /a _1=_3=_5=_7=_8=_10=_12=31,_4=_6=_9=_11=30
  4. set/a num=!(year%%4) ^& !(!(year%%100)) ^| !(year%%400),1/num 2>nul && set _2=29|| set _2=28
  5. :loop
  6. setlocal
  7. set /p n=input:
  8. setlocal enabledelayedexpansion
  9. set a=非法
  10. if "!n:~2,1!!n:~5,1!" equ "/" (
  11. for /l %%l in (1,1,12) do (
  12. set str=0%%l
  13. if "!n:~0,2!" equ "!str:~-2!" (
  14. for /l %%m in (1,1,!_%%l!) do (
  15. set str=0%%m
  16. if "!n:~3,2!" equ "!str:~-2!" (
  17. set a=合法
  18. )
  19. )
  20. )
  21. )
  22. )
  23. endlocal & endlocal &echo %a%
  24. goto :loop
复制代码

作者: aloha20200628    时间: 2024-8-24 19:09

本帖最后由 aloha20200628 于 2024-8-24 19:46 编辑

回复 46# buyiyang

45楼代码已订正,堵上了被多种 ‘怪异字符’ 轰击破防的缺口...

本帖歇后语》不用外部方法援助,不用正则匹配简化,感觉纯P自力更生的造码能力还可以...而真正的着力点落在了应对预处理和防止怪异字符的雷劈...

作者: buyiyang    时间: 2024-8-24 19:44

回复 45# aloha20200628

endlcoal 后第二行设定的变量也无了,
第14行的if %m% lss 1 if %m% gtr 12 逻辑有误,其实这一行不要也行,第17行if %d% gtr 0 if %d% leq !%_m%! 兼有限制月份的作用。
作者: aloha20200628    时间: 2024-8-24 20:52

本帖最后由 aloha20200628 于 2024-8-24 21:02 编辑

回复 50# buyiyang
谢谢细心测试 45楼代码已被订正...
要纯P到底,那些 卡位,定界,全数字核查... 一点懒儿都不能偷

作者: buyiyang    时间: 2024-8-24 21:11

本帖最后由 buyiyang 于 2024-8-25 12:47 编辑

综合一下35、45、48、55楼,有一些不错的写法
最终主体只需要9、11、12行三句判断就够了。
第9行限制输入字符数和分隔符位置,
第11行限制月份和日期均为数字,
第12行限制月份和日期范围。
  1. @echo off
  2. for /f "tokens=2" %%i in ('wmic os get codeset^,localdatetime') do set yyyy=%%i&call set yyyy=%%yyyy:~0,4%%
  3. set /a _01=_03=_05=_07=_08=_10=_12=31,_04=_06=_09=_11=30,"_02=28+(!(yyyy%%4)&!!(yyyy%%100)|!(yyyy%%400))"
  4. set "error=echo,Invalid date&endlocal&goto :loop"
  5. :loop
  6. set /p "md=Please enter a date (MM/DD):"
  7. setlocal enabledelayedexpansion
  8. if "!md:~2,-2!" neq "/" (%error%)
  9. set "mm=!md:~0,2!"&set "dd=!md:~3,2!"
  10. for /f "delims=0123456789" %%i in ("!mm!!dd!") do (%error%)
  11. if "!dd!" gtr "00" if "!dd!" leq "!_%mm%!" (echo,Valid date&endlocal&goto :loop)
  12. %error%
复制代码

作者: 77七    时间: 2024-8-24 21:54

回复 45# aloha20200628

大佬,感觉应对特殊字符还是开启延迟变量扩展专业,如第7、8行无法处理 双引号
作者: aloha20200628    时间: 2024-8-24 23:11

本帖最后由 aloha20200628 于 2024-8-24 23:26 编辑

回复 53# 77七

谢谢 代码已订正... 但愿是最后一把 ‘堵漏灵’...
从8楼调用 powershell+findstr 外援的4-5行代码到45楼的纯P自力更生版,已经垒起21行代码了,要经得起各种怪异字符的 ‘雷劈’ ... 纯粹是要过把瘾的一套脑力操...

作者: qixiaobin0715    时间: 2024-8-25 10:03

本帖最后由 qixiaobin0715 于 2024-8-25 10:39 编辑

不包括修改代码,我也发上来过两版代码,就是漏洞太多。总体感觉是,不能自我陶醉,沾沾自喜,总是觉得自己的代码思路好,完美无缺,实际上还差得远着呢,低调最重要。
不怕大家笑话,再来一版,希望大佬们批评指正。还是限定输入格式为:01/01这样的。
下面代码的前一部分还是主要借鉴了27楼的思路。原则上尽量少预设变量(29楼代码开始预设了几十个变量,主要是便于后面进行判断,可能会影响一些效率)。第6行判断语句既定位了“/”字符,又能限定输入的字符长度为5,一举两得,主要还是得益于aloha20200628的提醒:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. :o
  4. set/p "md=Please Input the Date (MM/DD):"
  5. set "mm/dd=!md!"
  6. if "!mm/dd:~2,-2!"=="/" (
  7.     if "!mm/dd:00=!"=="!mm/dd!" (
  8.         for /l %%i in (0,1,9) do set "mm/dd=!mm/dd:%%i=!"
  9.         if "!mm/dd!"=="/" set n=1
  10.     )
  11. )
  12. if not defined n goto :p
  13. for /f "tokens=1* delims=/" %%i in ("!md!") do (
  14.     if %%i gtr 12 (set n=) else if %%j gtr 31 (set n=)
  15.     if defined n (
  16.         if %%j equ 30 (
  17.             if %%i equ 2 set n=
  18.         ) else if %%j equ 31 (
  19.             for %%k in (02 04 06 09 11) do if %%k equ %%i set n=
  20.         )
  21.     )
  22. )
  23. :p
  24. if defined n (echo,Input Correct) else (echo,Input Error&goto :o)
  25. pause
复制代码
另外,改变一下输入格式。合法:1/2  1/31  01/02  01/31  12/01  12/1,非法:01/2  1/01。如何才能判断?
作者: 77七    时间: 2024-8-25 11:33

回复 55# qixiaobin0715


   大佬,您最后提出的新问题,我没看懂...
作者: qixiaobin0715    时间: 2024-8-25 12:01

本帖最后由 qixiaobin0715 于 2024-8-25 12:03 编辑

回复 56# 77七
我的失误,没有表达清楚。有人习惯月份日期是个位数时前面加个0,都变成两位数;有人不习惯加0补位,直接写。我要表达的是,两种不能混合写,比如1月1日,要么写成01/01或者1/1,而不能写成01/1或1/01。
作者: 77七    时间: 2024-8-25 12:08

回复 55# qixiaobin0715


   大佬,我觉得几十个变量不会影响效率。尤其类似的简短变量 set #01=01,试着定义了5000个,耗时1秒左右,然后判断是否定义,echo %time%对比了下,时间相同。
  1. 12:07:14.02
  2. 12:07:15.26
  3. 1
  4. 12:07:15.26
复制代码


分别为定义开始时间、结束时间、判断是否被定义时间
作者: buyiyang    时间: 2024-8-25 12:26

本帖最后由 buyiyang 于 2024-8-25 12:32 编辑

回复 55# qixiaobin0715


    我尝试写了一个
  1. @echo off
  2. for /f "tokens=2" %%i in ('wmic os get codeset^,localdatetime') do set yyyy=%%i&call set yyyy=%%yyyy:~0,4%%
  3. set /a _01=_03=_05=_07=_08=_10=_12=31,_04=_06=_09=_11=30,"_02=28+(!(yyyy%%4)&!!(yyyy%%100)|!(yyyy%%400))"
  4. set "error=echo,Invalid date&endlocal&goto :loop"
  5. :loop
  6. set /p "md=Please enter a date (M/D):"
  7. setlocal enabledelayedexpansion
  8. set "a=!md:*/=!"&set "dd=!a:/=!"
  9. if "!a!" neq "!dd!" (%error%)
  10. for /f "delims=0123456789" %%i in ("!md:/=!") do (%error%)
  11. for /f "tokens=1* delims=/" %%i in ("!md!") do (endlocal&set "mm=%%i"&set "dd=%%j")
  12. setlocal enabledelayedexpansion
  13. if "!dd!"=="" (%error%)
  14. if "!mm:~2,1!" neq "" (%error%)
  15. if "!dd:~2,1!" neq "" (%error%)
  16. if "!mm:~0,1!" equ "0" if "!dd:~1,1!"=="" (%error%)
  17. if "!dd:~0,1!" equ "0" if "!mm:~1,1!"=="" (%error%)
  18. set "mm=0!mm!"&set "mm=!mm:~-2!"
  19. set "dd=0!dd!"&set "dd=!dd:~-2!"
  20. if "!dd!" gtr "00" if "!dd!" leq "!_%mm%!" (echo,Valid date&endlocal&goto :loop)
  21. %error%
复制代码

作者: aloha20200628    时间: 2024-8-25 15:50

本帖最后由 aloha20200628 于 2024-8-25 16:18 编辑


精简了45楼代码中的操作步骤和合法性核查逻辑,行数减至18行,8个goto减至2个...
操作步骤要点》
   双引号排查
   输入长度排查
   定界符及其错位排查
   日月值全数字排查
   日月相关变化所对应的字典变量 以及 平闰年天数增减修正
   全程严防 &!"^ 等特殊字符雷劈的对策...
再次感谢各位细心测试反馈 就算是一场把纯P贯彻到底的基本功组合复习吧...
  1. @echo off &setlocal
  2. set/a "_1=31,_2=28,_3=31,_4=30,_5=31,_6=30,_7=31,_8=31,_9=30,_10=31,_11=30,_12=31,y=%date:~,4%%%4"
  3. if %y% equ 0 set "_2=29"
  4. :[md_loop]
  5. set "$md=" &set "c=" &set/p "$md=输入日期(月值/日值):"
  6. if not defined $md endlocal&exit/b
  7. setlocal enabledelayedexpansion &set #md=!$md:"=!
  8. if "!#md!"=="!$md!" if "!$md:~5!"=="" if "!$md:~4,1!" neq "" if "!$md:~2,1!"=="/" (
  9. set "m=!$md:~0,2!"&set "d=!$md:~3!"
  10. for /f "delims=0123456789/" %%n in ("!$md!") do set "c=1"
  11. if not defined c goto[ok] )
  12. echo,非法&endlocal&goto[md_loop]
  13. :[ok]
  14. if "!m:~0,1!" equ "0" set "m=!m:~1!"
  15. set "_m=_!m!" &set "w=非法"
  16. if "!d:~0,1!" equ "0" set "d=!d:~1!"
  17. if !d! gtr 0 if !d! leq !%_m%! set "w=合法"
  18. echo,!w!&endlocal&goto[md_loop]
复制代码

作者: qixiaobin0715    时间: 2024-8-26 08:21

回复 58# 77七
是的,这里只是对1个数据进行判断,不考虑效率没问题,像你先前所说的全定义更简单。
作者: qixiaobin0715    时间: 2024-8-26 14:34

回复 58# 77七
实际上不喜欢为了让别人看起来自己写的代码行数较少,而强行将多行变成一行,比较喜欢你的风格,虽然从行数上看代码较长,但是一目了然,清清楚楚,最近我好像也有点给带偏了。
给自己代码较长强行找点理由。既然对于这个问题来说,效率不是那么重要,就来个数据全定义代码,这样子判断起来就简单多了,要是有空的话,帮忙测试一下耗时情况:
  1. @echo off
  2. for /l %%i in (1,1,12) do (
  3.     for /l %%j in (1,1,31) do (
  4.         if %%i lss 10 (
  5.             if %%j lss 10 (
  6.                 set _"%%i/%%j"=0
  7.                 set _"0%%i/0%%j"=0
  8.             ) else (
  9.                 set _"%%i/%%j"=0
  10.                 set _"0%%i/%%j"=0
  11.             )
  12.         ) else (
  13.             if %%j lss 10 (
  14.                 set _"%%i/%%j"=0
  15.                 set _"%%i/0%%j"=0
  16.             ) else (
  17.                 set _"%%i/%%j"=0
  18.             )
  19.         )
  20.     )
  21. )
  22. for %%i in (2 4 6 9) do (
  23.     set _"%%i/31"=
  24.     set _"0%%i/31"=
  25. )
  26. set _"11/31"=
  27. set _"2/30"=
  28. set _"02/30"=
  29. setlocal enabledelayedexpansion
  30. :o
  31. set/p "md=Please Input the Date (MM/DD):"
  32. if defined _"!md!" (
  33.     echo,Input Correct
  34. ) else (
  35.     echo,Input Error
  36.     goto :o
  37. )
  38. pause
复制代码

作者: 77七    时间: 2024-8-26 15:45

回复 62# qixiaobin0715


   感谢大佬认可!我觉得代码发出来交流,如果写的紧凑到一起,至少我是不想读的。
用echo %time%简单测试了一轮,全部写明日期。
52楼(去除了获取年份及判断平闰年代码)耗时0.01秒
60楼 耗时0.01秒
62楼 耗时0.13秒
在本题实际应用环境下,感觉没必要考虑效率,毕竟打开批处理,就已经 “预处理” 完成了。

如果判断大量数据,猜测全定义效率最高,毕竟只要一个 if defined:lol
作者: qixiaobin0715    时间: 2024-8-26 16:12

回复 63# 77七
不好说,毕竟每次执行defined时都要从大量的预定义的变量中查找符合条件的变量。
作者: 77七    时间: 2024-8-26 16:27

回复 64# qixiaobin0715

大佬,我测试了下
   1.txt 为1-10000,共1万个数字用for /f 判断

  1. 16:25:16.03
  2. 16:25:16.15
  3. 16:25:16.20
复制代码



定义用了0.12秒,判断是否定义仅用了0.05秒
作者: qixiaobin0715    时间: 2024-8-26 16:40

回复 65# 77七
还是相信测试吧。
作者: aloha20200628    时间: 2024-8-26 16:49


批处是一种典型的解译型脚本,其执行原理基本上就是逐行处理(连注释行也不放过),也许对复合语块要先全块读尽再逐行处理,因此代码能并为一行会比分行的解译执行效率高,此被老帖早有述及,但这同时也会降低代码可读性... 总而言之,这是一个二者权衡尺度的问题,又是一个造码者编程风格或言个人偏好的问题...

作者: 77七    时间: 2024-8-26 17:22

回复 67# aloha20200628


   
大佬,我使用set /a 测试了一下
  1. @echo off
  2. for /l %%l in (1,1,5) do (
  3. timeout 3 >nul
  4. setlocal
  5. echo 紧凑型
  6. call echo %%time%%
  7. call :1
  8. call echo %%time%%
  9. endlocal
  10. echo=
  11. timeout 3 >nul
  12. setlocal
  13. echo 分散型
  14. call echo %%time%%
  15. call :2
  16. call echo %%time%%
  17. endlocal
  18. echo=
  19. )
  20. pause
  21. exit
  22. :1
  23. for /l %%l in (1,1,10000) do (
  24. set /a a=1&set /a b=2&set /a c=3&set /a d=4&set /a e=5&set /a f=6&set /a g=7
  25. )
  26. exit /b
  27. :2
  28. for /l %%l in (1,1,10000) do (
  29. set /a a=1
  30. set /a b=2
  31. set /a c=3
  32. set /a d=4
  33. set /a e=5
  34. set /a f=6
  35. set /a g=7
  36. )
  37. exit /b
复制代码


  1. 紧凑型
  2. 17:15:34.13
  3. 17:15:38.26
  4. 分散型
  5. 17:15:41.12
  6. 17:15:45.01
  7. 紧凑型
  8. 17:15:48.19
  9. 17:15:52.18
  10. 分散型
  11. 17:15:55.16
  12. 17:15:59.17
  13. 紧凑型
  14. 17:16:02.14
  15. 17:16:06.23
  16. 分散型
  17. 17:16:09.20
  18. 17:16:13.18
  19. 紧凑型
  20. 17:16:16.18
  21. 17:16:20.20
  22. 分散型
  23. 17:16:23.16
  24. 17:16:27.09
  25. 紧凑型
  26. 17:16:30.16
  27. 17:16:34.17
  28. 分散型
  29. 17:16:37.16
  30. 17:16:41.06
  31. 请按任意键继续. . .
复制代码


用&连接,效率反而要低些
作者: aloha20200628    时间: 2024-8-26 18:16

本帖最后由 aloha20200628 于 2024-8-26 18:30 编辑

回复 68# 77七

已说过也许批处对复合语块典型如for...句式是先全块读尽处理的,此乃for句式效率较高的原因,其内是不能如其外简单分行执行的...
与解译型脚本逐行解译执行原理一致的纯净测试应该如是:
   set a=...&set b=...&...

  set a=...
  set b=...
  ...
比对测试
但对批处的小微代码测试而言,其差别也许无感...

作者: qixiaobin0715    时间: 2024-8-26 18:48

回复 68# 77七
那就用goto循环语句测试一下试试。
作者: 77七    时间: 2024-8-26 18:52

回复 69# aloha20200628

简单测试了下,果然如大佬所言,call 了100次,&连接的耗时0.22秒,分开写的耗时0.36秒。
作者: 77七    时间: 2024-8-26 18:57

回复 70# qixiaobin0715


   goto测试,100次,&连接耗时0.23秒,分开写 0.40秒
作者: qixiaobin0715    时间: 2024-8-26 19:08

回复 72# 77七
谢谢测试,对此种效率有了更进一步的了解。
作者: WHY    时间: 2024-8-27 22:58

本帖最后由 WHY 于 2024-8-28 08:53 编辑

回复 68# 77七


    用 & 连接的语句,本身就是语句块;放到 for 循环里,语句块里面再套语句块,预处理时 cmd 认为是等价的,预处理花费的时间是一样的,执行时间相差无几。
就是说,
  1. for %%i in (1) do (
  2.     set "a=1"
  3.     set "b=2"
  4.     set "c=3"
  5. )
复制代码
  1. for %%i in (1) do set "a=1" & set "b=2" & set "c=3"
复制代码
以及
  1. for %%i in (1) do (
  2.     set "a=1" & set "b=2" & set "c=3"
  3. )
复制代码
等价

参考:http://www.bathome.net/thread-4482-1-1.html
PS: 想要提速,多在算法和思路上做文章,尽量少用call和goto;尽量少在for循环体内部使用外部命令。。。
用 & 、&&、||连接虽然可以带来效率提升,但提升空间非常有限,反倒是可读性、可维护性大打折扣,能不用就坚决不用吧。
作者: 77七    时间: 2024-8-28 00:39

回复 74# WHY


   
谢谢大佬指点!




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2