ErrorLevel为什么只能在||之后设置 运营商在一个失败的redirect?

当redirect失败时(由于文件不存在或文件访问不足),似乎不会设置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 

但是,只要失败的redirect之后是条件级联运算符|| ,这是查询退出代码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 ? 所有这些只有(失败的)redirect才是可能的,因为在任何命令执行之前都会处理这些操作?

更奇怪的是,我无法观察到相反的行为 – ErrorLevel在正确恢复testing设置(也就是用(call) (将ErrorLevel初始设置为1 )replace(call )时被&& – 重置为0 ,清除文件test.tmp的只读属性,创build文件test.nil (第一行不为空,以避免set /PErrorLevel设置为1 ),并使用文件扩展名.bat而不是.cmd进行testing(以避免set /PErrorLevel重置为0 ))。

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

Solutions Collecting From Web of "ErrorLevel为什么只能在||之后设置 运营商在一个失败的redirect?"

我在5年前在Windows的文件重定向和%errorlevel%中首次发现了这种不合逻辑的行为。 两个月后,我发现批量 RD(RMDIR)命令同样的问题:出错代码“rd”也是错误的 。 最后一个问题的标题实际上是误导性的,因为失败的RD的返回代码是非零的,但是ERRORLEVEL与执行该命令之前存在的值无关。 如果返回码真的是0,那么|| 操作员不会开火。

所有这些只有(失败的)重定向才是可能的,因为在任何命令执行之前都会处理这些操作?

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

那么ErrorLevel值和||之间的连接是什么? 运算符,为什么ErrorLevel受||影响? 是|| 将退出代码复制到ErrorLevel?

有两个不同的错误相关的值必须被跟踪 – 1)任何给定的命令(或操作)返回码(退出码),以及2)ERRORLEVEL。 返回码是瞬态的 – 每一次操作后都必须检查。 ERRORLEVEL是cmd.exe的方式来保持“重要”的错误状态随着时间的推移。 意图是检测所有错误,并且相应地设置ERRORLEVEL。 但是,如果在每次成功操作之后总是将其清零,那么ERRORLEVEL对于批量开发人员来说是无用的。 因此,cmd.exe的设计者试图做出逻辑选择,以便成功的命令何时清除ERRORLEVEL,以及何时保留先前的值。 我不确定他们的选择有多聪明,但是我试图记录cmd.exe内部命令在成功时将ERRORLEVEL清除为0的规则。 。

本节的其余部分是受过教育的猜想。 如果没有cmd.exe的原始开发人员的沟通,我不认为一个确定的答案是可能的。 但是,这是什么给了我一个心理框架,成功地导航cmd.exe错误行为morass。

我相信,无论在cmd.exe中发生错误的地方,开发人员都应该检测返回码,在出错时将ERRORLEVEL设置为非零值,然后触发任何|| 代码如果在玩。 但是在一些情况下,开发者通过不遵守规则来引入错误。 重定向失败或RD失败后,开发人员成功调用|| 代码,但未能正确设置ERRORLEVEL。

我也相信||的开发者 做了一些防御性的编程。 在||之前,ERRORLEVEL应该已经被设置为非零 代码被执行。 但我认为|| 开发人员明智地不信任他/她的同伴,并决定在||设置ERRORLEVEL 处理程序以及。

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

1) || 当RD失败时,操作员至少设置4个不同的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.但是|| 处理程序不知道翻译,所以它只是将原生返回码转发给ERRORLEVEL,覆盖已经存在的9009值。

我不知道有任何其他命令给出不同的非零ERRORLEVEL值取决于是否 被使用或没有。

更奇怪的是,我无法观察到相反的行为 – ErrorLevel在正确恢复测试设置(也就是用(调用)(将ErrorLevel初始设置为1)替换(调用))时被&& – 重置为0,清除文件test.tmp的只读属性,创建文件test.nil(第一行不为空,以避免设置/ P将ErrorLevel设置为1),并使用文件扩展名.bat而不是.cmd进行测试(以避免设置/ P将ErrorLevel重置为0))。

