gpt4 book ai didi

windows - 批量转到丢失错误级别

转载 作者:行者123 更新时间:2023-12-01 10:26:49 28 4
gpt4 key购买 nike

考虑以下 bat,test.bat(PC01 已关闭):

mkdir \\PC01\\c$\Test || goto :eof

如果我从命令 shell 运行那个 bat:
> test.bat || echo 99
> if ERRORLEVEL 1 echo 55

输出只是 55。没有 99。有一个错误级别,但是 || 运算符(operator)没有看到它。

如果我使用 cmd /c - 运行那个 bat
> cmd /c test.bat || echo 99
> if ERRORLEVEL 1 echo 55

输出为空白。错误级别为 0。

如果我删除 || goto :eof ,一切都会像人们预测的那样工作——即输出将是

99 55

有谁知道为什么会发生这种半生不熟的半存在的 ERRORLEVEL 行为?

最佳答案

在大多数情况下, || is the most reliable way to detect an error 。但是您偶然发现了 ERRORLEVEL 有效但 || 无效的罕见情况之一。

问题源于您的错误是在批处理脚本中引发的,并且 || 响应了最近执行的命令的返回码。您将 test.bat 视为单个“命令”,但实际上它是一系列命令。脚本中执行的最后一个命令是 GOTO :EOF ,并且成功执行。所以你的 test.bat||echo 99 正在响应 GOTO :EOF 的成功。

当您从脚本中删除 ||GOTO :EOF 时,您的 test.bat||echo99 会看到失败的 mkdir 的结果。但是如果你在 test.bat 的末尾添加 REM 命令,那么 test.bat||echo 99 会响应 REM 的成功,并且错误会再次被屏蔽。

ERRORLEVEL 在 test.bat||echo 99 之后仍然是非零的,因为像 GOTOREM 这样的命令在成功后不会清除任何先前的非零 ERRORLEVEL。这是 ERRORLEVEL 和返回码并非完全相同的众多证据之一。它肯定会让人感到困惑。

您可以将 test.bat 视为一个单元命令,并通过使用 CALL 获得您想要的行为。

C:\test>call test.bat && echo OK || echo FAIL
FAIL

C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2

这是有效的,因为 CALL 命令暂时将控制转移到被调用的脚本。当脚本终止时,控制返回到 CALL 命令,并返回当前的 ERRORLEVEL。所以 ||echo 99 正在响应 CALL 命令本身返回的错误,而不是脚本中的最后一个命令。

现在是 CMD /C 问题。
CMD /C返回的返回码是最后执行的命令的返回码。

这有效:
C:\test>cmd /c call test.bat && echo OK || echo FAIL
FAIL

C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2

因为 CMD /C返回的是 CALL语句返回的ERRORLEVEL

但这完全失败了:
C:\test>cmd /c test.bat && echo OK || echo FAIL
OK

C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
OK2

没有 CALLCMD /C 返回最后执行的命令的返回码,即 GOTO :EOFCMD /C 还将 ERRORLEVEL 设置为相同的返回代码,因此现在没有证据表明脚本中曾经存在错误。

沿着兔子洞往下走

R.L.H. 在他的回答和对我的回答的评论中,担心 || 有时会清除 ERRORLEVEL。他提供了似乎支持他的结论的证据。但情况并没有那么简单,事实证明 || 是检测错误的最可靠(但仍不完美)的方法。

如前所述,所有外部命令在退出时返回的返回码与 cmd.exe ERRORLEVEL 不同。

ERRORLEVEL 是在 cmd.exe session 本身中维护的一种状态,与返回代码完全不同。

这甚至记录在 EXIT 帮助中的 exitCode 定义中
( help exitexit /? )
EXIT [/B] [exitCode]

/B specifies to exit the current batch script instead of
CMD.EXE. If executed from outside a batch script, it
will quit CMD.EXE

exitCode specifies a numeric number. if /B is specified, sets
ERRORLEVEL that number. If quitting CMD.EXE, sets the process
exit code with that number.

