¿Cómo hago que un archivo por lotes finalice al encontrar un error?

290

Tengo un archivo por lotes que llama al mismo ejecutable una y otra vez con diferentes parámetros. ¿Cómo hago que termine de inmediato si una de las llamadas devuelve un código de error de cualquier nivel?

Básicamente, quiero el equivalente de MSBuild ContinueOnError=false.

Josh Kodroff
fuente
2
¿Qué shell de comandos ejecutará su script? ¿Command.com de DOS / Win9x o cmd.exe de Win2k +? Dado que eso hace una gran diferencia, ¿podría aclarar eso en una edición de su pregunta?
Mihai Limbășan

Respuestas:

299

Verifique errorlevelen una ifdeclaración y luego exit /b(salga solo del archivo b atch, no del proceso cmd.exe completo) para valores distintos de 0.

same-executable-over-and-over.exe /with different "parameters"
if %errorlevel% neq 0 exit /b %errorlevel%

Si desea que el valor del nivel de error se propague fuera de su archivo por lotes

if %errorlevel% neq 0 exit /b %errorlevel%

pero si esto está dentro de un forse vuelve un poco complicado. Necesitarás algo más como:

setlocal enabledelayedexpansion
for %%f in (C:\Windows\*) do (
    same-executable-over-and-over.exe /with different "parameters"
    if !errorlevel! neq 0 exit /b !errorlevel!
)

Editar: debe verificar el error después de cada comando. No existe un tipo de construcción global "en caso de error" en cmd.exe / command.com por lotes. También actualicé mi código por CodeMonkey , aunque nunca me encontré con un nivel de error negativo en ninguno de mis ataques por lotes en XP o Vista.

PAUSA del sistema
fuente
11
¿Hay alguna forma de indicarlo una vez para todo el archivo? ¿"Por error goto" o algo similar?
Josh Kodroff el
3
+1 para la verificación de nivel de error negativo. Si un script fallaba silenciosamente debido a un resultado negativo.
devstuff
1
Cuidado: la expansión enabledelaye es CRÍTICA y también se requiere para un bloque if / else o cualquier otro
MarcH
1
@ system-PAUSE ¿hay alguna diferencia entre los dos primeros 'if' que se muestran?
simpleuser
1
La expansión retrasada habilitada / deshabilitada o las extensiones de comando (requeridas para neq) habilitadas / deshabilitadas no importan en el uso, if not errorlevel 1 exit /Bcomo lo explica Microsoft en el artículo de soporte Uso de operadores de redirección de comandos y en la salida de ayuda al ejecutarse if /?en una ventana cmd. El nivel de error actual (código de salida) se mantiene al salir del procesamiento del archivo por lotes con exit /B. Nota: exitcon el parámetro /Brequiere extensiones de comando habilitadas, consulte ¿A dónde vuelve GOTO: EOF?
Mofi
252

Agregue || goto :labela cada línea y luego defina a :label.

Por ejemplo, cree este archivo .cmd:

@echo off

echo Starting very complicated batch file...
ping -invalid-arg || goto :error
echo OH noes, this shouldn't have succeeded.
goto :EOF

:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%

Consulte también la pregunta sobre cómo salir de la subrutina de archivo por lotes .

Ave
fuente
44
Es un idioma muy común en la mayoría de los lenguajes de script de shell, y se lee bien: "Haz esto, o esto si falla ..."
Fowl
3
Utilizo un SET para realizar un seguimiento manual del número de línea:command || (SET ErrorLine=102 && goto :error)
SandRock
1
@MarcelValdezOrozco Me parece que esto es para lo que ||se creó en primer lugar. Tal vez no vaya a en particular, pero "intente, haga esto por error" como mencionó Fowl. Mi pregunta es si esto funciona para todos los códigos de salida distintos de cero. Positivos solamente?
jpmc26
3
@ jpmc26 sí, demuéstralo a ti mismo cmd /k exit -1 && echo success || echo fail- las impresiones fallan.
Aves
2
Incluso puede evitar las etiquetas con algo comocommand || exit /b %errorlevel%
Johannes Brodwall
94

El más corto:

command || exit /b

Si lo necesita, puede configurar el código de salida:

command || exit /b 666

Y también puedes iniciar sesión:

command || echo ERROR && exit /b
Benoit Blanchon
fuente
44
¿exit / b por casualidad devuelve el código de salida original que falla?
Frank Schwieterman
55
@FrankSchwieterman, sí, %ERRORLEVEL%no se toca cuando llama exit /b, por lo que se reenvía el código de error
Benoit Blanchon
24

Una actualización menor, debe cambiar las comprobaciones de "if errorlevel 1" a lo siguiente ...

IF %ERRORLEVEL% NEQ 0 

Esto se debe a que en XP puedes obtener números negativos como errores. 0 = sin problemas, cualquier otra cosa es un problema.

Y tenga en cuenta la forma en que DOS maneja las pruebas "IF ERRORLEVEL" Volverá a ser verdadero si el número que está buscando es ese número o superior, por lo que si está buscando números de error específicos, debe comenzar con 255 y trabajar hacia abajo.


