2010-07-12 9 views
14

Tengo un problema con mi archivo por lotes. Construye varios programas automáticamente por hacer algo como esto:Salir de la secuencia de comandos desde dentro de una función

  • establecer algunas banderas de compilación
  • run 'gmake todos'
  • llamada de la función "Comprobar el nivel de error" y si errorlevel 1, salida

para que se vea así:

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

Hay 6 o 7 de ellos (y yo t podría crecer). Así que hice una función para verificar errorlevel en lugar de copiarlo/pegarlo en cada paso. El problema es el siguiente: la comprobación de errores se realiza a través de una función:

: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 

Ahora, cuando se ejecuta, el exit /B 1 simplemente sale de la función, pero no el archivo por lotes.

¿Sabes cómo salir del archivo por lotes completo sin tener que copiar/pegar mi "if errorlevel 1 .." en cada paso?

+2

No se puede. Simplemente reemplace 'call: interactive_check' con' if errorlevel 1 goto error'. No hay mucha diferencia entre copiar y pegar ex o último. :) – atzz

+0

Lo tengo, resolvió el problema con esto y funciona bien, ¡gracias! – Gui13

Respuesta

16

Usando un error de sintaxis fatal, como demuestra Jeb, mata todo el procesamiento por lotes, pero también tiene un efecto secundario desagradable - cambios en el entorno después de SETLOCAL se conservan, a pesar de que se supone que deben ser desechados a través de ENDLOCAL implícita cuando termina el procesamiento por lotes Vea mi publicación DosTips SETLOCAL continues after batch termination! para más información.

Basado en información en Why does a non-interactive batch script think I've pressed control-C?, he descubierto una manera limpia de salir de todas las secuencias de comandos por lotes desde una rutina LLAMADA o secuencia de comandos, y todos los cambios después de SETLOCAL se descartan correctamente.

@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 

Aquí se muestra el resultado de ejecutar el test.bat anterior. Puede ver que la secuencia de comandos nunca volvió de la llamada ExitBatch y la definición de la variable de prueba se ha descartado correctamente una vez que finaliza el procesamiento por lotes.

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

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

C:\test> 

La rutina ExitBatch se puede poner en su propio ExitBatch.bat script y colocado en algún lugar dentro de tu PATH de modo que pueda ser usado convenientemente por cualquier secuencia de comandos por lotes.

@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 

Actualización importante:

Ahora es posible implementar el manejo de lotes con excepción puro robusto. No solo puede lanzar una excepción que puede finalizar todo el procesamiento por lotes desde cualquier nivel CALL, sino que también puede detectar la excepción en un nivel superior, superar el código especial de limpieza de manejo de excepciones y reanudar el procesamiento, o continuar saliendo por otro lanzamiento. Ver Does Windows batch support exception handling? para más información.

+0

Ingenioso. Me alegra que pudieras poner mi descubrimiento a buen trabajo tan rápido; casi compensa el desconcierto que me causó originalmente. :-) –

+0

Bueno, merecía una aceptación, señor. – Gui13

+0

Buena solución, para casos normales más limpio que el mío. – jeb

20

Para una buena solución ver la versión mejorada parte

Puede detener un lote en cualquier momento, también en el interior de llamadas a funciones anidadas.

Solo tiene que crear un error de sintaxis, por ejemplo con un bloque vacío (), para suprimir el mensaje de error, se puede ejecutar en una llamada y el stderr de la llamada se redirige a 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 Versión mejorada: la salida sólo el lote actual, pero no la persona que llama por lotes

Como @dbenham mencionado, hay una nueva técnica para el manejo de excepciones, que también puede ser utilizado para salir solo del lote actual.

@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 
+0

Solución genial :) He resuelto el problema con la sugerencia de atzz, pero en caso de que alguien tropiece aquí, serás la respuesta. – Gui13

+1

Esta solución conservará indebidamente los cambios del entorno después de SETLOCAL. He descubierto una [solución limpia] (http://stackoverflow.com/a/25474648/1012053) que descarta correctamente todos los cambios después de SETLOCAL. – dbenham

1

que sugieren siguiente solución:

@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 

Se ahorra código de error de comando que producen errores. Si desea guardar el código de error a un valor específico, a continuación, modifique la línea:

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

no me gusta solución con error de sintaxis (ver jeb's post), porque se termina también la persona que llama de archivo por lotes actual también.

1

Cuando usa exit /b X para salir de la función establece ERRORLEVEL en el valor de X. A continuación, puede usar ||conditional processing symbol para ejecutar otro comando si ERRORLEVEL es distinto de cero después de call.

@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 

La salida de este script es:

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