当 CMD.EXE 运行外部命令时,它会检测可执行文件的返回代码并将 ERRORLEVEL 设置为匹配。请注意,0 表示成功,非零表示错误只是一个约定。某些外部命令可能不遵循该约定。例如, HELP 命令 (help.exe) 不遵循约定 - 如果您指定无效命令(如 help bogus ),则返回 0 ,但如果您请求有效命令的帮助(如 help rem ),则返回 1 。

执行外部命令时, || 运算符从不清除 ERRORLEVEL。检测到进程退出代码并在它非零时触发 ||,并且 ERRORLEVEL 仍将与退出代码匹配。话虽如此,出现在 && 和/或 || 之后的命令可能会修改 ERRORLEVEL,因此必须小心。

但是除了外部命令之外,还有许多其他情况我们开发人员关心成功/失败和返回代码/ERRORLEVEL。
  • 内部命令的执行
  • 重定向运算符 <>>>
  • 批处理脚本的执行
  • 无效命令执行失败

  • 不幸的是,CMD.EXE 在处理这些情况下的错误情况方面完全不一致。 CMD.EXE 有多个内部点,它必须检测错误,大概是通过某种形式的内部返回代码,不一定是 ERRORLEVEL,并且在这些点中的每一个点 CMD.EXE 都可以根据它发现的内容设置 ERRORLEVEL .

    对于我下面的测试用例,请注意 (call ) 带有空格,是一种神秘的语法,可以在每次测试之前将 ERRORLEVEL 清除为 0。稍后,我还将使用 (call) ,不带空格,将 ERRORLEVEL 设置为 1

    另请注意,已通过使用在我的命令 session 中启用了延迟扩展 cmd /v: on 在运行我的测试之前

    绝大多数内部命令在失败时将 ERRORLEVEL 设置为非零值,并且错误条件也会触发 || 。在这些情况下, || 从不清除或修改 ERRORLEVEL。
    这里有几个例子:
    C:\test>(call ) & set /a 1/0
    Divide by zero error.

    C:\test>echo !errorlevel!
    1073750993

    C:\test>(call ) & type notExists
    The system cannot find the file specified.

    C:\test>echo !errorlevel!
    1

    C:\test>(call ) & set /a 1/0 && echo OK || echo ERROR !errorlevel!
    Divide by zero error.
    ERROR 1073750993

    C:\test>(call ) & type notExists.txt && echo OK || echo ERROR !errorlevel!
    The system cannot find the file specified.
    ERROR 1

    然后至少有一个命令 RD(可能更多),以及在出错时触发 || 的重定向运算符,但除非使用 ||,否则不要设置 ERRORLEVEL。
    C:\test>(call ) & rd notExists
    The system cannot find the file specified.

    C:\test>echo !errorlevel!
    0

    C:\test>(call ) & echo x >\badPath\out.txt
    The system cannot find the path specified.

    C:\test>echo !errorlevel!
    0

    C:\test>(call ) & rd notExists && echo OK || echo ERROR !errorlevel!
    The system cannot find the file specified.
    ERROR 2

    C:\test>(call ) & echo x >\badPath\out.txt && echo OK || echo ERROR !errorlevel!
    The system cannot find the path specified.
    ERROR 1

    有关更多信息,请参阅 "rd" exits with errorlevel set to 0 on error when deletion fails, etcFile redirection in Windows and %errorlevel%

    我知道一个内部命令(可能还有其他命令)加上可以向 stderr 发出错误消息的基本失败 I/O 操作,但它们不会触发 || 也不会设置非零的 ERRORLEVEL。

    如果文件是只读的或不存在,则 DEL 命令可以打印错误,但它不会触发 || 或将 ERRORLEVEL 设置为非零
    C:\test>(call ) & del readOnlyFile
    C:\test\readOnlyFile
    Access is denied.

    C:\test>echo !errorlevel!
    0

    C:\test>(call ) & del readOnlyFile & echo OK || echo ERROR !errorlevel!
    C:\test\readOnlyFile
    Access is denied.
    OK

    有关 DEL 错误的更多信息,请参阅 https://stackoverflow.com/a/32068760/1012053

    以同样的方式,当 stdout 已成功重定向到 USB 设备上的文件,但在 ECHO 等命令尝试写入设备之前删除了设备,则 ECHO 将失败并向 stderr 发送错误消息,但 || 不会触发,并且 ERRORLEVEL 未设置为非零。有关更多信息,请参阅 http://www.dostips.com/forum/viewtopic.php?f=3&t=6881

    然后我们有执行批处理脚本的情况 - OP 问题的实际主题。如果没有 CALL|| 操作符会响应脚本中执行的最后一个命令。使用 CALL|| 运算符响应 CALL 命令返回的值,这是批处理终止时存在的最终 ERRORLEVEL。

    最后,我们有 R.L.H.报告,其中无效命令通常报告为 ERRORLEVEL 9009,但如果使用 ||,则报告为 ERRORLEVEL 1。
    C:\test>(call ) & InvalidCommand
    'InvalidCommand' is not recognized as an internal or external command,
    operable program or batch file.

    C:\test>echo !errorlevel!
    9009

    C:\test>(call ) & InvalidCommand && echo OK || echo ERROR !errorlevel!
    'InvalidCommand' is not recognized as an internal or external command,
    operable program or batch file.
    ERROR 1

    我无法证明这一点,但我怀疑命令失败的检测和将 ERRORLEVEL 设置为 9009 发生在命令执行过程中的很晚。我猜 || 在设置 9009 之前拦截了错误检测,此时它将它设置为 1。所以我不认为 || 正在清除 9009 错误,而是它是处理和设置错误的替代途径。

    这种行为的另一种机制是,无效命令可以始终将 ERRORLEVEL 设置为 9009,但返回码不同,为 1。 || 随后可以检测到 1 返回码并将 ERRORLEVEL 设置为匹配,从而覆盖 9009。

    无论如何,我不知道任何其他情况,其中非零 ERRORLEVEL 结果因是否使用 || 而异。

    这样就可以处理命令失败时发生的情况。但是当内部命令成功时呢?不幸的是,CMD.EXE 的一致性甚至比出错时还要差。它因命令而异,还可能取决于它是从命令提示符执行、从扩展名为 .bat 的批处理脚本还是从扩展名为 .cmd 的批处理脚本执行。

    我将下面的所有讨论都基于 Windows 10 行为。我怀疑与使用 cmd.exe 的早期 Windows 版本存在差异,但这是可能的。

    无论上下文如何,以下命令总是在成功时将 ERRORLEVEL 清除为 0:
  • CALL :如果 CALLed 命令没有设置它,则清除 ERRORLEVEL。
    示例:call echo OK
  • CD
  • CHDIR
  • 颜色
  • 复制
  • 日期
  • DEL:始终清除 ERRORLEVEL,即使 DEL 失败
  • 目录
  • ERASE :始终清除 ERRORLEVEL,即使 ERASE 失败
  • MD
  • MKDIR
  • MKLINK
  • 移动
  • PUSHD
  • 重命名
  • SETLOCAL
  • 时间
  • 类型
  • 版本
  • 验证
  • 音量

  • 下一组命令永远不会在成功时将 ERRORLEVEL 清除为 0,无论上下文如何,而是保留任何现有的非零值 ERRORLEVEL:
  • 中断
  • CLS
  • 回声
  • 本地
  • EXIT :显然 EXIT /B 0 清除 ERRORLEVEL,但没有值的 EXIT /B 保留先前的 ERRORLEVEL。
  • 对于
  • 转到
  • 如果
  • key
  • 暂停
  • POPD
  • RD
  • REM
  • RMDIR
  • 移位
  • 开始
  • 标题

  • 然后,如果从命令行或扩展名为 .bat 的脚本发出,这些命令在成功时不会清除 ERRORLEVEL,但如果从扩展名为 .cmd 的脚本发出,则将 ERRORLEVEL 清除为 0。有关更多信息,请参阅 https://stackoverflow.com/a/148991/1012053https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J
  • ASSOC
  • DPATH
  • FTYPE
  • 路径
  • 提示
  • 设置

  • 无论任何 ERRORLEVEL 值如何, && 运算符都会检测前一个命令是否成功,如果成功则仅执行后续命令。 && 运算符忽略 ERRORLEVEL 的值,并且从不修改它。

    这里有两个例子表明如果先前的命令成功, && 总是会触发,即使 ERRORLEVEL 不为零。 CD 命令是该命令清除任何先前的 ERRORLEVEL 的示例,而 ECHO 命令是该命令不清除先前的 ERRORLEVEL 的示例。请注意,在发出成功的命令之前,我使用 (call) 将 ERRORLEVEL 强制为 1。
    C:\TEST>(call)

    C:\TEST>echo !errorlevel!
    1

    C:\test>(call) & cd \test

    C:\test>echo !errorlevel!
    0

    C:\test>(call) & cd \test && echo OK !errorlevel! || echo ERROR !errorlevel!
    OK 0

    C:\test>(call) & echo Successful command
    Successful command

    C:\test>echo !errorlevel!
    1

    C:\test>(call) & echo Successful command && echo OK !errorlevel! || echo ERROR !errorlevel!
    Successful command
    OK 1

    在我用于错误检测的所有代码示例中,我依赖于 ECHO 从不清除先前存在的非零 ERRORLEVEL 的事实。但是下面的脚本是在 &&|| 之后使用其他命令时可能发生的情况的示例。
    @echo off
    setlocal enableDelayedExpansion
    (call)
    echo ERRORLEVEL = !errorlevel!
    (call) && echo OK !errorlevel! || echo ERROR !errorlevel!
    (call) && (echo OK !errorlevel! & set "err=0") || (echo ERROR !errorlevel! & set "err=1" & echo ERROR !errorlevel!)
    echo ERRORLEVEL = !errorlevel!
    echo ERR = !ERR!

    这是脚本具有 .bat 扩展名时的输出:
    C:\test>test.bat
    ERRORLEVEL = 1
    ERROR 1
    ERROR 1
    ERROR 1
    ERRORLEVEL = 1
    ERR = 1

    这是脚本具有 .cmd 扩展名时的输出:
    C:\test>test.cmd
    ERRORLEVEL = 1
    ERROR 1
    ERROR 1
    ERROR 0
    ERRORLEVEL = 0
    ERR = 1

    请记住,每个执行的命令都有可能改变 ERRORLEVEL。因此,即使 &&|| 是检测命令成功或失败的最可靠方法,但如果您关心 ERRORLEVEL 值,则必须注意在这些运算符之后使用哪些命令。

    现在是时候爬出这个臭兔子​​洞呼吸新鲜空气了!

    所以我们学了什么?

    没有一种完美的方法可以检测任意命令是成功还是失败。但是, &&|| 是检测成功和失败的最可靠方法。

    一般情况下, &&||都不直接修改ERRORLEVEL。但也有一些罕见的异常(exception)。
  • || 正确设置 ERRORLEVEL,否则当 RD 或重定向失败
  • || 在无效命令执行失败时设置不同的 ERRORLEVEL,如果未使用 ||(1 对 9009),则会发生。

  • 最后,除非使用了 CALL 命令,否则 || 不会检测由批处理脚本返回的非零 ERRORLEVEL 作为错误。

    如果您严格依赖 if errorlevel 1 ...if %errorlevel% neq 0 ... 来检测错误,那么您将面临错过 RD 和重定向(以及其他?)可能引发的错误的风险,并且您还冒着错误地认为某些内部命令失败的风险,而实际上它可能会失败是先前失败的命令的保留。

    关于windows - 批量转到丢失错误级别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34936240/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com