fuente
1
Ninguno de los comandos internos y externos estándar de Windows sale nunca con un valor negativo. Microsoft advierte a cualquier escritor de programas que salga con un valor negativo, por ejemplo, en un artículo de MSDN sobre Environment.ExitCode Property . De hecho, siempre debe averiguar qué código de salida utiliza una aplicación en caso de éxito y cuáles en los diversos errores, consulte HTML Help Workshop devuelve el error después de compilar correctamente el archivo .chm para obtener un ejemplo negativo de MS sobre las expectativas del usuario.
Mofi
17

Aquí hay un programa políglota para BASH y Windows CMD que ejecuta una serie de comandos y se cierra si alguno de ellos falla:

#!/bin/bash 2> nul

:; set -o errexit
:; function goto() { return $?; }

command 1 || goto :error

command 2 || goto :error

command 3 || goto :error

:; exit 0
exit /b 0

:error
exit /b %errorlevel%

He usado este tipo de cosas en el pasado para un script de integración continua de múltiples plataformas .

Erik Aronesty
fuente
10

Prefiero la forma OR de comando, ya que los encuentro más legibles (en lugar de tener un if después de cada comando). Sin embargo, la forma ingenua de hacer esto command || exit /b %ERRORLEVEL%está mal .

Esto se debe a que el lote expande las variables cuando se lee una línea por primera vez, en lugar de cuando se están utilizando. Esto significa que si commandfalla la línea de arriba, el archivo por lotes sale correctamente, pero sale con el código de retorno 0, porque ese es el valor de %ERRORLEVEL%al comienzo de la línea. Obviamente, esto no es deseable en nuestro script, por lo que debemos habilitar la expansión retrasada , así:

SETLOCAL EnableDelayedExpansion

command-1 || exit /b !ERRORLEVEL!
command-2 || exit /b !ERRORLEVEL!
command-3 || exit /b !ERRORLEVEL!
command-4 || exit /b !ERRORLEVEL!

Este fragmento ejecutará los comandos 1-4, y si alguno de ellos falla, saldrá con el mismo código de salida que el comando que falló.

Xarn
fuente
1

No siempre podemos depender de ERRORLEVEL, porque muchas veces los programas externos o los scripts por lotes no devuelven códigos de salida.

En ese caso, podemos usar verificaciones genéricas para fallas como esta:

IF EXIST %outfile% (DEL /F %outfile%)
CALL some_script.bat -o %outfile%
IF NOT EXIST %outfile%  (ECHO ERROR & EXIT /b)

Y si el programa genera algo para consolar, también podemos verificarlo.

some_program.exe 2>&1 | FIND "error message here" && (ECHO ERROR & EXIT /b)
some_program.exe 2>&1 | FIND "Done processing." || (ECHO ERROR & EXIT /b)
Amr Ali
fuente
1

No importa cómo lo intenté, el nivel de error siempre permanece en 0 incluso cuando msbuild falló. Entonces construí mi solución:

Compilar proyecto y guardar registro en Build.log

SET Build_Opt=/flp:summary;logfile=Build.log;append=true

msbuild "myproj.csproj" /t:rebuild /p:Configuration=release /fl %Build_Opt%

busque la cadena "0 Error" en el registro de compilación, establezca el resultado en var

FOR /F "tokens=* USEBACKQ" %%F IN (`find /c /i "0 Error" Build.log`) DO (
    SET var=%%F
)
echo %var%

obtener el último carácter, que indica cuántas líneas contiene la cadena de búsqueda

set result=%var:~-1%

echo "%result%"

si no se encuentra la cadena, entonces error> 0, falla la compilación

if "%result%"=="0" ( echo "build failed" )

Esa solución se inspiró en la publicación de Mechaflash en Cómo configurar la salida de comandos como una variable en un archivo por lotes

y https://ss64.com/nt/syntax-substring.html

E.Seven
fuente
1
funciona solo para recuentos lss de diez. Mejor; for /f %%F in ('type build.log^|find /c /i "0 Error") do set result=%%F. Nota: find "0 Error"también encontrará 10 Errors.
Stephan
-2
@echo off

set startbuild=%TIME%

C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe c:\link.xml /flp1:logfile=c:\link\errors.log;errorsonly /flp2:logfile=c:\link\warnings.log;warningsonly || goto :error

copy c:\app_offline.htm "\\lawpccnweb01\d$\websites\OperationsLinkWeb\app_offline.htm"

del \\lawpccnweb01\d$\websites\OperationsLinkWeb\bin\ /Q

echo Start Copy: %TIME%

set copystart=%TIME%

xcopy C:\link\_PublishedWebsites\OperationsLink \\lawpccnweb01\d$\websites\OperationsLinkWeb\ /s /y /d

del \\lawpccnweb01\d$\websites\OperationsLinkWeb\app_offline.htm

echo Started Build: %startbuild%
echo Started Copy: %copystart%
echo Finished Copy: %TIME%

c:\link\warnings.log

:error

c:\link\errors.log
Demican
fuente
44
Agregue más información a su respuesta. Solo un bloque de código no es muy útil.
PoweredByOrange
Además de no haber agregado ningún comentario, su fragmento no parece un buen candidato para explicar una funcionalidad: parece contener muchas cosas que son completamente irrelevantes para la pregunta.
Raúl Salinas-Monteagudo