Как заставить пакетный файл завершить работу при обнаружении ошибки?

290

У меня есть командный файл, который снова и снова вызывает один и тот же исполняемый файл с разными параметрами. Как сделать так, чтобы он немедленно завершился, если один из вызовов возвращает код ошибки любого уровня?

По сути, я хочу эквивалент MSBuild ContinueOnError=false.

Джош Кодрофф
источник
2
Какая командная оболочка будет запускать ваш скрипт? Command.com для DOS / Win9x или cmd.exe для Win2k +? Поскольку это имеет огромное значение, не могли бы вы уточнить это в редактировании вашего вопроса?
Михай Лимбакан

Ответы:

300

Проверьте errorlevelв ifзаявлении, а затем exit /b(выход из б файла ATCH только, а не весь процесс cmd.exe) для отличных 0 значений.

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

Если вы хотите, чтобы значение уровня ошибки распространялось за пределы вашего пакетного файла

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

но если это внутри, forэто становится немного сложнее. Вам понадобится что-то похожее на:

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

Изменить: Вы должны проверить ошибку после каждой команды. В пакете cmd.exe / command.com нет общего типа конструкции «при ошибке». Я также обновил свой код для CodeMonkey , хотя я никогда не сталкивался с отрицательным уровнем ошибок ни в одном из моих пакетных операций на XP или Vista.

системная пауза
источник
11
Есть ли способ заявить это один раз для всего файла? «По ошибке goto» или что-то подобное?
Джош Кодрофф
3
+1 за отрицательную проверку уровня ошибки. Был сценарий молча сбой из-за отрицательного результата.
Devstuff
1
Осторожно: расширение enabledelayedexsion является КРИТИЧЕСКИМ и также требуется для if / else или любого другого блока
MarcH
1
@ system-PAUSE есть ли разница между первыми двумя показанными словами "если"?
simpleuser
1
Задержка расширения включена / отключена, или расширения команды (обязательные для neq) включены / отключены, не имеет значения при использовании, if not errorlevel 1 exit /Bкак объяснено Microsoft в статье поддержки Использование операторов перенаправления команд и при выводе справки при запуске if /?в окне cmd. Текущий уровень ошибки (код выхода) сохраняется при выходе из обработки командного файла с помощью exit /B. Примечание: для exitпараметра /Bтребуются включенные расширения команд, см. Куда возвращается GOTO: EOF?
Мофи
252

Добавьте || goto :labelк каждой строке, а затем определите :label.

Например, создайте этот файл .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%

Смотрите также вопрос о выходе из подпрограммы пакетного файла .

живность
источник
4
Это очень распространенная идиома в большинстве языков сценариев оболочки, и она хорошо читается: «Делай то или это, если не получится ..»
Фаул
3
Я использую SET, чтобы вручную отслеживать номер строки:command || (SET ErrorLine=102 && goto :error)
SandRock
1
@MarcelValdezOrozco Мне кажется, что это то, что ||было создано в первую очередь. Может быть, не goto конкретно, но "попробуй, сделай это по ошибке", как упоминал Фаул. Мой вопрос: это работает для всех ненулевых кодов выхода? Только положительные?
jpmc26
3
@ jpmc26 да, это так, докажи это себе cmd /k exit -1 && echo success || echo fail- печать не удалась .
мясо птицы
2
Вы даже можете избежать ярлыков с чем-то вродеcommand || exit /b %errorlevel%
Йоханнес Бродуолл
95

Кратчайший:

command || exit /b

Если вам нужно, вы можете установить код выхода:

command || exit /b 666

И вы также можете войти:

command || echo ERROR && exit /b
Бенуа Бланшон
источник
4
может ли exit / b случайно вернуть исходный ошибочный код выхода?
Фрэнк Швитерман
5
@FrankSchwieterman, да, %ERRORLEVEL%остается нетронутым, когда вы звоните exit /b, поэтому код ошибки перенаправляется
Бенуа Бланшон
24

Одно незначительное обновление, вы должны изменить проверки «if errorlevel 1» на следующее ...

IF %ERRORLEVEL% NEQ 0 

Это потому, что в XP вы можете получить отрицательные числа как ошибки. 0 = нет проблем, все остальное - проблема.

И помните, как DOS обрабатывает тесты «IF ERRORLEVEL». Он вернет true, если номер, который вы проверяете, это число или больше, поэтому, если вы ищете конкретные номера ошибок, вам нужно начать с 255 и продолжить работу.


источник
1
Ни одна из стандартных внутренних и внешних команд Windows никогда не завершается с отрицательным значением. Microsoft предупреждает любого разработчика программы о выходе с отрицательным значением, например, в статье MSDN о свойстве Environment.ExitCode . Фактически всегда нужно выяснить, какой код завершения используется приложением в случае успеха, а какой - при различных ошибках, см. HTML Help Workshop возвращает ошибку после успешно скомпилированного файла .chm для отрицательного примера MS по ожиданиям пользователей.
Мофи
17

Вот программа polyglot для BASH и Windows CMD, которая выполняет серию команд и завершает свою работу в случае сбоя любой из них:

#!/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%

Я использовал этот тип вещей в прошлом для сценария непрерывной интеграции для нескольких платформ .

Эрик Аронесты
источник
10

Я предпочитаю форму команды ИЛИ, так как считаю ее наиболее читаемой (в отличие от наличия if после каждой команды). Однако наивно способ сделать это, command || exit /b %ERRORLEVEL%является неправильным .

Это связано с тем, что пакетное развертывание переменных происходит при первом чтении строки, а не при их использовании. Это означает, что если commandв приведенной выше строке произойдет сбой, пакетный файл завершится правильно, но завершится с кодом возврата 0, потому что это значение, которое %ERRORLEVEL%было в начале строки. Очевидно, что это нежелательно в нашем скрипте, поэтому мы должны включить отложенное расширение , например:

SETLOCAL EnableDelayedExpansion

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

Этот фрагмент будет выполнять команды 1-4, и в случае сбоя любой из них он завершится с тем же кодом завершения, что и неудачная команда.

Зарн
источник
1

Мы не всегда можем зависеть от ERRORLEVEL, потому что много раз внешние программы или пакетные сценарии не возвращают коды завершения.

В этом случае мы можем использовать общие проверки для сбоев, как это:

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

И если программа выводит что-то на консоль, мы можем это проверить.

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)
Амр Али
источник
1

Независимо от того, как я пытался, уровень ошибки всегда остается 0, даже если msbuild не удалось. Итак, я построил мой обходной путь:

Создайте проект и сохраните журнал в Build.log

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

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

искать строку «0 Error» в журнале сборки, установить результат в var

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

получить последний символ, который указывает, сколько строк содержит строку поиска

set result=%var:~-1%

echo "%result%"

если строка не найдена, то ошибка> 0, сборка не удалась

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

Это решение было вдохновлено постом Mechaflash в разделе Как настроить вывод команд в качестве переменной в командном файле

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

E.Seven
источник
1
работает только на счетах менее десяти. Лучше; for /f %%F in ('type build.log^|find /c /i "0 Error") do set result=%%F, Примечание: find "0 Error"также найдете 10 Errors.
Стефан
-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
источник
4
Пожалуйста, добавьте больше информации к вашему ответу. Просто блок кода не очень полезен.
PoweredByOrange
Помимо отсутствия каких-либо комментариев, ваш фрагмент не выглядит хорошим кандидатом для объяснения функциональности: он содержит много вещей, которые совершенно не имеют отношения к вопросу.
Рауль Салинас-Монтеагудо