批处理文件中变量与常量的比较失败

3

我想写一段简单的代码来获取一些漂亮格式化的“时间戳”。 将时间获取到我的两个变量StartEnd中是可以的。我也可以打印出它的格式为0:0:0。但如果小于10,则我想要一个前导零,但显然我会得到一个错误,说“找不到参数10或拼写错误”。 我发现这似乎是要比较的变量,但我没有修复它。有什么想法吗?

@ECHO OFF
REM Time Calculation
FOR /F "skip=1 tokens=1-6" %%A IN ('WMIC Path Win32_LocalTime Get Day^,Hour^,Minute^,Second /Format:table ^| findstr /r "."') DO (
 set Day=%%A
 set Hour=%%B
 set Minute=%%C
 set Second=%%D
)
set /a Start=%Day%*8640000+%Hour%*360000+%Minute%*6000+%Second%*100
@ECHO ON
ping 8.8.8.8 -n 11
@ECHO OFF
FOR /F "skip=1 tokens=1-6" %%A IN ('WMIC Path Win32_LocalTime Get Day^,Hour^,Minute^,Second /Format:table ^| findstr /r "."') DO (
 set Day=%%A
 set Hour=%%B
 set Minute=%%C
 set Second=%%D
)
set /a End=%Day%*8640000+%Hour%*360000+%Minute%*6000+%Second%*100
set /a Diff=%End%-%Start%
set /a Diff=(%Diff%)/100
set /a DiffSec=%Diff%%%60
set /a Diff=(%Diff%-%Diff%%%60)/60
set /a DiffMin=%Diff%%%60
set /a Diff=(%Diff%-%Diff%%%60)/60
set /a DiffHrs=%Diff%

ECHO Laufzeit Auftraege loeschen: %DiffHrs%:%DiffMin%:%DiffSec%

:: format with leading zeroes
if %DiffSec% LSS 10 (ECHO "LESS 10")else %DiffSec% LSS 1 (ECHO "LESS 1")
::if %DiffSec% LSS 10 (set DiffSec=0%DiffSec%)else [%DiffSec%] LSS 1 (set DiffSec=00)
::if %DiffMin% LSS 10 (set DiffMin=0%DiffMin%)else [%DiffMin%] LSS 1 (set DiffMin=00)
::if %DiffHrs% LSS 10 (set DiffHrs=0%DiffHrs%)else [%DiffHrs%] LSS 1 (set DiffHrs=00)

ECHO Laufzeit Auftraege loeschen: %DiffHrs%:%DiffMin%:%DiffSec%
2个回答

9

1. 调试批处理文件

为了在代码中找到语法错误,建议在命令提示符窗口中运行批处理文件,并在每个echo off被修改为echo ON或从批处理文件中删除或使用REM命令进行注释后再运行。

默认情况下,Windows命令解释器会输出每个命令行或整个命令块,以(开头并以匹配的)结尾,在解析和预处理后,在其中引用了%variable%(即时扩展)的环境变量已经被当前值替换为执行命令行/块之前的环境变量的值。

在批处理文件的顶部使用@echo off可以关闭此默认行为,其中命令行开头的@也禁用了输出这个第一行命令行。当批处理文件的开发完成并且批处理文件正常工作时,这当然是可以的。但是对于调试不按预期工作的批处理文件,最好也看到命令解释器实际执行的命令行,以找出批处理文件执行在哪里因为错误而意外退出。

ECHO的行为在在命令提示符窗口中运行echo /?命令时非常简单地解释了一下。

打开命令提示符窗口将隐式启动cmd.exe,并使用选项/K来在批处理文件或应用程序完成执行后保持命令进程运行和控制台窗口打开。

异常情况是,如果批处理文件包含没有参数/Bexit命令,则当前命令进程始终会退出,并独立于调用层次结构。 exit /B等效于goto :EOF,应该使用它而不是只使用exit,除非有一个真正好的理由使用exitexit /Bgoto :EOF都需要默认启用的命令扩展,在Windows上默认情况下启用。

