Windows 为什么在重定向失败时仅在| |运算符之后设置ErrorLevel?

Windows 为什么在重定向失败时仅在| |运算符之后设置ErrorLevel?,windows,batch-file,cmd,io-redirection,exit-code,Windows,Batch File,Cmd,Io Redirection,Exit Code,重定向失败时(由于文件不存在或文件访问不足),似乎未设置ErrorLevel值(在以下示例中,文件test.tmp受写保护,文件test.nil不存在): 有趣的是,当使用操作员&&时,ErrorLevel保持0: >>> (call ) & rem // (reset `ErrorLevel`) >>> (> "test.tmp" echo Text) && echo Pass Access is denied. >&

重定向失败时(由于文件不存在或文件访问不足),似乎未设置
ErrorLevel
值(在以下示例中,文件
test.tmp
受写保护,文件
test.nil
不存在):

有趣的是,当使用操作员
&&
时,
ErrorLevel
保持
0

>>> (call ) & rem // (reset `ErrorLevel`)

>>> (> "test.tmp" echo Text) && echo Pass
Access is denied.

>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0

>>> (call ) & rem // (reset `ErrorLevel`)

>>> (< "test.nil" set /P DUMMY="") && echo Pass
The system cannot find the file specified.

>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
如果同时出现条件连接运算符
&&
|
,则
ErrorLevel
也设置为
1
(如果
|
出现在
&&
之前,两个分支都会像上一个示例中一样执行,但我认为这只是因为
&&
计算前面
echo
命令的退出代码):

>(调用)&rem/(重置'ErrorLevel`)
>>>(>“test.tmp”回显文本)&回显通过| |回显失败
访问被拒绝。
失败
>>>回显ErrorLevel=%ErrorLevel%
ErrorLevel=1
>>>(调用)&rem/(重置'ErrorLevel`)
>>>(<“test.nil”set/P DUMMY=“”)| |回波失败和回波通过
系统找不到指定的文件。
失败
通过
>>>回显ErrorLevel=%ErrorLevel%
ErrorLevel=1
那么
ErrorLevel
值和
|
操作符之间的关系是什么,为什么
ErrorLevel
会受到
|
的影响?是否
|
将退出代码复制到
ErrorLevel
?所有这些都只有在(失败)的情况下才可能发生重定向,因为这是在执行任何命令之前处理的

更奇怪的是,当正确恢复测试设置时(即,用
(调用)
(最初将
错误级别设置为
1
),我无法观察到相反的行为--
错误级别被
和&
重置为
0
),清除文件
test.tmp
的只读属性,创建文件
test.nil
(第一行不为空以避免
set/P
ErrorLevel
设置为
1
),并使用文件扩展名
.bat
而不是
.cmd进行测试(为了避免
set/P
ErrorLevel
重置为
0


我在Windows 7和Windows 10上观察到了所描述的行为。

我第一次发现这种不合逻辑的行为大约是在5年前。两个月后,我发现RD(RMDIR)也存在同样的问题位于处的命令。最后一个问题的标题实际上是误导性的,因为失败RD的返回码不是零,但执行该命令之前存在的任何值的ERRORLEVEL都是不变的。如果返回码真的是0,则
|
操作符将不会触发

这一切是否只有在(失败的)重定向时才可能发生,因为这样做是错误的 在执行任何命令之前处理

在执行命令之前重定向失败是正确的。
|
响应重定向操作的非零返回代码。如果重定向失败,则永远不会执行命令(在您的情况下为ECHO)

那么ErrorLevel值和|| 接线员,为什么ErrorLevel会受到| |的影响?| |正在复制出口吗 代码到错误级别

必须跟踪两个不同的错误相关值-1)任何给定的命令(或操作)返回代码(退出代码),2)错误级别。返回代码是瞬态的-必须在每次操作后检查它们。ERRORLEVEL是cmd.exe随时间保持“重要”错误状态的方法。目的是检测所有错误,并相应设置错误级别。但是,如果每次成功操作后都将ERRORLEVEL清除为0,那么对于批处理开发人员来说,ERRORLEVEL将是无用的。因此,cmd.exe的设计者试图做出逻辑选择,以确定成功的命令何时清除ERRORLEVEL,以及何时保留先前的值。我不确定他们的选择有多明智,但我已经尝试在网站上记录这些规则

本节的其余部分是经过教育的猜想。如果没有cmd.exe的原始开发人员的沟通,我认为不可能有一个明确的答案。但是,这给了我一个心智框架,使我能够成功地驾驭cmd.exe错误行为的泥潭

我相信,只要cmd.exe中出现错误,开发人员就应该检测返回代码,在出现错误时将ERRORLEVEL设置为非零,然后触发任何
|
代码(如果有)。但在少数情况下,开发人员由于不遵守规则而引入了一个bug。重定向失败或RD失败后,开发人员成功调用了
|
代码,但未能正确设置错误级别

我还相信
| |
的开发人员做了一些防御性编程。在执行
| |
代码之前,ERRORLEVEL应已设置为非零。但是我认为
|
开发人员明智地不信任他/她的同事,并决定在
|
处理程序中设置错误级别

至于使用什么非零值,似乎合乎逻辑的是,
|
会将原始返回代码值转发到ERRORLEVEL。这意味着原始返回代码必须存储在与ERRORLEVEL不同的临时存储区域中。我有两条证据支持这一理论:

1)

