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
}