【问题标题】:Does Windows batch support exception handling?Windows 批处理是否支持异常处理?
【发布时间】:2015-07-16 04:24:00
【问题描述】:

Windows 批处理编程是否支持异常处理?如果没有,有什么方法可以有效地模拟批处理文件中的异常处理?

我希望能够在批处理脚本中的任何位置、任何 CALL 级别“抛出异常”,并让 CALL 堆栈反复弹出,直到找到一个活动的“TRY 块”,然后一个“CATCH 块”可以完全处理异常并继续,或进行一些清理并继续弹出 CALL 堆栈。如果从未处理过异常,则终止批处理,控制返回到命令行上下文并显示错误消息。

已经有couple posted ways to terminate batch processing at any CALL depth,但这些技术都不允许任何结构化的清理活动,而这些活动通常会通过异常处理在其他语言中提供。

注意: 这是我已经知道最近才发现的好答案的情况,我想分享信息

【问题讨论】:

    标签: batch-file exception exception-handling cmd


    【解决方案1】:

    Windows 批处理脚本当然没有任何正式的异常处理——考虑到该语言的原始程度,这不足为奇。在我最疯狂的梦想中,我从未想过有效的异常处理会被破解。

    但是随后在Russian site 上发现了一些关于错误 GOTO 语句行为的惊人发现(我不知道说了什么,我看不懂俄语)。 An English summary was posted at DosTips,并进一步调查了该行为。

    事实证明,(GOTO) 2>NUL 的行为几乎与 EXIT /B 相同,除了已解析的代码块中的连接命令在有效返回后仍会执行之外, 在CALLer 的上下文!

    这是一个简短的例子,展示了大部分的要点。

    @echo off
    setlocal enableDelayedExpansion
    set "var=Parent Value"
    (
      call :test
      echo This and the following line are not executed
      exit /b
    )
    :break
    echo How did I get here^^!^^!^^!^^!
    exit /b
    
    :test
    setlocal disableDelayedExpansion
    set "var=Child Value"
    (goto) 2>nul & echo var=!var! & goto :break
    echo This line is not executed
    
    :break
    echo This line is not executed
    

    -- 输出--

    var=Parent Value
    How did I get here!!!!
    

    这个功能完全出乎意料,而且非常强大和有用。它被用于:

    • 创建 PrintHere.bat - 模拟 'nix here 文档功能
    • 创建一个RETURN.BAT utility,任何批处理“函数”都可以方便地调用以跨 ENDLOCAL 屏障返回任何值,几乎没有任何限制。该代码是jeb's original idea 的充实版本。

    现在我还可以在列表中添加异常处理:-)

    该技术依赖于名为 EXCEPTION.BAT 的批处理实用程序来定义用于指定 TRY/CATCH 块以及引发异常的环境变量“宏”。

    在实现 TRY/CATCH 块之前,必须使用以下方法定义宏:

    call exception init
    

    然后使用以下语法定义 TRY/CATCH 块:

    :calledRoutine
    setlocal
    %@Try%
      REM normal code goes here
    %@EndTry%
    :@Catch
      REM Exception handling code goes here
    :@EndCatch
    

    可以通过以下方式随时抛出异常:

    call exception throw  errorNumber  "messageString"  "locationString"
    

    当抛出异常时,它会使用(GOTO) 2>NUL 迭代地弹出 CALL 堆栈,直到找到活动的 TRY/CATCH,然后分支到 CATCH 块并执行该代码。 CATCH 块可以使用一系列异常属性变量:

    • exception.Code - 数字异常代码
    • exception.Msg - 异常消息字符串
    • exception.Loc - 描述引发异常的位置的字符串
    • exception.Stack - 从 CATCH 块(或命令行,如果未捕获)跟踪调用堆栈的字符串,一直到异常源。

    如果异常处理完毕,则应通过call exception clear清除异常,脚本正常运行。如果异常没有被完全处理,那么可以抛出一个新的异常和一个全新的异常。堆栈,或者可以保留旧的堆栈

    call exception rethrow  errorNumber  "messageString"  "locationString"
    

    如果未处理异常,则打印“未处理异常”消息,包括四个异常属性,终止所有批处理,并将控制返回到命令行上下文。

    这是使这一切成为可能的代码 - 完整的文档嵌入在脚本中,并可通过命令行通过 exception help 或 exception /? 获得。

    EXCEPTION.BAT

    ::EXCEPTION.BAT Version 1.4
    ::
    :: Provides exception handling for Windows batch scripts.
    ::
    :: Designed and written by Dave Benham, with important contributions from
    :: DosTips users jeb and siberia-man
    ::
    :: Full documentation is at the bottom of this script
    ::
    :: History:
    ::   v1.4 2016-08-16  Improved detection of command line delayed expansion
    ::                    using an original idea by jeb
    ::   v1.3 2015-12-12  Added paged help option via MORE
    ::   v1.2 2015-07-16  Use COMSPEC instead of OS to detect delayed expansion
    ::   v1.1 2015-07-03  Preserve ! in exception attributes when delayed expansion enabled
    ::   v1.0 2015-06-26  Initial versioned release with embedded documentation
    ::
    @echo off
    if "%~1" equ "/??" goto pagedHelp
    if "%~1" equ "/?" goto help
    if "%~1" equ "" goto help
    shift /1 & goto %1
    
    
    :throw  errCode  errMsg  errLoc
    set "exception.Stack="
    :: Fall through to :rethrow
    
    
    :rethrow  errCode  errMsg  errLoc
    setlocal disableDelayedExpansion
    if not defined exception.Restart set "exception.Stack=[%~1:%~2] %exception.Stack%"
    for /f "delims=" %%1 in ("%~1") do for /f "delims=" %%2 in ("%~2") do for /f "delims=" %%3 in ("%~3") do (
      setlocal enableDelayedExpansion
      for /l %%# in (1 1 10) do for /f "delims=" %%S in (" !exception.Stack!") do (
        (goto) 2>NUL
        setlocal enableDelayedExpansion
        if "!!" equ "" (
          endlocal
          setlocal disableDelayedExpansion
          call set "funcName=%%~0"
          call set "batName=%%~f0"
          if defined exception.Restart (set "exception.Restart=") else call set "exception.Stack=%%funcName%%%%S"
          setlocal EnableDelayedExpansion
          if !exception.Try! == !batName!:!funcName! (
            endlocal
            endlocal
            set "exception.Code=%%1"
            if "!!" equ "" (
              call "%~f0" setDelayed
            ) else (
              set "exception.Msg=%%2"
              set "exception.Loc=%%3"
              set "exception.Stack=%%S"
            )
            set "exception.Try="
            (CALL )
            goto :@Catch
          )
        ) else (
          for %%V in (Code Msg Loc Stack Try Restart) do set "exception.%%V="
          if "^!^" equ "^!" (
            call "%~f0" showDelayed
          ) else (
            echo(
            echo Unhandled batch exception:
            echo   Code = %%1
            echo   Msg  = %%2
            echo   Loc  = %%3
            echo   Stack=%%S
          )
          echo on
          call "%~f0" Kill
        )>&2
      )
      set exception.Restart=1
      setlocal disableDelayedExpansion
      call "%~f0" rethrow %1 %2 %3
    )
    :: Never reaches here
    
    
    :init
    set "@Try=call set exception.Try=%%~f0:%%~0"
    set "@EndTry=set "exception.Try=" & goto :@endCatch"
    :: Fall through to :clear
    
    
    :clear
    for %%V in (Code Msg Loc Stack Restart Try) do set "exception.%%V="
    exit /b
    
    
    :Kill - Cease all processing, ignoring any remaining cached commands
    setlocal disableDelayedExpansion
    if not exist "%temp%\Kill.Yes" call :buildYes
    call :CtrlC <"%temp%\Kill.Yes" 1>nul 2>&1
    :CtrlC
    @cmd /c exit -1073741510
    
    :buildYes - Establish a Yes file for the language used by the OS
    pushd "%temp%"
    set "yes="
    copy nul Kill.Yes >nul
    for /f "delims=(/ tokens=2" %%Y in (
      '"copy /-y nul Kill.Yes <nul"'
    ) do if not defined yes set "yes=%%Y"
    echo %yes%>Kill.Yes
    popd
    exit /b
    
    
    :setDelayed
    setLocal disableDelayedExpansion
    for %%. in (.) do (
      set "v2=%%2"
      set "v3=%%3"
      set "vS=%%S"
    )
    (
      endlocal
      set "exception.Msg=%v2:!=^!%"
      set "exception.Loc=%v3:!=^!%"
      set "exception.Stack=%vS:!=^!%"
    )
    exit /b
    
    
    :showDelayed -
    setLocal disableDelayedExpansion
    for %%. in (.) do (
      set "v2=%%2"
      set "v3=%%3"
      set "vS=%%S"
    )
    for /f "delims=" %%2 in ("%v2:!=^!%") do for /f "delims=" %%3 in ("%v3:!=^!%") do for /f "delims=" %%S in ("%vS:!=^!%") do (
      endlocal
      echo(
      echo Unhandled batch exception:
      echo   Code = %%1
      echo   Msg  = %%2
      echo   Loc  = %%3
      echo   Stack=%%S
    )
    exit /b
    
    
    :-?
    :help
    setlocal disableDelayedExpansion
    for /f "delims=:" %%N in ('findstr /rbn ":::DOCUMENTATION:::" "%~f0"') do set "skip=%%N"
    for /f "skip=%skip% tokens=1* delims=:" %%A in ('findstr /n "^" "%~f0"') do echo(%%B
    exit /b
    
    
    :-??
    :pagedHelp
    setlocal disableDelayedExpansion
    for /f "delims=:" %%N in ('findstr /rbn ":::DOCUMENTATION:::" "%~f0"') do set "skip=%%N"
    ((for /f "skip=%skip% tokens=1* delims=:" %%A in ('findstr /n "^" "%~f0"') do @echo(%%B)|more /e) 2>nul
    exit /b
    
    
    :-v
    :/v
    :version
    echo(
    for /f "delims=:" %%A in ('findstr "^::EXCEPTION.BAT" "%~f0"') do echo %%A
    exit /b
    
    
    :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    :::DOCUMENTATION:::
    
    EXCEPTION.BAT is a pure batch script utility that provides robust exception
    handling within batch scripts. It enables code to be placed in TRY/CATCH blocks.
    If no exception is thrown, then only code within the TRY block is executed.
    If an exception is thrown, the batch CALL stack is popped repeatedly until it
    reaches an active TRY block, at which point control is passed to the associated
    CATCH block and normal processing resumes from that point. Code within a CATCH
    block is ignored unless an exception is thrown.
    
    An exception may be caught in a different script from where it was thrown.
    
    If no active TRY is found after throwing an exception, then an unhandled
    exception message is printed to stderr, all processing is terminated within the
    current CMD shell, and control is returned to the shell command line.
    
    TRY blocks are specified using macros. Obviously the macros must be defined
    before they can be used. The TRY macros are defined using the following CALL
    
        call exception init
    
    Besides defining @Try and @EndTry, the init routine also explicitly clears any
    residual exception that may have been left by prior processing.
    
    A TRY/CATCH block is structured as follows:
    
        %@Try%
          REM any normal code goes here
        %@EndTry%
        :@Catch
          REM exception handling code goes here
        :@EndCatch
    
    - Every TRY must have an associated CATCH.
    
    - TRY/CATCH blocks cannot be nested.
    
    - Any script or :labeled routine that uses TRY/CATCH must have at least one
      SETLOCAL prior to the appearance of the first TRY.
    
    - TRY/CATCH blocks use labels, so they should not be placed within parentheses.
      It can be done, but the parentheses block is broken when control is passed to
      the :@Catch or :@EndCatch label, and the code becomes difficult to interpret
      and maintain.
    
    - Any valid code can be used within a TRY or CATCH block, including CALL, GOTO,
      :labels, and balanced parentheses. However, GOTO cannot be used to leave a
      TRY block. GOTO can only be used within a TRY block if the label appears
      within the same TRY block.
    
    - GOTO must never transfer control from outside TRY/CATCH to within a TRY or
      CATCH block.
    
    - CALL should not be used to call a label within a TRY or CATCH block.
    
    - CALLed routines containing TRY/CATCH must have labels that are unique within
      the script. This is generally good batch programming practice anyway.
      It is OK for different scripts to share :label names.
    
    - If a script or routine recursively CALLs itself and contains TRY/CATCH, then
      it must not throw an exception until after execution of the first %@Try%
    
    Exceptions are thrown by using
    
        call exception throw  Code  Message  Location
    
    where
    
        Code = The numeric code value for the exception.
    
        Message = A description of the exception.
    
        Location = A string that helps identify where the exception occurred.
                   Any value may be used. A good generic value is "%~f0[%~0]",
                   which expands to the full path of the currently executing
                   script, followed by the currently executing routine name
                   within square brackets.
    
    The Message and Location values must be quoted if they contain spaces or poison
    characters like & | < >. The values must not contain additional internal quotes,
    and they must not contain a caret ^.
    
    The following variables will be defined for use by the CATCH block:
    
      exception.Code  = the Code value
      exception.Msg   = the Message value
      exception.Loc   = the Location value
      exception.Stack = traces the call stack from the CATCH block (or command line
                        if not caught), all the way to the exception.
    
    If the exception is not caught, then all four values are printed as part of the
    "unhandled exception" message, and the exception variables are not defined.
    
    A CATCH block should always do ONE of the following at the end:
    
    - If the exception has been handled and processing can continue, then clear the
      exception definition by using
    
        call exception clear
    
      Clear should never be used within a Try block.
    
    - If the exception has not been fully handled, then a new exception should be
      thrown which can be caught by a higher level CATCH. You can throw a new
      exception using the normal THROW, which will clear exception.Stack and any
      higher CATCH will have no awareness of the original exception.
    
      Alternatively, you may rethrow an exception and preserve the exeption stack
      all the way to the original exception:
    
        call exception rethrow  Code  Message  Location
    
      It is your choice as to whether you want to pass the original Code and/or
      Message and/or Location. Either way, the stack will preserve all exceptions
      if rethrow is used.
    
      Rethrow should only be used within a CATCH block.
    
    
    One last restriction - the full path to EXCEPTION.BAT must not include ! or ^.
    
    
    This documentation can be accessed via the following commands
    
        constant stream:   exception /?   OR  exception help
        paged via MORE:    exception /??  OR  exception pagedHelp
    
    The version of this utility can be accessed via
    
        exception /v  OR  exception version
    
    
    EXCEPTION.BAT was designed and written by Dave Benham, with important
    contributions from DosTips users jeb and siberia-man.
    
    Development history can be traced at:
      http://www.dostips.com/forum/viewtopic.php?f=3&t=6497
    

    以下是测试 EXCEPTION.BAT 功能的脚本。该脚本递归调用自身 7 次。每次迭代都有两个 CALL,一个用于演示正常异常传播的 :label,另一个用于演示跨脚本 CALL 的异常传播的脚本。

    从递归调用返回时,如果迭代计数是 3 的倍数(迭代 3 和 6),则会引发异常。

    每个 CALL 都有自己的异常处理程序,通常会报告异常,然后重新抛出修改后的异常。但如果迭代次数为 5,则处理异常并恢复正常处理。

    @echo off
    
    :: Main
    setlocal enableDelayedExpansion
    if not defined @Try call exception init
    
    set /a cnt+=1
    echo Main Iteration %cnt% - Calling :Sub
    %@Try%
    (
      call :Sub
      call echo Main Iteration %cnt% - :Sub returned %%errorlevel%%
    )
    %@EndTry%
    :@Catch
      setlocal enableDelayedExpansion
      echo(
      echo Main Iteration %cnt% - Exception detected:
      echo   Code     = !exception.code!
      echo   Message  = !exception.msg!
      echo   Location = !exception.loc!
      echo Rethrowing modified exception
      echo(
      endlocal
      call exception rethrow -%cnt% "Main Exception^!" "%~f0<%~0>"
    :@EndCatch
    echo Main Iteration %cnt% - Exit
    exit /b %cnt%
    
    
    :Sub
    setlocal
    echo :Sub Iteration %cnt% - Start
    %@Try%
      if %cnt% lss 7 (
        echo :Sub Iteration %cnt% - Calling "%~f0"
        call "%~f0"
        %= Show any non-exception return code (demonstrate ERRORLEVEL is preserved if no exception) =%
        call echo :Sub Iteration %cnt% - testException returned %%errorlevel%%
      )
      %= Throw an exception if the iteration count is a multiple of 3 =%
      set /a "1/(cnt%%3)" 2>nul || (
        echo Throwing exception
        call exception throw -%cnt% "Divide by 0 exception^!" "%~f0<%~0>"
      )
    %@EndTry%
    :@Catch
      setlocal enableDelayedExpansion
      echo(
      echo :Sub Iteration %cnt% - Exception detected:
      echo   Code     = !exception.code!
      echo   Message  = !exception.msg!
      echo   Location = !exception.loc!
      endlocal
      %= Handle the exception if iteration count is a multiple of 5, else rethrow it with new properties =%
      set /a "1/(cnt%%5)" 2>nul && (
        echo Rethrowing modified exception
        echo(
        call exception rethrow -%cnt% ":Sub Exception^!" "%~f0<%~0>"
      ) || (
        call exception clear
        echo Exception handled
        echo(
      )
    :@EndCatch
    echo :Sub Iteration %cnt% - Exit
    exit /b %cnt%
    

    -- 输出--

    Main Iteration 1 - Calling :Sub
    :Sub Iteration 1 - Start
    :Sub Iteration 1 - Calling "C:\test\testException.bat"
    Main Iteration 2 - Calling :Sub
    :Sub Iteration 2 - Start
    :Sub Iteration 2 - Calling "C:\test\testException.bat"
    Main Iteration 3 - Calling :Sub
    :Sub Iteration 3 - Start
    :Sub Iteration 3 - Calling "C:\test\testException.bat"
    Main Iteration 4 - Calling :Sub
    :Sub Iteration 4 - Start
    :Sub Iteration 4 - Calling "C:\test\testException.bat"
    Main Iteration 5 - Calling :Sub
    :Sub Iteration 5 - Start
    :Sub Iteration 5 - Calling "C:\test\testException.bat"
    Main Iteration 6 - Calling :Sub
    :Sub Iteration 6 - Start
    :Sub Iteration 6 - Calling "C:\test\testException.bat"
    Main Iteration 7 - Calling :Sub
    :Sub Iteration 7 - Start
    :Sub Iteration 7 - Exit
    Main Iteration 7 - :Sub returned 7
    Main Iteration 7 - Exit
    :Sub Iteration 6 - testException returned 7
    Throwing exception
    
    :Sub Iteration 6 - Exception detected:
      Code     = -6
      Message  = Divide by 0 exception!
      Location = C:\test\testException.bat<:Sub>
    Rethrowing modified exception
    
    
    Main Iteration 6 - Exception detected:
      Code     = -6
      Message  = :Sub Exception!
      Location = C:\test\testException.bat<:Sub>
    Rethrowing modified exception
    
    
    :Sub Iteration 5 - Exception detected:
      Code     = -6
      Message  = Main Exception!
      Location = C:\test\testException.bat<C:\test\testException.bat>
    Exception handled
    
    :Sub Iteration 5 - Exit
    Main Iteration 5 - :Sub returned 5
    Main Iteration 5 - Exit
    :Sub Iteration 4 - testException returned 5
    :Sub Iteration 4 - Exit
    Main Iteration 4 - :Sub returned 4
    Main Iteration 4 - Exit
    :Sub Iteration 3 - testException returned 4
    Throwing exception
    
    :Sub Iteration 3 - Exception detected:
      Code     = -3
      Message  = Divide by 0 exception!
      Location = C:\test\testException.bat<:Sub>
    Rethrowing modified exception
    
    
    Main Iteration 3 - Exception detected:
      Code     = -3
      Message  = :Sub Exception!
      Location = C:\test\testException.bat<:Sub>
    Rethrowing modified exception
    
    
    :Sub Iteration 2 - Exception detected:
      Code     = -3
      Message  = Main Exception!
      Location = C:\test\testException.bat<C:\test\testException.bat>
    Rethrowing modified exception
    
    
    Main Iteration 2 - Exception detected:
      Code     = -2
      Message  = :Sub Exception!
      Location = C:\test\testException.bat<:Sub>
    Rethrowing modified exception
    
    
    :Sub Iteration 1 - Exception detected:
      Code     = -2
      Message  = Main Exception!
      Location = C:\test\testException.bat<C:\test\testException.bat>
    Rethrowing modified exception
    
    
    Main Iteration 1 - Exception detected:
      Code     = -1
      Message  = :Sub Exception!
      Location = C:\test\testException.bat<:Sub>
    Rethrowing modified exception
    
    
    Unhandled batch exception:
      Code = -1
      Msg  = Main Exception!
      Loc  = C:\test\testException.bat<testException>
      Stack= testException [-1:Main Exception!]  :Sub [-1::Sub Exception!]  C:\test\testException.bat [-2:Main Exception!]  :Sub [-2::Sub Exception!]  C:\test\testException.bat [-3:Main Exception!]  :Sub [-3::Sub Exception!]  [-3:Divide by 0 exception!]
    

    最后,这里有一系列简单的脚本,展示了如何有效地使用异常,即使中间脚本对它们一无所知!

    从一个简单的除法脚本实用程序开始,它将两个数字相除并打印结果:

    divide.bat

    :: divide.bat  numerator  divisor
    @echo off
    setlocal
    set /a result=%1 / %2 2>nul || call exception throw -100 "Division exception" "divide.bat"
    echo %1 / %2 = %result%
    exit /b
    

    请注意脚本在检测到错误时如何抛出异常,但它不采取任何措施来捕获异常。

    现在我将编写一个对批处理异常完全幼稚的除法测试工具。

    testDivide.bat

    @echo off
    for /l %%N in (4 -1 0) do call divide 12 %%N
    echo Finished successfully!
    

    --输出--

    C:\test>testDivide
    12 / 4 = 3
    12 / 3 = 4
    12 / 2 = 6
    12 / 1 = 12
    
    Unhandled batch exception:
      Code = -100
      Msg  = Division exception
      Loc  = divide.bat
      Stack= testDivide divide [-100:Division exception]
    

    注意最终的 ECHO 是如何永远不会执行的,因为没有处理 divide.bat 引发的异常。

    最后,我将编写一个主脚本,调用天真的 testDivide 并正确处理异常:

    ma​​ster.bat

    @echo off
    setlocal
    call exception init
    
    %@Try%
      call testDivide
    %@EndTry%
    :@Catch
      echo %exception.Msg% detected and handled
      call exception clear
    :@EndCatch
    echo Finished Successfully!
    

    -- 输出--

    C:\test>master
    12 / 4 = 3
    12 / 3 = 4
    12 / 2 = 6
    12 / 1 = 12
    Division exception detected and handled
    Finished Successfully!
    

    主脚本能够成功捕获由divide.bat 引发的异常,即使它必须通过testDivide.bat,它对异常一无所知。非常酷:-)

    现在这当然不是解决所有与错误处理相关的问题的灵丹妙药:

    • 内置文档中全面描述了许多语法和代码布局限制。但没有什么太令人震惊的了。

    • 无法自动将所有错误视为异常。所有异常都必须由代码显式抛出。这可能是一件好事,因为错误报告是按惯例处理的——没有严格的规则。有些程序不遵循约定。例如,HELP ValidCommand 返回 ERRORLEVEL 1,按惯例表示错误,而 HELP InvalidCommand 返回 ERRORLEVEL 0,表示成功。

    • 此批处理异常技术无法捕获和处理致命的运行时错误。例如GOTO :NonExistentLabel 仍然会立即终止所有批处理,没有任何机会捕获错误。

    您可以在http://www.dostips.com/forum/viewtopic.php?f=3&t=6497 关注 EXCEPTION.BAT 的发展。任何未来的发展都将在那里发布。我可能不会更新这篇 StackOverflow 帖子。

    【讨论】:

    • 我这个语言书呆子真的很感动,也有点兴奋。但是我的维护程序员正在谴责另一种使批处理文件神秘莫测的方法。
    【解决方案2】:

    好吧,如果“异常处理”一词与其他编程语言的含义相同,我认为答案是:“否”。

    在所有标准编程语言中,术语“异常处理”refers to“在计算过程中发生的异常——需要特殊处理的异常或异常情况”,例如运行时错误,可能以不同的方式进行管理系统执行的标准错误处理。

    例如,in C++:“异常是运行时异常,例如除以零,在您的程序遇到时需要立即处理”。

    .NET Framework 指定:“异常表示应用程序执行期间发生的错误”。

    在Visual Basic 6:“Visual Basic 支持异常(错误)处理,它允许程序在执行期间检测并可能从错误中恢复。”

    JScript 描述表明:“try...catch...finally 语句提供了一种方法来处理给定代码块中可能出现的部分或全部错误,同时仍在运行代码”。

    在所有这些语言中,“异常处理”意味着管理运行时错误,否则会导致程序中断并显示错误消息。这样做的方法是通过这种方式的“try...catch”语句:

    try {
       *any* code 
       that may cause a *run-time ERROR*
    }
    catch (exception) {
       code that allows to *identify the error*
       testing specific values of "exception"
    }
    

    现在与建议的批处理文件代码仿真的区别。

    在批处理文件中无法“管理”运行时错误:所有运行时错误都会导致批处理文件停止执行并显示错误消息。在批处理文件中,与其他语言不同,有几种情况不报告为“错误”,而只是作为命令的可管理结果。例如,如果find 命令找不到搜索字符串,它会返回大于零的错误级别,并且以完全等效的方式,如果set /A 命令产生“运行时错误” (如除以零),它返回大于零的错误级别并正常继续执行。这样,任何程序都可以管理通过标准批处理代码以这种方式报告的任何可能的错误情况,而无需“异常处理”。

    在标准的“try...catch”功能中,任何可能产生任何运行时错误的代码都可以放在“try”部分,没有进一步测试;该异常由系统自动抛出。可以通过“catch”部分中的单独测试来识别导致异常的特定错误。建议的批处理仿真完全不同。在这种情况下,必须在“尝试”部分单独检查每个特定的“错误”情况,以便明确抛出相应的“异常”; “catch”部分还必须处理每个给定的异常。

    这种机制看起来更像是另一种标准编程语言功能:like C++ 语言的“事件管理”机制,Windows Exception Handling Functions 也支持该机制。在此方案中,异常/事件通过 RaiseException 函数显式引发,这导致执行线程跳转到先前通过 AddExceptionHandler 注册的函数。

    请不要误会我的意思。我认为这种方法是一种有价值的工具,可以以简单而强大的方式促进批处理代码中“错误”的管理。但是,我不同意通过标准编程语言的“try...catch”构造使用此功能的提议方案,这给人一种错误的印象,即可以在 Windows 批处理文件中模拟标准异常处理机制以捕获运行时错误。以我的拙见,如果它基于“RegisterExceptionHandler”和“RaiseException”方案,该方法将变得接近标准......

    【讨论】:

      【解决方案3】:

      在很多情况下使用successCmd &amp;&amp; ( failingCmd &amp; (call ) ) || ( excHandlingCmd ) 语法非常好,并且不需要任何其他文件,如下所述:https://stackoverflow.com/a/17085933/1915920

      (虚拟(call ) 仅适用于第二个(最后一个)cmd 失败的情况)

      【讨论】:

      • 您的代码中有一个错误 - 它必须是 (call ) 清除任何错误,而不是 (call) 设置错误。加上第一组括号放错了位置。但即使已修复,这也不能解决原始问题中的目标。
      • @dbenham:非常感谢,已更正。它当然不是一种成熟的异常处理技术,但它以一种简单的方式展示了这一点以及它基本上是如何实现的。我偶然发现这是bat handle error 的第一个谷歌点击。除此之外,我认为复杂的除外。应该避免在批处理文件中处理,因为它已经表明您需要更复杂的技术,这些技术已经在更好的脚本环境(bash/cygwin、groovy、python、javascript/nodejs 等)中可用。如果有这么远,(s)他应该考虑切换语言而不是黑客攻击。
      • 仍然不正确。您的(call ) 仅在failingCmd 成功时执行,但您确实希望它始终在failingCmd 之后执行以保证没有错误。我会改正的。
      • 好的,现在更正了,但这个答案不相关。鉴于我写了您引用的答案,我很清楚这种技术。
      猜你喜欢
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 2021-10-26
      • 2016-09-15
      • 1970-01-01
      • 2021-04-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多