使用“cmd”和“start”命令启动应用程序后如何获取退出代码

12

我有一个控制台应用程序,通过TCP/IP与其交互。

我还有一个测试框架,基本上是一组批处理脚本(...不是我的错)。该测试框架对于每个测试所做的事情基本上是:

  1. start /min "myapplication.exe"并等待收到验证消息以确认应用程序已启动并运行。
  2. 通过TCP/IP发送命令到此应用程序,接收其回复,并检查时间和值是否与特定测试所期望的相符。

我目前遇到的一个问题是,由于某些内部错误,应用程序会过早地退出。我想区分失败的测试和应用程序崩溃的情况。我唯一的指示是应用程序的退出代码。

所以,我尝试了以下操作:

start /min cmd /c "myapplication.exe || echo %errorLevel% > exitcode.txt"

然后在测试脚本中稍后执行:

if exist exitcode.txt (
    set /p exitcode=<exitcode.txt
    echo ERROR: myapplication.exe returned exitcode %exitcode%.
    goto error
) else (
    goto do_processing
)

但由于某种奇怪的原因,文本文件从未出现,即使有时我会收到有关应用程序崩溃的对话框,即使我强制它失败并返回已知的非零退出代码。测试只是通过do_processing,结果当然会失败。

编辑 当我运行

start /min cmd /c "nonsense || echo %errorLevel% > test.txt"

我有时会得到一个包含字符串9009的文本文件,但其他时间该文本文件包含字符串0,或者有时是1,...这是什么情况?!

编辑2 如果您键入

cmd /k "nonsense || echo %errorLevel%"

(注意/k选项),您将在新窗口中看到打印出0,但如果您随后键入echo %errorlevel%,则会得到1...

我知道批处理不太健全,但它至少应该是一致地疯狂...

对此有什么想法吗?


你需要使用/wait选项来获取退出代码。这本应该打败使用start的初衷。当需要进行错误检查时,请避免使用fire-and-forget。 - Hans Passant
@Hans Passant:我认为这样做不会起作用,因为如果你使用start,退出代码将不会传递给调用脚本。你应该使用call代替。它的行为类似于start /wait但可以传递变量。请参考此链接:https://dev59.com/ZGYr5IYBdhLWcg3w8-vA - MichaelS
@HansPassant:但是start /wait意味着脚本执行将会阻塞。因此,脚本无法继续对其发出命令... - Rody Oldenhuis
@MichaelS 我调用了start(以便将任务放到后台)与cmd(以便能够发出2个命令;可执行文件,加上输出到文件的echo)。 - Rody Oldenhuis
3个回答

18

正常的扩展方式(例如%errorLevel%)发生在语句解析时,而整个CMD /C命令行在一次解析中被解析,因此您得到的值是命令运行之前存在的值(始终为0)。

您可以在https://dev59.com/6G855IYBdhLWcg3w5Igb#4095133上获得更精确的解释。起初可能很难理解和理解各个阶段的意义,但这是值得努力的。

要解决问题,您必须延迟变量的扩展,直到exe运行后。

您有两个选项:

选项1)使用CALL获取延迟扩展的回合。

在批处理文件中,您需要将百分号加倍。但是,使用CMD /C运行的命令是在命令行上下文中运行的,而不是批处理。在命令行下,加倍的百分号不起作用。

相反,您必须将插入^符号(cmd.exe转义字符)到变量名中。扩展的第一阶段在转义处理之前进行,因此它寻找带有^符号的名称,并且找不到它。如果未找到,则当未找到变量时,命令行解析器会保留原始文本。接下来处理特殊字符并使用转义符。因此,在CALL扩展回合发生时,它看到正确的变量名。

start /min cmd /c "myapplication.exe || call echo %^errorLevel% > exitcode.txt"

我相信您正在批处理脚本中发布START命令,因此您还必须加倍百分号以防止父批处理脚本扩展ERRORLEVEL。

start /min cmd /c "myapplication.exe || call echo %%^errorLevel%% > exitcode.txt"

选项2)使用延迟扩展

延迟扩展语法是!errorlevel!而不是%errorlevel%。但在使用之前,必须启用延迟扩展。在批处理脚本中,您将使用setlocal enableDelayedExpansion,但这在命令行上下文中无效。相反,您必须使用cmd.exe的/v:on选项。

假设您的批处理脚本未启用延迟扩展,则只需使用以下内容:

start /min cmd /v:on /c "myapplication.exe || echo !errorLevel! > exitcode.txt"

但是,如果你的批处理脚本启用了延迟环境变量扩展功能,那么你必须转义!,以防止父批处理脚本扩展ERRORLEVEL。请注意,你仍然必须使用/v:on,因为STARTed子过程(通常)默认禁用延迟环境变量扩展。

start /min cmd /v:on /c "myapplication.exe || echo ^!errorLevel^! > exitcode.txt"

啊啊啊啊!我不敢相信我又上当了!!这个解决方案很好,非常感谢。唉,想想这给那么多人带来的头疼…… - Rody Oldenhuis
只是出于好奇,callcmd /v:on 有什么优缺点吗? - Rody Oldenhuis
1
在这种情况下,其实并不是。CALL 是相对较慢的,但只有在紧密循环内进行多次迭代时才会发挥作用 - 这里没有问题。另外,CALL 会对传递引号抑制符的引号造成破坏(将它们加倍),同样不是问题。延迟扩展会破坏包含 ! 字符的 FOR 变量的扩展,但也不是问题。 - dbenham
仍有一些问题没有得到解决...当条件发生(应用程序崩溃)时,文本文件会按照你提供的两个选项都被写入。然而,使用选项1)它包含"ECHO is on.",而使用选项2)它包含"0"......不确定是否重要,但是start是从一个批处理文件中的if (...)调用的,该批处理文件是从另一个批处理文件中called的,该文件具有setlocal EnableDelayedExpansion - Rody Oldenhuis

4

此处所解释,为了能够评估退出代码,您必须使用call而不是startcall将在相同的变量环境中启动脚本,而start将在新的环境中运行,该环境无法从第一个脚本中访问。


你可以使用 @echo %errorlevel% >> output.txt 来编写退出代码。这也是为什么你不能使用 start 的原因。start 不会在调用脚本中设置可访问的 %errorlevel%。使用 start 可以让你在后台执行测试,但你永远无法访问退出代码。 - MichaelS
哦,抱歉,我误解了你。 - MichaelS
这就是为什么我使用 start /min cmd /c "myapplication.exe || echo %errorLevel% > exitcode.txt"Start 启动一个带有自己变量环境的 cmd,运行应用程序并(有条件地)将任何非零退出代码输出到文件中。 - Rody Oldenhuis
你试过使用start /min cmd /c "myapplication.exe & IF NOT %errorlevel%==0 echo %errorLevel% > exitcode.txt"这个方法吗? - MichaelS
我会尝试,虽然我不知道这有什么不同...如果if not %errorlevel%==0||运算符的本质相同,那么它们不应该是等价的吗? - Rody Oldenhuis
显示剩余4条评论

1
另一个解决方案可能是使用&代替||
start myapplication.exe ^& echo %errorLevel% ^> exitcode.txt

"^是转义字符,这样它就可以在起始位置内被解析,而不是在外部被解析,详情请参见此处。这对我很有效,希望对别人也有所帮助。"

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