在Windows批处理编程中,如何获取管道命令的错误级别?

35

默认情况下,批处理文件返回最后一个命令的错误码。

有没有办法返回以前命令的错误码?特别是,在管道中返回命令的错误码是否可能?

例如,这个一行的批处理脚本:

foo.exe

返回foo的错误代码。但这个:

foo.exe | tee output.txt

始终返回tee的退出码,该退出码为零。


1
相关:https://dev59.com/TGgu5IYBdhLWcg3wrIqR - eckes
这里的目标究竟是什么?只是为了能够响应错误代码,记录输出并在控制台上显示吗?如果是这样,mkl可能有最好的答案,但我直到发布我的回答并再次审查所有内容后才意识到这一点。 - jwdonahue
这个问题以及所有的回答都不值得它们所获得的声望。我已经删除了我的回答。关于为什么上述代码失败的最好解释可以在https://dev59.com/fWsy5IYBdhLWcg3w-i7h找到,尽管它不是该问题的完全重复。 - jwdonahue
6个回答

13

我也遇到了类似的问题,最终采用以下解决方案,因为我不需要检测确切的错误代码,只需要知道成功或失败。

echo > .failed.tmp    

( foo.exe && del .failed.tmp ) | tee foo.log

if exist .failed.tmp (
    del .failed.tmp
    exit /b 1
) else (
    exit /b 0
)

3

%ERRORLEVEL%变量在管道命令运行之前不会被更新;你需要像其他答案中建议的那样使用包装器。

然而,你可以使用“IF ERRORLEVEL #”。例如:

(
type filename
@REM Use an existing (or not) filename to test each branch
IF ERRORLEVEL 1 (echo ERROR) ELSE (echo OKAY)
) > logfile.txt

ECHO命令只会在返回错误时运行;然而,%ERRORLEVEL%似乎不一致。

7
这对我不起作用,我收到了“ECHO在此时是意外的”的消息。 - Sam Mackrill
5
能否请给这个答案点赞的5位用户提供可重现的步骤?我和@SamMackrill有相同的问题。此时,Windows没有任何预期。 - harpo
建议的答案不起作用。它会出现一个消息“(在此时意外”这是在Windows 10机器上观察到的。 - John Rocha
确实可以(在Win10中,使用cmd.exe而不是powershell),只需启用cmd扩展即可。 - whitey04

3

一种解决方法是通过文件进行间接引用。

就像这样

foo.exe > tmp.txt
set FOOERR=%ERRORLEVEL%
cat tmp.txt
exit %FOOERR%

18
这样做完全失去了使用tee的意义,因为它既可以立即输出到控制台,也可以记录到文件中。 - Sam Mackrill

2

经过大约一天的搜索,我找到了一种方法:

set error_=0
9>&1 1>&2 2>&9 (for /f "delims=" %%i in ('9^>^&1 1^>^&2 2^>^&9 ^(^(^(2^>^&1 call "%homeDir%%1"^) ^|^| ^(1^>^&2 2^>nul echo FAILED^)^) ^| 2^>nul "%homeDir%mtee" /T /+ "%homeDir%logs\%date_%_%1.log"^)') do (set error_=1))

exit /b %error_%

在上面的示例中,“%homeDir%%1”被执行,并且其输出被管道传输到“%homeDir%mtee”。这行代码检测故障(建议您绘制批处理上下文及其stdin / stdout / stderr分配的图表,以便了解它的作用 :-))。我没有找到提取实际错误级别的好方法。我得到的最好的东西是用一些批处理脚本调用“call rc.bat”来替换“echo”命令,看起来像:
@echo %errorlevel%

然后将“set error_=1”替换为“set error_=%%i”。

但问题在于,这个调用也可能失败,而且很难检测到。尽管如此,它仍然比没有好得多——我在互联网上没有找到任何解决方案。


2
为了调用tee来输入批处理文件,而不是单个命令,并且自由使用errorlevel,我使用以下技巧:
if "%1" == "body" goto :body
call %0 body | tee log.txt
goto :eof
:body

set nls_lang=american_america
set HomePath=%~dp0

sqlplus "usr/pwd@tnsname" "@%HomePath%script.sql" 
if errorlevel 1 goto dberror

rem Here I can do something which is dependent on correct finish of script.sql    

:dberror

echo script.sqlerror failed

它通过使用tee进行分离,使批处理中不调用任何命令。


1

您可以通过创建一个包装器来解决问题:

rem wrapper for command file, wrapper.cmd

call foo.exe

echo %errorlevel%

if errorlevel 1 goto...

然后将tee附加到包装器中:

wrapper.cmd | tee result.log

当然,这并不完全相同,例如,如果您想在包装文件中登录多个文件,则不可能,但在我的情况下,它解决了问题。

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