如何从Windows命令行获取应用程序退出代码?

How do I get the application exit code from a Windows command line?

我正在运行一个程序,想看看它的返回代码是什么(因为它根据不同的错误返回不同的代码)。

我知道在Bash中我可以通过运行来做到这一点

echo $?

在Windows上使用cmd.exe时该怎么办?


名为errorlevel的伪环境变量存储退出代码:

1
echo Exit Code is %errorlevel%

此外,if命令具有特殊语法:

1
if errorlevel

有关详细信息,请参阅if /?

1
2
3
4
5
6
@echo off
my_nify_exe.exe
if errorlevel 1 (
   echo Failure Reason Given is %errorlevel%
   exit /b %errorlevel%
)

警告:如果设置环境变量名称errorlevel%errorlevel%将返回该值而不是退出代码。使用(set errorlevel=)清除环境变量,允许通过%errorlevel%环境变量访问errorlevel的真值。


测试errorlevel适用于控制台应用程序,但正如dmihailescu所暗示的那样,如果您尝试从命令提示符运行窗口化应用程序(例如基于Win32),则无法运行。窗口化应用程序将在后台运行,控件将立即返回到命令提示符(很可能errorlevel为零,表示该过程已成功创建)。当窗口化应用程序最终退出时,其退出状态将丢失。

但是,更简单的替代方法是使用命令提示符的START /WAIT命令启动窗口化应用程序,而不是使用其他地方提到的基于控制台的C ++启动程序。这将启动窗口化应用程序,等待它退出,然后将控制权返回到命令提示符,并在errorlevel中设置进程的退出状态。

1
2
start /wait something.exe
echo %errorlevel%


使用内置的ERRORLEVEL变量:

1
echo %ERRORLEVEL%

但要注意应用程序是否定义了名为ERRORLEVEL的环境变量!


如果要精确匹配错误代码(例如等于0),请使用:

1
2
3
4
5
6
7
8
@echo off
my_nify_exe.exe
if %ERRORLEVEL% EQU 0 (
   echo Success
) else (
   echo Failure Reason Given is %errorlevel%
   exit /b %errorlevel%
)

if errorlevel 0匹配errorlevel> = 0.参见if /?


使用未附加到控制台的程序时,它可能无法正常工作,因为当您认为有退出代码时,该应用程序可能仍在运行。
用C ++实现它的解决方案如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include"stdafx.h"
#include"windows.h"
#include"stdio.h"
#include"tchar.h"
#include"stdio.h"
#include"shellapi.h"

int _tmain( int argc, TCHAR *argv[] )
{

    CString cmdline(GetCommandLineW());
    cmdline.TrimLeft('"');
    CString self(argv[0]);
    self.Trim('"');
    CString args = cmdline.Mid(self.GetLength()+1);
    args.TrimLeft(_T("""));
    printf("Arguments passed: '%ws'
",args);
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    if( argc < 2 )
    {
        printf("Usage: %s arg1,arg2....
", argv[0]);
        return -1;
    }

    CString strCmd(args);
    // Start the child process.
    if( !CreateProcess( NULL,   // No module name (use command line)
        (LPTSTR)(strCmd.GetString()),        // Command line
        NULL,           // Process handle not inheritable
        NULL,           // Thread handle not inheritable
        FALSE,          // Set handle inheritance to FALSE
        0,              // No creation flags
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory
        &si,            // Pointer to STARTUPINFO structure
        &pi )           // Pointer to PROCESS_INFORMATION structure
    )
    {
        printf("CreateProcess failed (%d)
", GetLastError() );
        return GetLastError();
    }
    else
        printf("Waiting for "%ws" to exit.....
", strCmd );

    // Wait until child process exits.
    WaitForSingleObject( pi.hProcess, INFINITE );
    int result = -1;
    if(!GetExitCodeProcess(pi.hProcess,(LPDWORD)&result))
    {
        printf("GetExitCodeProcess() failed (%d)
", GetLastError() );
    }
    else
        printf("The exit code for '%ws' is %d
",(LPTSTR)(strCmd.GetString()), result );
    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    return result;
}


值得注意的是.BAT和.CMD文件的运行方式不同。

阅读https://ss64.com/nt/errorlevel.html,它注意到以下内容:

There is a key difference between the way .CMD and .BAT batch files set errorlevels:

An old .BAT batch script running the 'new' internal commands: APPEND, ASSOC, PATH, PROMPT, FTYPE and SET will only set ERRORLEVEL if an error occurs. So if you have two commands in the batch script and the first fails, the ERRORLEVEL will remain set even after the second command succeeds.

This can make debugging a problem BAT script more difficult, a CMD batch script is more consistent and will set ERRORLEVEL after every command that you run .

这导致我在执行连续命令时没有悲伤,但即使发生故障,ERRORLEVEL也会保持不变。


有一次,我需要准确地将日志事件从Cygwin推送到Windows事件日志。我希望WEVL中的消息是自定义的,具有正确的退出代码,详细信息,优先级,消息等。所以我创建了一个小的Bash脚本来处理这个问题。这是在GitHub上,logit.sh。

一些摘录:

1
2
usage: logit.sh [-h] [-p] [-i=n] [-s] <description>
example: logit.sh -p error -i 501 -s myscript.sh"failed to run the mount command"

这是临时文件内容部分:

1
2
3
4
5
6
7
LGT_TEMP_FILE="$(mktemp --suffix .cmd)"
cat<<EOF>$LGT_TEMP_FILE
    @echo off
    set LGT_EXITCODE="$LGT_ID"
    exit /b %LGT_ID%
EOF
unix2dos"$LGT_TEMP_FILE"

这是在WEVL中创建事件的功能:

1
2
3
4
5
6
7
8
9
10
11
__create_event () {
    local cmd="eventcreate /ID $LGT_ID /L Application /SO $LGT_SOURCE /T $LGT_PRIORITY /D"
    if [["$1" == *';'* ]]; then
        local IFS=';'
        for i in"$1"; do
            $cmd"$i" &>/dev/null
        done
    else
        $cmd"$LGT_DESC" &>/dev/null
    fi
}

执行批处理脚本并调用__create_event:

1
2
cmd /c"$(cygpath -wa"$LGT_TEMP_FILE")"
__create_event