Windows命令解释器:如何获取第一个管道命令的退出代码

18
在下面提供的示例中,我执行nmake,然后将STDOUT/STDERR重定向到tee,它会将其发送到屏幕和日志文件。 问题是我试图捕获nmake的退出代码而不是tee的。 我需要的是来自nmake而不是tee的退出代码。
nmake | tee output.txt
3个回答

30

你可能认为可以做以下操作,但它不起作用。

(nmake & call set myError=%%errorlevel%%) | tee output.txt

问题在于Windows管道的工作机制。每个管道端在它自己的CMD shell中执行。所以你在那里设置的任何环境变量都会在命令结束后消失。此外,由于额外的解析级别和CMD shell具有命令行上下文而不是批处理上下文,%errorlevel%的延迟展开更加复杂。

你可以采取以下措施:

(nmake & call echo %%^^errorlevel%% ^>myError.txt) | tee output.txt
for /f %%A in (myError.txt) do echo nmake returned %%A
del myError.txt

或者你可以将errorlevel嵌入到你的output.txt中:

(nmake & call echo nmakeReturnCode: %%^^errorlevel%%) | tee output.txt
for /f "tokens=2" %%A in ('findstr /b "nmakeReturnCode:" output.txt') do echo nmake returned %%A

以上两种解决方案都假设您正在批处理脚本中运行命令。如果您要从命令行执行命令,则两个解决方案都需要使用 %^^errorlevel% 而不是 %%^^errorlevel%%

但是,考虑到nmake不需要用户输入,并且通常速度很快,因此实时监控可能不是问题,那么最简单的解决方案似乎是

nmake >output.txt
set myError=%errorlevel%
type output.txt
echo nmake returned %myError%


注意 - 在使用Windows管道时存在许多微妙的复杂性。一个很好的参考是为什么在代码块内部使用延迟扩展失败?。我建议阅读问题和所有答案。被选中的答案拥有最佳信息,但其他答案可以帮助提供背景。

编辑 2015-06-02

我最近发现你可以使用DOSKEY宏来干净地存储和检索一个(或两个)管道的ERRORLEVEL,而不必诉诸于临时文件。我的想法来自DosTips用户Ed Dyreen在http://www.dostips.com/forum/viewtopic.php?p=41409#p41409上。DOSKEY宏无法通过批处理执行,但定义会在ENDLOCAL 和 CMD /C exit之后保留!

以下是您在该情况下如何使用它:

(nmake & call doskey /exename=err err=%%^^errorlevel%%) | tee output.txt
for /f "tokens=2 delims==" %%A in ('doskey /m:err') do echo nmake returned %%A
如果需要的话,你可以在最后添加一个命令来清除 err "宏"的定义,以便在获取值后进行清理。
doskey /exename=err err=

简单的解决方案打败了使用tee(同时输出)的主要原因。另外,对于第一个解决方案,您可以使用“set /p MY_ERROR= < myError.txt”。 - Sam Mackrill
call setcall echo等等的意思是什么?我之前从未见过这种语法。 - alecov
2
@Alek - 这是一个旧的技巧,用于在初始解析后获得第二轮变量扩展。这是使用延迟扩展的替代方法。echo !var!类似于call echo %%var%%。通常更喜欢使用延迟扩展,但有时需要使用CALL技巧。 - dbenham
1
如果任何人想尝试第一个“或者您可以将errorlevel嵌入output.txt”的命令行示例,而不编写批处理文件,则需要执行类似于以下内容的操作:(echo 正在退出... & cmd /c exit /b 1234 & call echo %^^errorlevel% > myerror.txt) | tee file.txt,因为在批处理文件中只有百分号会加倍以进行转义。 - Brent Rittenhouse
此外,@dbenham,我感到困惑,因为您说“DOSKEY宏不能通过批处理执行”,但是在示例中看到您将百分比加倍,这使它看起来好像可以执行。这尤其奇怪,因为...如果您无法在批处理文件中使用它们,那么这如何是一个可用的示例呢?...最终我意识到您的意思是您可以在批处理文件中使用它,因为您只是定义它,而不是执行它,并且定义会持续存在。我真的不知道如何更好地表达这个问题,但认为了解没有doskey知识会让人感到困惑。=/ - Brent Rittenhouse
显示剩余3条评论

3

有一种名为"mtee"的“tee”版本(可在https://ritchielawrence.github.io/mtee/上找到),它可选择性地采用管道进程的退出代码。使用它,您可以编写以下代码:

nmake | mtee /E output.txt

对我来说似乎没有按照广告所述的那样工作: C:\Temp>c:\Python26\python.exe t.py | h:\apps\mtee /E out3.txtC:\Temp>echo %ERRORLEVEL% 0C:\Temp>more t.py import sys sys.exit(1)C:\Temp>h:\apps\mtee /?MTEE v2.21 Win32命令行标准流分割器适用于Windows XP..10。 版权所有(c)2001-2016 Ritchie Lawrence - Jamie
注意问题:https://github.com/ritchielawrence/mtee/issues/4 - Andry

2

受 @dbenham 回答的启发,我制作了以下版本:

(%cmd% & call echo EXIT /B %^^ERRORLEVEL% >%ret_file%)
      | tee %log_file% & %ret_file%>NUL

这是一个可以在命令行或批处理文件中使用的一行代码。它有一个优点,就是该行会产生正确的%ERRORLEVEL%,因此您可以以传统方式进行评估。
1. 执行命令%cmd%,将%ERRORLEVEL%设置为我们需要检索的值。 2. 我们使用CALL机制来获得手动延迟扩展(以便我们获得正确的%ERRORLEVEL%值)。调用创建了一个小命令作为批处理文件%ret_file%,该文件以与原始命令%cmd%相同的%ERRORLEVEL%退出。 3. 管道执行所需操作,然后执行小命令%ret_file%,将%ERRORLEVEL%设置为适当的值。
缺点是这个一行代码会在您的目录中留下包含少量文件的垃圾。
EXIT /B <some_error_value>

您可以轻松调整一行代码,使脚本中的%ret_file%在执行后自动清除。

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