一旦你接受并不是所有的命令在成功的时候都会清除ERRORLEVEL,那么这个行为是非常有意义的。 如果&&消除了保留的错误,保留以前的错误也不会有太大好处。

注意 :此答案中的所有内容只是cmd.exe的汇编代码/调试符号的个人解释,这是生成汇编程序输出的源代码的一个推导。 在这个答案中的所有代码只是一种伪代码 ,大致反映了cmd.exe发生的事情,只显示与问题相关的部分。

我们需要知道的第一件事是从GetEnvVar函数的内部变量_LastRetCode (至少这是调试信息符号中的名称)中检索errorlevel值。

第二件事要知道的是,在内部大部分cmd命令都与一组函数相关联。 这些函数改变(或不) _LastRetCode的值,但是它们还返回一个内部使用的成功/失败代码( 0/1值),以确定是否有错误。

echo命令的情况下, eEcho函数处理输出功能,编码类似

 eEcho( x ){ .... // Code to echo the required value .... return 0 } 

也就是说, echo命令只有一个退出点,并且不会设置/清除_LastRetCode变量(它不会更改错误errorlevel值),并且将始终返回成功代码。 echo没有失败(从批处理的角度来看,它可能会失败,并写入stderr ,但它将始终返回0 ,永远不会更改_LastRetCode )。

但是,这个函数是如何被调用的?如何创建重定向?

有一个Dispatch功能,决定要调用的命令/函数,并且以前调用SetDir函数(如果需要)来创建所需的重定向。 一旦设置了重定向, GetFuncPtr函数将检索要执行的函数的地址(与cmd命令关联的函数)调用它,并将其输出返回给调用者。

 Dispatch( x, x ){ .... if (redirectionNeeded){ ret = SetRedir(...) if (ret != 0) return 1 } .... func = GetFuncPtr(...) ret = func(...) return ret } 

虽然这些函数的返回值反映出现了一个错误(它们在失败时返回1 ,在成功时返回0 ),而DispatchSetDir不会改变_LastRetCode变量(虽然这里没有显示,但是没有一个引用变量),因此重定向失败时不会发生错误errorlevel更改。

使用||时会发生什么变化 运营商?

|| 运算符是在编码的eOr函数内处理的,是的,或多或少

 eOr( x ){ ret = Dispatch( leftCommand ) if (ret == 0) return 0 _LastRetCode = ret ret = Dispatch( rightCommand ) return ret } 

它首先执行左侧的命令。 如果它没有返回错误,则无事可做,并以成功值退出。 但是,如果左侧命令失败,则在调用右侧命令之前将返回的值存储在_LastRetCode变量中。 在这个问题的情况下

 (> "test.tmp" echo Text) || echo Fail 

执行是

  • Dispatch (谁已经从处理批处理执行的函数中调用,并且接收参数作为参数执行)调用eOr
  • eOr调用Dispatch执行eEcho (操作员的左侧)
  • Dispatch调用SetDir来创建重定向
  • SetDir失败并返回1_LastRetCode没有任何更改)
  • SetDir失败时, Dispatch返回1
  • eOr检查返回值,因为它不是0 ,返回值存储在_LastRetCode 。 现在_LastRetCode1
  • eOr调用Dispatch执行eEcho (操作员右侧)
  • Dispatch调用GetFuncPtr来检索eEcho函数的地址。
  • Dispatch调用返回的函数指针并返回它的返回值( eEcho总是返回0
  • eOr返回Dispatch0 )的返回值

这就是错误值返回( return 1 )在失败的重定向操作如何存储在_LastRetCode使用|| 运营商。

eAnd函数中会发生什么,也就是&&运算符?

 eAnd( x ){ ret = Dispatch( leftCommand ) if (ret != 0) return ret ret = Dispatch( rightCommand ) return ret } 

_LastRetCode没有任何改变,因此,如果被调用的命令不改变这个变量,那么errorlevel没有任何改变。

请记住,这只是我所看到的一个解释,真正的代码将表现得几乎相同,但几乎肯定会有所不同。