双击批处理文件会导致使用选项/C启动cmd.exe,以自动关闭命令进程及其控制台窗口,无论批处理文件终止的原因如何。这种自动关闭控制台窗口的行为对于调试批处理文件来说不好,因为当批处理文件执行由于语法错误而终止时,错误消息将无法看到。

有关Windows命令解释器选项的更多详细信息,请在命令提示符窗口中运行以下命令:cmd /?

如何使用 goto :EOF(冒号非常重要)或exit /B(仅是goto :EOF的内部别名)来有意退出批处理文件的执行可以在运行goto /?exit /?命令时显示的这两个命令的帮助中得到解释。
对于调试较大的批处理文件,使用在批处理文件顶部临时添加的goto跳转到某个块以及goto :EOF在调试后退出批处理很有帮助。
顺便说一下:::是批处理文件中经常用于注释的无效标签,因为标签行在批处理文件执行时永远不会被显示出来。但在FOR循环的命令块中无法使用标签,因为Windows命令解释器无法正确解释带有标签的FOR循环的命令块。因此,最好使用REM(注释)命令进行注释,因为该命令专门用于批处理文件中的注释,并且可以真正地在批处理文件的任何位置起作用。
另请参见如何调试.BAT脚本?

2.批处理文件中的错误

在从命令提示符窗口运行使用rem @echo off(在文本编辑器中进行替换)将@ECHO OFF注释掉的问题中发布的批处理文件时,可以很容易地看出出现错误的行数。
if %DiffSec% LSS 10 (ECHO "LESS 10")else %DiffSec% LSS 1 (ECHO "LESS 1")

如果环境变量DiffSec的当前值不低于10,则Windows命令解释器将执行ELSE分支,该分支以数字10开头。
Windows命令解释器无法在当前目录或环境变量PATH的分号分隔目录列表中指定的任何目录中找到具有环境变量PATHEXT中指定的分号分隔文件扩展名列表的应用程序。
错误显然在于缺少下一个比较的IF命令。因此,正确的代码应该是:
if %DiffSec% LSS 10 (ECHO "LESS 10") else if %DiffSec% LSS 1 ECHO "LESS 1"

如果将条件写在多行上,阅读起来会更容易:

if %DiffSec% LSS 10 (
    ECHO "LESS 10"
) else if %DiffSec% LSS 1 (
    ECHO "LESS 1"
)

语法现在是正确的。

但是,正如JosefZ在他的评论中已经提到的那样,第二个条件没有意义。如果DiffSec的值为10或更大,则会执行 ELSE 分支中的 IF 命令,那么这个条件绝对也永远不会成立。因此,更有意义的语句应该是:

if %DiffSec% LSS 1 (ECHO LESS 1) else if %DiffSec% LSS 10 ECHO LESS 10

或者,作为另一种选择。
if %DiffSec% LSS 1 (
    ECHO LESS 1
) else if %DiffSec% LSS 10 (
    ECHO LESS 10
)

关于批处理文件中有效的IF ELSE条件的更多信息,请参见以下答案:

3. 为小于10的数字添加前导零

环境变量始终是字符串类型。对于算术表达式,如果可能的话,环境变量的字符串值将转换为带符号的32位整数,算术表达式的结果将从带符号的32位整数转换回字符串。

此外,像if %DiffSec% LSS 10这样的IF条件在执行之前被展开,例如展开为if 5 LSS 10,将会将5(0x35)从字符串转换为整数,同时将10(0x31 0x30)也从字符串转换为整数,以便将这两个数字作为整数进行比较。

因此,如果可能的话,避免进行这样的数字比较会比较快一些。

通过使用字符串替换,可以很容易地向小于10的数字添加前导零,而不需要对值进行真正的测试。

首先,将环境变量的当前值添加一个(对于两位数)或多个0(对于3、4甚至更多位数)。

set "DiffSec=0%DiffSec%"

接下来,将像2这样的最后X个字符分配给当前环境变量的值,以便赋值给环境变量。

set "DiffSec=%DiffSec:~-2%"

要了解字符串替换的内容,请在命令提示符窗口中运行set /?命令,并查看输出的SET命令帮助。

在这两行代码的结果中,DiffSec始终是一个两位数的数字,范围从0099

4. 算术表达式的解析

