【问题标题】:Command script exit code not seen by same line && or ||?同一行 && 或 || 看不到命令​​脚本退出代码?
【发布时间】:2015-09-19 08:17:25
【问题描述】:

考虑一个名为 t.cmd 的命令脚本,它仅包含以下两行:

@exit /b 123
@echo If you see this, THEN EXIT FAILED..

所以,脚本只是将脚本执行过程的退出码设置为123,并没有杀死cmd.exe。最后的 echo 确认 exit 实际上会导致立即返回(其输出不应出现)。

现在执行这个脚本,然后打印出%errorlevel%:

>t.cmd

>echo %errorlevel%
123

到目前为止一切顺利:一切都完全符合预期。

但是现在在一行上执行以上所有内容,使用 && 进行条件执行:

>t.cmd && echo %errorlevel%
123

我不希望这样:如果 t.cmd 真的返回一个非 0 退出代码,那么它应该停止 && 之后的所有内容(即回显)执行。我们看到它打印的事实意味着它确实执行了。到底是怎么回事?

如果在一行上执行以上所有操作,使用 ||对于条件执行:

>t.cmd || echo %errorlevel%

>

这种行为也与我的预期相反(尽管它与上面的 && 行为一致)。

请注意,这种奇怪的行为仅适用于 bat 文件,但不适用于“原始命令”。

证明:考虑以下命令行交互,我尝试执行虚假命令 abcdef,而不是调用 t.cmd:

>abcdef
'abcdef' is not recognized as an internal or external command,
operable program or batch file.

>echo %errorlevel%
9009

>abcdef && echo %errorlevel%
'abcdef' is not recognized as an internal or external command,
operable program or batch file.

>abcdef || echo %errorlevel%
'abcdef' is not recognized as an internal or external command,
operable program or batch file.
9009

这里,&& 和 ||立即查看失败的虚假命令的退出代码。

那么为什么 cmd 文件的行为会有所不同呢?

File redirection in Windows and %errorlevel%中发现了一个可能与cmd.exe相关的错误

另外,我知道ERRORLEVEL is not %ERRORLEVEL%

顺便说一句,上面的代码都是在 Win 7 Pro 64 位机器上执行的。我不知道其他版本的 Windows 行为如何。

【问题讨论】:

  • 只是说,批量只需要一个&|
  • @Monacraft - &&& 是完全不同的东西,||| 也是如此。

标签: windows batch-file cmd


【解决方案1】:

%errorlevel% 在读取行时扩展。所以在读取该行时它是 123,所以来自上一个命令而不是 t.exe。 && 仅在当前错误级别(来自 t 命令)为 0 时执行。

请参阅setlocal /?set /? 了解更多信息。

