【问题标题】:Best Practice - Testing Batch Commands for ErrorLevel最佳实践 - 测试 ErrorLevel 的批处理命令
【发布时间】:2019-01-10 17:26:28
【问题描述】:

我的批处理文件运行几个注册表编辑命令,如果所述注册表编辑命令返回错误级别,我想实现一个条件。我的问题是:我应该将这些命令包装在一个函数中,还是在每个注册表命令之后创建有条件的错误级别?我担心将这些命令包装到一个函数中是不是每个命令都会被检查。我想确保每个命令都得到检查,并返回一个错误级别。

用于在函数中包装命令的代码块可能不正确或缺少部分。不确定我是否需要在每个函数结束时包含一个 EXIT?这是一个多步骤批处理脚本的一部分。

TITLE Disable UAC
ECHO STEP 1: Disable User Account Control
reg add HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f

IF %ERRORLEVEL% NEQ 0 ECHO An error was found w/ error code of: %ERRORLEVEL%

或

:disable_uac
TITLE Disable UAC
ECHO STEP 1: Disable User Account Control
reg add HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f

call :disable_uac
IF %ERRORLEVEL% NEQ 0 ECHO An error was found w/ error code of: %ERRORLEVEL$

关于第一个代码块,我假设 ERRORLEVEL 条件只会返回 LAST 命令的错误代码而不是两者?

更新代码:

@echo off
goto ErrorCheck

SetLocal EnableDelayedExpansion

ECHO STEP 1: Copy Folder
robocopy "C:\Users\!username!\Documents\WindowsSetup\Training_Videos" "C:\Users\!username!\Desktop\Training_Videos" /e /copyall
CMD /K

:ErrorCheck
title Disable UAC
for /F "useback skip=2 delims= eol=" %%I in ("%~f0") do (
    IF "%%~I" == ":ErrorCheck" exit /B
    ECHO %%I
    %%I >>"C:\errorlog.log"
    if errorlevel 1 exit /B & ECHO You have encountered an issue. Look at Error Log for more information
)

【问题讨论】:

  • 是的,ERRORLEVEL 仅指最后一个命令,一旦您执行另一个命令,它就会被覆盖。
  • 因此,如果我不将这两个命令包装在一个函数中,那么如果我想要检查这两个命令,我需要调用该 ERRORLEVEL if 语句两次?
  • 您需要检查错误的频率取决于您在做什么。在这种情况下,第一个 reg 命令的错误并不一定意味着第二个 reg 命令也会失败(因为它们写入不同的注册表位置)。但也许继续执行脚本仍然没有意义,因此您可能仍然想要在每个命令之后检查错误。 (顺便说一句,你为什么用大写字母写“EVERY”?错误处理是必要的——不要认为这是一件麻烦事!)
  • 使用for 循环或在每个命令后使用%errorlevel%。
  • 通过附加|| (echo this command failed) 在失败时使用条件执行怎么样?

标签: batch-file cmd


【解决方案1】:

一个非常简单的解决方案,可以运行在批处理文件中编写的多个命令,并抑制编写用于处理 STDOUT 的标准输出和编写用于处理 STDERR 的错误消息并退出批处理文件执行在第一个命令或可执行文件以退出代码大于或等于退出时,1 是:

@echo off
goto MainCode

reg.exe add HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f
reg.exe add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f

:MainCode
title Disable UAC
for /F "useback skip=2 delims= eol=" %%I in ("%~f0") do (
    if "%%~I" == ":MainCode" exit /B
    echo %%I
    %%I >nul 2>nul
    if errorlevel 1 exit /B
)

由于 FOR 循环命令行上的 skip=2 和标签行 :MainCode,此批处理文件执行批处理文件中第二行下方定义的所有非空行。

这些命令行不能包含环境变量引用以及所提供的代码,因为环境变量引用在执行之前不会被 Windows 命令处理器扩展。当然可以在FOR循环执行期间使用命令call强制额外解析命令行以扩展环境变量引用。

goto MainCode 和 :MainCode 之间的命令行也不能包含重定向运算符,例如 <、>、>> 和 |。重定向由cmd.exe 执行批处理文件完成。 Windows 命令处理器在执行前必须在解析命令行时识别它们。因此,当cmd.exe 在执行之前解析命令行%%I >nul 2>nul 时,它会在执行当前分配给循环变量I 的任何内容之前识别重定向>nul 和2>nul,但cmd.exe 将永远不会识别指定的此类重定向在命令行上分配给循环变量I。

另见How does the Windows Command Interpreter (CMD.EXE) parse scripts?

此演示批处理文件的执行在第一个命令/可执行文件退出时立即退出,值大于或等于 1。 IF 和 EXIT 命令不会修改变量errorlevel,因此调用进程会获取失败命令/可执行文件的退出代码。

另见:

批处理文件的退出代码是0,没有命令退出值大于或等于 1。

FOR 在此处打开已由cmd.exe 指定的批处理文件,该批处理文件由%~f0 指定,由于选项/F 而扩展为批处理文件本身的驱动器+路径+名称+扩展名并处理一行一行。

选项usebackq 是获取包含在" 中的完整批处理文件名所必需的,将其解释为应处理哪些行的文件名,而不是将完整限定批处理文件名本身解释为要由处理的字符串为。

skip=2 选项告诉 FOR 跳过打开文件的前两行并开始处理第三行的行。

