重定向失败(由于文件不存在或文件访问不足),似乎没有设置ErrorLevel
值(在以下示例中,文件test.tmp
被写保护,文件test.nil
不存在):
>>> (call ) & rem // (reset `ErrorLevel`)
>>> > "test.tmp" echo Text
Access is denied.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
>>> (call ) & rem // (reset `ErrorLevel`)
>>> < "test.nil" set /P DUMMY=""
The system cannot find the file specified.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
但是,只要失败的重定向后面是查询退出代码的条件并置运算符||
,ErrorLevel
就会意外地设置为1
:
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (> "test.tmp" echo Text) || echo Fail
Access is denied.
Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (< "test.nil" set /P DUMMY="") || echo Fail
The system cannot find the file specified.
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=1
有趣的是,当使用运算符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
, 0
仍然&
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (> "test.tmp" echo Text) & echo Pass or Fail
Access is denied.
Pass or Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
>>> (call ) & rem // (reset `ErrorLevel`)
>>> (< "test.nil" set /P DUMMY="") & echo Pass or Fail
The system cannot find the file specified.
Pass or Fail
>>> echo ErrorLevel=%ErrorLevel%
ErrorLevel=0
如果出现条件连接运算符&&
和||
,ErrorLevel
也设置为1
(如果||
出现在&&
之前,两个分支都按照上一个示例执行,但我认为这只是因为&&
计算了前面echo
命令的退出代码):
>>> (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
那么ErrorLevel
值和||
运算符之间的联系是什么,为什么ErrorLevel
受||
的影响? ||
是否将退出代码复制到ErrorLevel
?所有这些只能用于(失败的)重定向,因为这些是在执行任何命令之前处理的吗?
更奇怪的是,在正确恢复测试设置(即替换{{}时,我无法观察到相反的行为 - ErrorLevel
被0
重置为&&
{1}} (call )
{最初设置(call)
到ErrorLevel
),清除文件1
的只读属性,创建文件test.tmp
(首先行不为空以避免test.nil
将set /P
设置为ErrorLevel
),并使用文件扩展名1
而不是.bat
进行测试(以避免.cmd
将set /P
重置为ErrorLevel
))。
我在Windows 7和Windows 10上观察到了所描述的行为。
答案 0 :(得分:5)
我差不多5年前在File redirection in Windows and %errorlevel%发现了这种不合逻辑的行为。两个月后,我在batch: Exit code for "rd" is 0 on error as well发现了与RD(RMDIR)命令相同的问题。最后一个问题的标题实际上是误导性的,因为失败的RD的返回码是非零的,但是ERRORLEVEL与执行命令之前存在的任何值都没有变化。如果返回码真的为0,则||
运算符不会触发。
所有这些只能用于(失败的)重定向,因为这样 在任何命令执行之前处理?
在执行命令之前,重定向失败是正确的。并且||
响应重定向操作的非零返回码。如果重定向失败,则永远不会执行该命令(在您的情况下为ECHO)。
那么ErrorLevel值和||之间的联系是什么 运算符,为什么ErrorLevel受||影响?是||复制出口 代码到ErrorLevel?
必须跟踪两个不同的错误相关值 - 1)任何给定的命令(或操作)返回代码(退出代码),以及2)ERRORLEVEL。返回代码是瞬态的 - 必须在每次操作后检查它们。 ERRORLEVEL是cmd.exe随时间保持“重要”错误状态的方法。目的是检测所有错误,并相应地设置ERRORLEVEL。但是ERRORLEVEL对于批处理开发人员来说是没用的,如果它在每次成功操作后总是被清除为0。因此,cmd.exe的设计者试图在成功的命令何时清除ERRORLEVEL以及何时保留先前值时做出逻辑选择。我不确定他们的选择有多么明智,但我试图在Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?记录规则。
本节的其余部分是受过教育的猜想。如果没有cmd.exe原始开发人员的通信,我认为没有明确的答案。但这就是为我提供了一个成功导航cmd.exe错误行为的心理框架。
我相信在cmd.exe中可能发生错误的地方,开发人员应该检测返回代码,出错时将ERRORLEVEL设置为非零,然后触发任何||
代码,如果它在玩。但在少数情况下,开发人员通过不遵守规则来引入错误。在重定向失败或RD失败后,开发人员成功调用了||
代码,但未能正确设置ERRORLEVEL。
我也相信||
的开发人员做了一些防御性编程。在执行||
代码之前,ERRORLEVEL应该已经设置为非零。但我认为||
开发人员明智地不相信他/她的同行,并决定在||
处理程序中设置ERRORLEVEL。
关于使用什么非零值,||
将原始返回码值转发给ERRORLEVEL似乎是合乎逻辑的。这意味着原始返回代码必须存储在与ERRORLEVEL不同的某个临时存储区域中。我有两个支持这一理论的证据:
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,则ERRORLEVEL为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
我在脑海中解决了这个异常现象,假设负责在||
缺席中设置9009 ERRORLEVEL的代码必须进行某种类型的上下文敏感转换才能生成9009.但是||
handler不知道翻译,因此它只是将原生返回代码转发给ERRORLEVEL,覆盖已存在的9009值。
根据是否使用||
,我不知道任何其他命令会提供不同的非零ERRORLEVEL值。
更奇怪的是,我无法观察到相反的行为 - 错误级别由&amp;&amp;重置为0 - ,正确还原时 测试设置(即,通过(调用)替换(调用)(将ErrorLevel设置为 最初1),清除文件test.tmp的只读属性, 创建文件test.nil(第一行不为空以避免设置/ P) ErrorLevel为1),并使用文件扩展名.bat而不是.cmd 测试(避免设置/ P将ErrorLevel重置为0))。
一旦你接受并非所有命令在成功时都清除了ERRORLEVEL,那么这种行为就非常有意义了。如果&&
要消除保留的错误,保留先前的错误并不会有多大帮助。
答案 1 :(得分:4)
note :本答案中的所有内容只是对cmd.exe
的汇编代码/调试符号的个人解释,是对生成汇编器输出的源代码的推导。这个答案中的所有代码都只是一种伪代码,大致反映了cmd.exe
中发生的事情,只显示与问题相关的部分。
我们需要知道的第一件事是从errorlevel
中的内部变量_LastRetCode
(至少这是调试信息符号中的名称)检索GetEnvVar
值功能
要知道的第二件事是内部大多数cmd
命令与一组函数相关联。这些函数更改(或不更改)_LastRetCode
中的值,但它们还返回内部用于确定是否存在错误的成功/失败代码(0/1
值)。
对于echo
命令,eEcho
函数处理输出功能,编码类似
eEcho( x ){
....
// Code to echo the required value
....
return 0
}
也就是说,echo
命令只有一个退出点,并且没有设置/清除_LastRetCode
变量(它不会更改errorlevel
值)并且总是会返回一个成功代码。 echo
不会失败(从批处理的角度来看,它可能会失败并写入stderr
,但它将始终返回0
并且永远不会更改_LastRetCode
。 / p>
但是,如何调用此函数?,如何创建重定向?
有一个Dispatch
函数确定要调用的命令/函数,之前调用SetDir
函数(如果需要)来创建所需的重定向。设置重定向后,GetFuncPtr
函数将检索要执行的函数的地址(与cmd
命令关联的函数)调用它并将其输出返回给调用者。
Dispatch( x, x ){
....
if (redirectionNeeded){
ret = SetRedir(...)
if (ret != 0) return 1
}
....
func = GetFuncPtr(...)
ret = func(...)
return ret
}
虽然这些函数的返回值反映了错误的存在(失败时返回1
,成功时返回0
),而Dispatch
则不返回SetDir
更改_LastRetCode
变量(虽然此处未显示,但它们都没有对变量进行任何引用),因此重定向失败时没有errorlevel
更改。
使用||
运算符时会发生什么变化?
||
运算符在已编码的eOr
函数内处理,是,再次或多或少
eOr( x ){
ret = Dispatch( leftCommand )
if (ret == 0) return 0
_LastRetCode = ret
ret = Dispatch( rightCommand )
return ret
}
首先执行左侧的命令。如果它没有返回错误,则无需执行任何操作,并以成功值退出。但是如果left命令失败,它会在调用右侧的命令之前将返回的值存储在_LastRetCode
变量中。在问题的情况下
(> "test.tmp" echo Text) || echo Fail
执行是
Dispatch
(已从处理批处理执行的函数调用并作为参数接收执行的内容)调用eOr
eOr
致电Dispatch
执行eEcho
(运营商左侧)Dispatch
调用SetDir
来创建重定向SetDir
失败并返回1
(_LastRetCode
没有任何变更)Dispatch
返回1
,SetDir
失败eOr
检查返回值,因为它不是0
,返回的值会存储在_LastRetCode
中。现在_LastRetCode
是1
eOr
调用Dispatch
来执行eEcho
(运营商的右侧)Dispatch
调用GetFuncPtr
来检索eEcho
函数的地址。Dispatch
调用返回的函数指针并返回返回值(eEcho
始终返回0
)eOr
返回Dispatch
(0
)这就是使用return 1
运算符时,失败的重定向操作返回的错误值(_LastRetCode
)存储在||
中的方式。
eAnd
函数中发生了什么,即&&
运算符?
eAnd( x ){
ret = Dispatch( leftCommand )
if (ret != 0) return ret
ret = Dispatch( rightCommand )
return ret
}
_LastRetCode
没有任何变化,因此,如果被调用的命令没有改变变量,则errorlevel
没有任何变化。
请记住,这只是我所看到的解释,实际代码的行为大致相同,但几乎肯定会有所不同。