在Windows命令解释器中,set /a后面的字符串被完全不同地解释为算术表达式。

空格和制表符是单词分隔符,但没有其他特殊意义。因此,建议使用空格使算术表达式更易读。

接下来有很多运算符,它们在运行set /?命令时在命令提示符窗口中显示在SET命令帮助中。

另外,十进制、八进制和十六进制整数在算术表达式中被解释为整数。

最后,任何其他字符串都被解释为环境变量的名称,其当前值从字符串转换为整数。

因此,在算术表达式中不建议使用立即或延迟扩展。

使用%variable%在命令块中引用环境变量的值不好,因为解析整个命令块之前,环境变量的当前值已经替换了变量引用。

使用!variable!在算术表达式中引用环境变量的值也不好,因为它需要启用延迟扩展,这将导致字符串中的感叹号不再作为文字字符处理。

因此,如果可能的话,最好始终只是在算术表达式中简单地编写变量名称,而不要使用百分号或感叹号将其括起来,因为变量名不包含空格字符,且以不能被Windows命令解释器解释为整数字符的字符开头。

有关如何仅使用setset /P(提示)或set /A(算术表达式)为环境变量赋值的详细信息,请参见在使用命令行上的“set var = text”后,为什么没有“echo %var%”输出字符串?的答案。

5. 修正和优化的代码

可以将问题中的代码修正和优化为以下代码:

@echo off
rem Time Calculation
for /F "skip=1 tokens=1-4" %%A in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_LocalTime GET Day^,Hour^,Minute^,Second') do (
    set Day=%%A
    set Hour=%%B
    set Minute=%%C
    set Second=%%D
)
set /A TimeStart=Day * 86400 + Hour * 3600 + Minute *60 + Second

@echo on
%SystemRoot%\System32\ping.exe 8.8.8.8 -n 11
@echo off

for /F "skip=1 tokens=1-4" %%A in ('%SystemRoot%\System32\wbem\wmic.exe PATH Win32_LocalTime GET Day^,Hour^,Minute^,Second') do (
    set Day=%%A
    set Hour=%%B
    set Minute=%%C
    set Second=%%D
)
set /A TimeEnd=Day * 86400 + Hour * 3600 + Minute *60 + Second

set /A TimeDiff=TimeEnd - TimeStart
set /A DiffSec=TimeDiff %% 60
set /A TimeDiff=(TimeDiff - DiffSec) / 60
set /A DiffMin= TimeDiff %% 60
set /A DiffHrs=(TimeDiff - DiffMin) / 60

set "DiffSec=0%DiffSec%"
set "DiffSec=%DiffSec:~-2%"
set "DiffMin=0%DiffMin%"
set "DiffMin=%DiffMin:~-2%"
set "DiffHrs=0%DiffHrs%"
set "DiffHrs=%DiffHrs:~-2%"

echo Time needed for orders deletion: %DiffHrs%:%DiffMin%:%DiffSec%

为了理解使用的命令及其工作原理,请打开一个命令提示符窗口,执行以下命令,并仔细阅读每个命令显示的所有帮助页面。
  • echo /? 显示消息或启用/禁用回显。
  • for /? 在一组文件、字符串或文本中循环执行命令。
  • ping /? 检查与另一台计算机的连接。
  • rem /? 在脚本文件中添加注释(不会被执行)。
  • set /? 显示、设置或删除环境变量。
  • wmic /? 执行Windows管理规范(WMI)查询。
  • wmic path /? 显示指定路径中的WMI类。

刚回来,因为这篇帖子已经有1000次浏览了,发现除了接受你详细解释一切并优化代码的精彩答案之外,我还没有感谢你Mofi。 - LostKatana
作为“1.调试批处理文件”的补充,请参考以下问题:如何调试 .BAT 脚本? - aschipfl

1
if %DiffSec% LSS 10 (ECHO "LESS 10")else IF %DiffSec% LSS 1 (ECHO "LESS 1")

您需要在else后面加上if

3
如果 %DiffSec% 小于 1 (ECHO "LESS 1"), 否则如果 %DiffSec% 小于 10 (ECHO "LESS 10")。否则,else 对于任何小于 1 的内容都不会生效。 - JosefZ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接