FOR 带有选项/F 总是忽略空行。 FOR 在使用选项/F 时也会忽略以分号开头的行,因为eol=; 是默认的行尾定义。因此,eol= 用于定义无行尾字符,从而不会忽略以分号开头的行。

FOR 默认情况下将从文件中读取的每个非空行拆分为子字符串,使用普通空格和水平制表符作为字符串分隔符,并且只会将第一个空格/制表符分隔的字符串分配给指定的循环变量 @987654359 @。这里不需要这种线分割行为。批处理文件中写入的整行应分配给循环变量I。出于这个原因,delims= 被指定定义一个空的字符串分隔字符列表,它完全禁用行拆分行为。

批处理文件的主要代码也可以这样写:

title Disable UAC
del "%TEMP%\%~n0.log" 2>nul
for /F "useback skip=2 delims= eol=" %%I in ("%~f0") do (
    if "%%~I" == ":MainCode" goto Finished
    %%I >nul 2>>"%TEMP%\%~n0.log" || exit /B
)
:Finished
del "%TEMP%\%~n0.log" 2>nul
exit /B 0

批处理文件的此变体以静默方式运行命令/可执行文件,同时抑制标准输出并将错误输出记录到文件夹中的文件中,用于临时文件的名称为批处理文件,文件扩展名为.log。

如果任何命令/可执行文件以不等于 0 且最后执行的命令/可执行文件的退出代码或到达标签行并删除空日志文件和退出代码 0 的值退出,则循环退出。

还有更强化的版本是:

@echo off
goto MainCode

e   TITLE Disable UAC
c   echo Errors are log into file: "%TEMP%\%~n0.log"
p   STEP 1: Disable User Account Control
r   reg.exe add HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f
r   reg.exe add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f

:MainCode
del "%TEMP%\%~n0.log" 2>nul
for /F "useback skip=2 tokens=1* eol=" %%I in ("%~f0") do (
    if %%I == :MainCode del "%TEMP%\%~n0.log" 2>nul & exit /B 0
    if %%I == c (
        call %%J
    ) else if %%I == e (
        %%J
    ) else if %%I == p (
        echo(%%J
    ) else (
        %%J >nul 2>>"%TEMP%\%~n0.log" || exit /B
    )
)

在FOR循环中,每个命令行的开头都有一个字符来控制如何执行这一行。该字符用一个或多个空格或水平制表符与命令行分隔。执行控制字符的含义如下:

  • c ... 使用命令 CALL 调用此命令行以在执行前扩展环境变量引用,并且不抑制任何输出并且不评估退出代码。
  • e ... 执行此命令行而不抑制任何输出且不评估退出代码。
  • p ...此行仅包含要使用命令 ECHO 打印(进入控制台窗口)的文本。
  • r ... 运行此命令行,抑制标准输出并将错误输出记录到日志文件,并在命令/可执行文件的退出代码上退出批处理文件执行不是0,表示执行不成功.

echo 和 %%J 之间的 ( 可以通过在命令块中插入仅包含 p 的行来打印空行。

FOR 选项delims= 被tokens=1* 替换。 FOR 的行拆分行为与默认分隔符空格和制表符在这里需要。第一个空格/制表符分隔的字符串应该分配给循环变量I,这里是每行开头的命令执行控制字符,分别执行或输出标签:MainCode。在分配给循环变量I 的第一个字符串之后的空格/制表符之后的所有内容都不应在空格/制表符上进一步拆分。出于这个原因,* 被附加到tokens=1,它告诉 FOR 在c、e、p 或 r 之后的空格/制表符之后分配该行的其余部分根据ASCII table(即字符J)到下一个循环变量。

要了解所使用的命令及其工作原理,请打开命令提示符窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。

  • call /? ... 解释 %~f0 ... 参数 0 的完整文件名,即带有文件扩展名和完整路径的批处理文件名以及 %~n0 扩展为仅没有文件扩展名的批处理文件的文件名和路径。
  • echo /?
  • exit /?
  • for /?
  • goto /?
  • if /? ... 解释说if ERRORLEVEL number 表示ERRORLEVEL 是否大于或等于 number 和不等于 number 有多少人没有阅读帮助/文档认为。
  • reg /?
  • reg add /?

有关运算符& 和|| 的说明,另请参阅single line with multiple commands using Windows batch file。

【讨论】:

    【解决方案2】:

    我会使用条件执行,例如:

    @echo off
    CALL :DISABLEUAC || GOTO :ERRHANDLER
    GOTO :EOF
    
    :DISABLEUAC
    ECHO STEP 1: Disable User Account Control
    reg add HKEY_CURRENT_USER\... /f || GOTO :EOF
    reg add HKEY_LOCAL_MACHINE\... /f || GOTO :EOF
    GOTO :EOF
    
    :ERRHANDLER
    ECHO An error was found w/ error code of: %ERRORLEVEL%
    GOTO :EOF
    
    • 在 DISABLEUAC 子例程中,如果命令失败,则使用条件执行退出子例程

    • 在主模块中,调用子程序,如果子程序失败,使用条件执行跳转到错误处理程序。

    【讨论】:

      猜你喜欢
      • 2017-04-27
      • 1970-01-01
      • 2019-10-20
      • 1970-01-01
      • 1970-01-01
      • 2011-06-29
      • 2020-02-05
      • 2013-08-25
      • 1970-01-01
      相关资源
      最近更新 更多