簡體   English   中英

從函數內部退出批處理腳本

[英]Exit batch script from inside a function

我的批處理文件有問題。 它通過執行以下操作自動構建多個程序:

  • 設置一些編譯標志
  • 運行'gmake all'
  • 調用“檢查錯誤級別”函數,如果錯誤級別為 1,則退出

所以它看起來像這樣:

set FLAG=1
...
gmake all
call :interactive_check
set OTHERFLAG=1
...
gmake all
call :interactive_check

其中有 6 或 7 個(並且它可能會增長)。 所以我做了一個函數來檢查錯誤級別,而不是在每一步都復制/粘貼它。 問題是:錯誤檢查是通過一個函數進行的:

:interactive_check
if errorlevel 1 (
echo.
echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
echo Error in compilation process... exiting
echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
echo.
cd %root_dir%
exit /B 1
) ELSE (
echo.Continuing to next step
)
goto:eof

現在,運行它時, exit /B 1只是退出函數,而不是批處理文件

你知道如何退出完整的批處理文件而不必在每一步都復制/粘貼我的“if errorlevel 1 ..”嗎?

對於一個好的解決方案,請參閱部分改進版本

您可以隨時停止批處理,也可以在嵌套函數調用中停止。

你只需要創建一個語法錯誤,例如用一個空塊()來抑制錯誤消息,它可以在調用中執行,並將調用的 stderr 重定向到 nul。

@echo off
setlocal enabledelayedexpansion

rem Do something
call :interactive_check

rem Do something
call :interactive_check

goto :eof

::::::::::::::::::::::::::
:interactive_check
if errorlevel 1 (
    echo.
    echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
    echo Error in compilation process... exiting
    echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
    call :halt 1
) ELSE (
    echo.Continuing to next step
)
goto :eof

:: Sets the errorlevel and stops the batch immediately
:halt
call :__SetErrorLevel %1
call :__ErrorExit 2> nul
goto :eof

:__ErrorExit
rem Creates a syntax error, stops immediately
() 
goto :eof

:__SetErrorLevel
exit /b %time:~-2%
goto :eof

2017-04-09 改進版本:只退出當前批次,不退出調用者批次

正如@dbenham 提到的,有一種用於異常處理的新技術,也可用於僅退出當前批處理。

@echo off
echo Do something, detecting some fatal error
call :ExitBatch 3
exit /b

:ExitBatch [errorcode] - Exits only the current batch file, regardless how many CALLs
set _errLevel=%1
REM *** Remove all calls from the current batch from the call stack
:popStack
(
    (goto) 2>nul
    setlocal DisableDelayedExpansion    
    call set "caller=%%~0"
    call set _caller=%%caller:~0,1%%
    call set _caller=%%_caller::=%%
    if not defined _caller (
        REM callType = func
        rem set _errLevel=%_errLevel%
        goto :popStack
    )   
    (goto) 2>nul
    endlocal
    cmd /c "exit /b %_errLevel%"
)
echo NEVER REACHED
exit /b

正如 jeb 所演示的那樣,使用致命語法錯誤確實會終止所有批處理,但它也有一個令人討厭的副作用 - 保留 SETLOCAL 后的環境更改,即使它們應該在批處理結束時通過隱式 ENDLOCAL 丟棄。 請參閱我的 DosTips 帖子SETLOCAL continues after batch termination! 想要查詢更多的信息。

基於Why does a non-interactive batch script think I've pressed control-C? 中的信息 ,我發現了一種從 CALLed 例程或腳本中退出所有批處理腳本的干凈方法,並且正確丟棄了 SETLOCAL 之后的所有更改。

@echo off
setlocal
set test=AFTER main SETLOCAL
call :sub
echo returning from main  NEVER REACHED
exit /b

:sub
setlocal
set test=AFTER sub SETLOCAL
set test
call :ExitBatch
echo returning from sub2 NEVER REACHED
exit /b


:ExitBatch - Cleanly exit batch processing, regardless how many CALLs
if not exist "%temp%\ExitBatchYes.txt" call :buildYes
call :CtrlC <"%temp%\ExitBatchYes.txt" 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 ExitBatchYes.txt >nul
for /f "delims=(/ tokens=2" %%Y in (
  '"copy /-y nul ExitBatchYes.txt <nul"'
) do if not defined yes set "yes=%%Y"
echo %yes%>ExitBatchYes.txt
popd
exit /b

這是運行上述 test.bat 的示例輸出。 您可以看到腳本從未從 :ExitBatch 調用返回,並且一旦批處理終止,測試變量定義就被正確丟棄。

C:\test>test.bat
test=AFTER sub SETLOCAL

C:\test>set test
Environment variable test not defined

C:\test>

The:ExitBatch 例程可以放入它自己的 ExitBatch.bat 腳本中,並放置在您的 PATH 中的某個位置,以便任何批處理腳本都可以方便地使用它。

@echo off
:ExitBatch - Cleanly exit batch processing, regardless how many CALLs
if not exist "%temp%\ExitBatchYes.txt" call :buildYes
call :CtrlC <"%temp%\ExitBatchYes.txt" 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 ExitBatchYes.txt >nul
for /f "delims=(/ tokens=2" %%Y in (
  '"copy /-y nul ExitBatchYes.txt <nul"'
) do if not defined yes set "yes=%%Y"
echo %yes%>ExitBatchYes.txt
popd
exit /b

重要更新:

現在可以使用純批處理實現強大的異常處理。 不僅可以從任何CALL級別拋出一個可以終止所有批處理的異常,還可以在更高級別捕獲異常,執行特殊的異常處理清理代碼,並恢復處理,或者通過另一個throw繼續退出! 請參閱Windows 批處理是否支持異常處理? 獲取更多信息。