【讨论】:

    【解决方案2】:

    t.bat稍作修改如下:

    @exit /b 123%~1
    @echo If you see this, THEN EXIT FAILED..
    

    想想下一个输出:

    ==>t.bat 1
    
    ==>echo %errorlevel%
    1231
    
    ==>t.bat 2&echo %errorlevel%
    1231
    
    ==>echo %errorlevel%
    1232
    
    ==>cmd /V /C t.bat 3^&echo !errorlevel!
    1233
    
    ==>echo %errorlevel%
    0
    
    ==>cmd /V /C t.bat 4^&echo !errorlevel!^&exit /B !errorlevel!
    1234
    
    ==>echo %errorlevel%
    1234
    
    ==>
    

    资源

    编辑启发EnableDelayedExpansion

    ==>cmd /v
    Microsoft Windows [Version 6.3.9600]
    (c) 2013 Microsoft Corporation. All rights reserved.
    
    ==>t.bat 5&echo !errorlevel!
    1235
    
    ==>echo %errorlevel%
    1235
    
    ==>
    

    编辑 2 启发(或混淆?)&&||。将下一个代码 sn-p 保存为errlevels.cmd:

    @ECHO ON >NUL
    @SETLOCAL enableextensions enabledelayedexpansion
    (call )
    @echo ^(call ^) command clears errorlevel %errorlevel%
    abcd /G>NUL 2>&1
    @echo abcd /G: "'abcd' not recognized" errorlevel %errorlevel%
    abcd /G>NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
    @echo abcd /G: ^|^|   changed errorlevel %errorlevel%
    find /G >NUL 2>&1 && echo YES !errorlevel! || echo NO !errorlevel!
    @echo find /G: ^|^| unchanged errorlevel %errorlevel%
    call t.cmd 333 && echo YES !errorlevel! || echo NO !errorlevel!
    type t.cmd
    t.cmd 222 && echo YES !errorlevel! || echo NO !errorlevel!
    

    输出(来自errlevels.cmd):

    ==>errlevels.cmd
    
    ==>(call  )
    (call ) command clears errorlevel 0
    
    ==>abcd /G 1>NUL 2>&1
    abcd /G: "'abcd' not recognized" errorlevel 9009
    
    ==>abcd /G  1>NUL 2>&1  && echo YES !errorlevel!   || echo NO !errorlevel!
    NO 1
    abcd /G: ||   changed errorlevel 1
    
    ==>find /G   1>NUL 2>&1  && echo YES !errorlevel!   || echo NO !errorlevel!
    NO 2
    find /G: || unchanged errorlevel 2
    
    ==>call t.cmd 333   && echo YES !errorlevel!   || echo NO !errorlevel!
    NO 333
    
    ==>type t.cmd
    @exit /B %~1
    
    ==>t.cmd 222   && echo YES !errorlevel!   || echo NO !errorlevel!
    YES 222
    
    ==>
    

    注意

    • || 显示错误级别 1,尽管 'abcd' not recognized 错误应该是 9009
    • 对于FIND: Invalid switch 错误,|| 保持错误级别 2 不变。
    • || 分支在 call t.cmd 333 同时进行评估
    • && 分支在 t.cmd 222 中评估。

    (call )DBenham's answer

    如果你想强制errorlevel0,那么你可以使用这个 完全不直观,但非常有效的语法:(call )。空间 在call 之后至关重要。

    如果要将errorlevel 设置为1,可以使用(call)。它 在call 之后没有任何空格是至关重要的。

    【讨论】:

    • JosefZ:这是一个了不起的答案!
    • JosefZ:我正在再看你的回复,我有点困惑。在您的 Edit2 之前,我完全理解您;延迟扩展(或我没有扩展)解释了一切。我不明白的是“注意”之后列表中的第一个和第四个结果。对于第一个,您对为什么 9009 错误级别显示为 1 有任何解释吗?您的第三个结果正是我所期望的,但是为什么在您的第四个结果中 && 评估?我认为 t.cmd 中除 0 以外的任何错误级别都应始终导致 ||执行?
    • JosefZ:我打开了一个新的 DOS shell,在 Edit1 之前我可以准确地重现你的所有结果。但是您的 Edit2 结果更成问题。我首先打开另一个新的 DOS shell。我发现在执行您的任何命令之前我必须执行“cmd /v”,否则!错误级别!无法解决。那是因为您的第二个命令(@SETLOCAL)只应该在 .cmd 文件中执行吗?实际上,输出开头的“errlevels.cmd”是什么?是您希望放入所有这些命令的文件的名称吗?
    • [上一条评论的继续...] 继续进行纯命令行交互,当我执行“find /G”行时,我看到 1 而不是 2。这适用于下一个“@echo find /G" 行也是如此。我尝试将所有命令放入一个名为 errlevels.cmd 的新文件中并执行该文件。它还为两个“查找/G”行打印 1 而不是 2。奇怪的是,对于最终命令,它会打印“YES !errorlevel!”。 DOS真是太疯狂了……
    【解决方案3】:

    @JosefZ 的响应很好地涵盖了ERRORLEVEL 和退出代码之间关于命令脚本的差异。

    不幸的是,正如他所指出的,&&|| 运算符仅在您使用 call 命令调用命令脚本时才有效。在大多数情况下,您希望用户可以只运行您的命令脚本,而不必记住每次都为其加上 call 前缀。

    通常,我希望我的脚本同时设置ERRORLEVEL 和退出代码以指示失败(以使我的脚本的行为与常规可执行文件相同)。我曾经使用exit /b <nonzero> 尝试这样做,但是遇到了上面指出的问题。

    事实证明,Windows 命令解释器以最后执行的命令的退出代码退出。具有讽刺意味的是,exit /b <nonzero> 命令的实际退出代码是 0(因为它已成功退出)。它确实设置了ERRORLEVEL,但没有设置退出代码。因此,该命令将不起作用。所有这一切的解决方案是 (1) 使用 cmd /c exit <nonzero> 命令,以及 (2) 让它作为脚本中的最后一个命令执行。由于cmd 命令返回到脚本,然后执行下一个命令,因此使其成为执行的最后一行的唯一方法是让它成为脚本的最后一行。

    因此,这里有一个解决方案,可以让一切都按照 OP 的要求运行:

    @echo off & setlocal
    
    if "%1" equ "fail" (
        echo -- Failure
        goto fail
    ) else (
        echo -- Success
        exit /b 0
    )
    
    :fail
        @REM // Exit with script a failure exit code.
        cmd /c exit 37
    

    【讨论】:

    • 感谢您实际回答问题。 ERRORLEVEL 和退出代码不同(但相关)的想法没有有据可查(我包括 JosefZ 的回答,我相信 not提供出色的覆盖范围)。 Butsee dbenham’s answer here.
    • 最好edit你的答案。有些人不看cmets;更糟糕的是,cmets 有时会被删除。
    猜你喜欢
    • 2013-08-26
    • 2017-03-04
    • 1970-01-01
    • 2017-10-31
    • 1970-01-01
    • 1970-01-01
    • 2014-06-14
    • 1970-01-01
    • 2023-04-10
    相关资源
    最近更新 更多