2)
|
设置的ERRORLEVEL与CMD/C设置的值相同,CMD/C只是转发最后一个命令/操作的返回代码

C:\test>(call )&rd .
The process cannot access the file because it is being used by another process.

C:\test>echo %errorlevel%
0

C:\test>(call )&rd . || rem
The process cannot access the file because it is being used by another process.

C:\test>echo %errorlevel%
32

C:\test>(call )&cmd /c rd .
The process cannot access the file because it is being used by another process.

C:\test>echo %errorlevel%
32
然而,有一个特点可能使这一理论失效。如果试图运行不存在的命令,则会出现9009错误:

C:\test>invalidCommand
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:\test>echo %errorlevel%
9009
但是如果使用
|
运算符或CMD/C,则错误级别为1:-/

C:\test>invalidCommand || rem
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:\test>echo %errorlevel%
1

C:\test>(call )

C:\test>cmd /c invalidCommand
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:\test>echo %errorlevel%
1
我通过假设负责在a中设置9009 ERRORLEVEL的代码解决了这个异常
>>> (call ) & rem // (reset `ErrorLevel`)

>>> (> "test.tmp" echo Text) && echo Pass || echo Fail
Access is denied.
Fail

>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1

>>> (call ) & rem // (reset `ErrorLevel`)

>>> (< "test.nil" set /P DUMMY="") || echo Fail && echo Pass
The system cannot find the file specified.
Fail
Pass

>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
C:\test>(call )&rd .
The process cannot access the file because it is being used by another process.

C:\test>echo %errorlevel%
0

C:\test>(call )&rd . || rem
The process cannot access the file because it is being used by another process.

C:\test>echo %errorlevel%
32

C:\test>(call )&cmd /c rd .
The process cannot access the file because it is being used by another process.

C:\test>echo %errorlevel%
32
C:\test>invalidCommand
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:\test>echo %errorlevel%
9009
C:\test>invalidCommand || rem
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:\test>echo %errorlevel%
1

C:\test>(call )

C:\test>cmd /c invalidCommand
'invalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:\test>echo %errorlevel%
1
eEcho( x ){
    ....
    // Code to echo the required value
    ....
    return 0
}
Dispatch( x, x ){
    ....

    if (redirectionNeeded){
        ret = SetRedir(...)
        if (ret != 0) return 1
    }

    ....

    func = GetFuncPtr(...)
    ret = func(...)
    return ret
}
eOr( x ){
    ret = Dispatch( leftCommand )
    if (ret == 0) return 0

    _LastRetCode = ret

    ret = Dispatch( rightCommand )
    return ret 
}
(> "test.tmp" echo Text) || echo Fail
eAnd( x ){
    ret = Dispatch( leftCommand )
    if (ret != 0) return ret 

    ret = Dispatch( rightCommand )
    return ret
}