當您使用exit /b X退出函數時,它會將ERRORLEVEL設置為X的值。 然后你可以使用|| 如果callERRORLEVEL非零,則條件處理符號執行另一個命令。

@echo off
setlocal
call :myfunction PASS || goto :eof
call :myfunction FAIL || goto :eof
echo Execution never gets here
goto :eof

:myfunction
    if "%1"=="FAIL" ( 
        echo myfunction: got a FAIL. Will exit.
        exit /b 1
    )
    echo myfunction: Everything is good.
    exit /b 0

該腳本的輸出是:

myfunction: Everything is good.
myfunction: got a FAIL. Will exit.

我建議以下解決方案:

@echo off

:: comment next line if you want to export any local variables in caller environment
setlocal

set FLAG=1
rem Do something
call :interactive_check

set FLAG2=2
rem Do something
call :interactive_check

goto :eof

:interactive_check
if errorlevel 1 (
    echo.
    echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
    echo Error in compilation process... exiting
    echo /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\/!\
    (goto) 2>nul & endlocal & exit /b %ERRORLEVEL%
) else (
    echo Continuing to next step
)
goto :eof

它保存產生錯誤的命令的錯誤代碼。 如果要將錯誤代碼保存為特定值,請修改以下行:

(goto) 2>nul & endlocal & exit /b YOUR_EXITCODE_HERE

我不喜歡語法錯誤的解決方案(參見jeb 的帖子),因為它也終止了當前批處理文件的調用者。

超過堆棧限制將立即終止所有批處理:

@echo off

echo I am %0, calling :subroutine
call:subroutine

NEVER PARSED
exit /b


:subroutine
echo I am %0, calling :recurse
echo Exiting batch script IMMEDIATELY

cmd /c exit /b %=EXIT CODE HERE=%
2>nul call:recurse

NEVER PARSED
exit /b


:recurse - Aborts all batch processing, regardless of CALL depth
call:recurse

NEVER PARSED
exit /b

大多數解決方案僅適用於 2 個堆棧級別或屏幕上的錯誤消息。
所以我想出了兩個解決方案,我認為它們都比我在這里看到的要好。

沒有 SETLOCAL 和 ERRORLEVEL 的復雜腳本的解決方案

此解決方案適用於:

  • 您有太多遞歸或條件函數,不知道程序何時終止
  • 你不關心留在環境中的變量
  • 你不關心 errorLevel
CALL :leave 2>nul
:leave
()
GOTO :eof

解釋:它會call:leave將它放在堆棧級別,偽參數2>nul會將錯誤消息發送到 void,從而抑制該代碼塊的錯誤。 然后()將生成一個致命錯誤,該錯誤將殺死整個程序。

您甚至可以通過將 SET 設置為空白或在CALL:leave之前將 ENDLOCAL 放在 main 中來刪除變量。 但在一個復雜的程序中,您不知道何時執行此操作。 但在這種情況下,我想出了一個更好的解決方案......

我知道很多人在這里看到過這段代碼: (GOTO) 2>nul & ENDLOCAL & EXIT /b %yourCode% ,但正如我所說,它只是終止當前函數並終止調用它的前一個函數。 如果堆棧上有多個函數相互調用,它就不起作用。 所以你可以強制出現幾個錯誤:

(GOTO) 2>nul & (GOTO) 2>nul & (GOTO) 2>nul & ENDLOCAL & EXIT /b

但是,您不知道要執行多少次,如果執行過度,您甚至可以終止:main 並執行 exit /b 以關閉控制台。 然后我有

具有 SETLOCAL 和 ERRORLEVEL 的復雜腳本的解決方案

此解決方案適用於:

  • 您有太多遞歸或條件函數,不知道程序何時終止
  • 你不想要剩余變量
  • 你檢查 errorLevel (可選)
SET dummy=iAmDefined
FOR /l %%i in (0, 1, 1000) do (
  IF defined dummy (
    (GOTO) 2>nul & IF NOT defined dummy (CMD /c EXIT /b %yourExitCode%)
  )
)

它不會運行一千次,但只會根據需要運行多次,直到 until.dummy。 錯過作業。 你不需要啟用 EnableDelayedExpansion 除非你想調試 !dummy! 回聲。

例子:

@ECHO off
SETLOCAL EnableDelayedExpansion
SET dummy=iAmDefined

CALL :func3

:func1
ECHO i'm in func1
CALL :leave 2>nul
::GOTO :eof

:func2
ECHO i'm in func2
CALL: func1
::GOTO :eof

:func3
ECHO i'm in func3
CALL: func2
::GOTO :eof

:leave
ECHO ===leaving===
FOR /l %%i in (1, 1, 1000) of (
  IF defined dummy (
    (GOTO) & ECHO %%i !dummy! & IF NOT defined dummy (CMD /c EXIT /b 555)
  )
)
::GOTO :eof
::ENDLOCAL

退出 e 說明:

             -- 1 call  (called func3)
i'm in func3 -- 2 calls (called func2)
i'm in func2 -- 3 calls (called func1)
i'm in func1 -- 4 calls (called leave)
===leaving===
1 iAmDefined -- rollback :leave
2 iAmDefined -- rollback :func3
3 iAmDefined -- rollback :func2
4 iAmDefined -- rollback :func1
5 !dummy!    -- rollback :main before unset dummy
                new CMD and exit after unset dummy

在第 5 個虛擬機處,它仍然設置在 (GOTO) 之前,但 ECHO 設置在之后。 The:EOF 被注釋以說明這些 gotoes 沒有被使用

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM