Jak mogę zakończyć plik wsadowy po napotkaniu błędu?

290

Mam plik wsadowy, który w kółko wywołuje ten sam plik wykonywalny z różnymi parametrami. Jak sprawić, by zakończyło się natychmiast, jeśli jedno z wywołań zwróci kod błędu dowolnego poziomu?

Zasadniczo chcę odpowiednik MSBuild ContinueOnError=false.

Josh Kodroff
źródło
2
Jaka powłoka poleceń uruchomi twój skrypt? DOS / Win9x's command.com lub Win2k +'s cmd.exe? Skoro to robi różnicę, czy mógłbyś wyjaśnić to w edycji swojego pytania?
Mihai Limbășan

Odpowiedzi:

299

Sprawdź, czy errorlevelw ifrachunku, a następnie exit /b(opuścić b plik Atch tylko nie cały proces cmd.exe) do wartości innych niż 0.

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

Jeśli chcesz, aby wartość poziomu błędu propagowała się poza plikiem wsadowym

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

ale jeśli to jest w środku for, staje się to trochę trudne. Potrzebujesz czegoś więcej:

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

Edycja: Musisz sprawdzić błąd po każdym poleceniu. W partii cmd.exe / command.com nie ma globalnego typu „na błąd goto”. Zaktualizowałem również mój kod według CodeMonkey , chociaż nigdy nie spotkałem się z ujemnym poziomem błędu w żadnym z moich hackowań wsadowych na XP lub Vistę.

system PAUZA
źródło
11
Czy istnieje sposób, aby podać to raz dla całego pliku? „On error goto” czy coś podobnego?
Josh Kodroff,
3
+1 za ujemne sprawdzenie poziomu błędu. Skrypt po cichu zawiódł z powodu wyniku negatywnego.
devstuff
1
Ostrożnie: włączone opóźnione rozszerzenie jest KRYTYCZNE i wymagane również dla bloku if / else lub dowolnego innego bloku
MarcH
1
@ system-PAUSE czy jest jakaś różnica między pierwszymi dwoma wyświetlanymi „jeśli”?
simpleuser
1
Opóźnione rozszerzenie włączone / wyłączone lub rozszerzenia poleceń (wymagane dla neq) włączone / wyłączone nie ma znaczenia przy używaniu, if not errorlevel 1 exit /Bjak wyjaśniono w artykule pomocy technicznej Microsoft Korzystanie z operatorów przekierowywania poleceń i pomoc wyjściowa po uruchomieniu if /?w oknie cmd. Bieżący poziom błędu (kod wyjścia) jest utrzymywany przy wychodzeniu z przetwarzania pliku wsadowego za pomocą exit /B. Uwaga: exitjeśli parametr /Bwymaga włączonych rozszerzeń poleceń, zobacz Gdzie wraca GOTO: EOF?
Mofi
252

Dodaj || goto :labeldo każdej linii, a następnie zdefiniuj :label.

Na przykład utwórz ten plik .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%

Zobacz także pytanie dotyczące wychodzenia z podprogramu pliku wsadowego .

Ptactwo
źródło
4
Jest to bardzo powszechny idiom w większości języków skryptowych powłoki i brzmi dobrze: „Zrób to, albo jeśli to się nie powiedzie ...”
Fowl
3
Używam SET, aby ręcznie śledzić numer linii:command || (SET ErrorLine=102 && goto :error)
SandRock,
1
@MarcelValdezOrozco Wydaje mi się, że po to właśnie ||stworzono. Być może nie w szczególności, ale „spróbuj, zrób to z błędem”, jak wspomniała Fowl. Moje pytanie brzmi: czy to działa dla wszystkich niezerowych kodów wyjścia? Tylko pozytywne?
jpmc26,
3
@ jpmc26 tak to robi, udowodnij to sobie - cmd /k exit -1 && echo success || echo fail- drukowanie kończy się niepowodzeniem.
Ptactwo
2
Możesz nawet uniknąć etykiet z czymś takimcommand || exit /b %errorlevel%
Johannes Brodwall
95

Najkrótszy:

command || exit /b

Jeśli potrzebujesz, możesz ustawić kod wyjścia:

command || exit /b 666

Możesz także zalogować:

command || echo ERROR && exit /b
Benoit Blanchon
źródło
4
czy exit / b przypadkowo zwraca pierwotny kod wyjścia, który się nie powiódł?
Frank Schwieterman
5
@FrankSchwieterman, tak, %ERRORLEVEL%pozostaje nietknięty, kiedy dzwonisz exit /b, więc kod błędu jest przekazywany
Benoit Blanchon
24

Jedna drobna aktualizacja, należy zmienić sprawdzanie „jeśli poziom błędu 1” na następujące ...

IF %ERRORLEVEL% NEQ 0 

Wynika to z faktu, że w XP można uzyskać liczby ujemne jako błędy. 0 = brak problemów, wszystko inne stanowi problem.

I pamiętaj o tym, jak DOS obsługuje testy „IF ERRORLEVEL”. Zwróci wartość true, jeśli liczba, którą sprawdzasz, jest tą liczbą lub wyższą, więc jeśli szukasz określonych numerów błędów, musisz zacząć od 255 i pracować dalej.


źródło
1
Żadne ze standardowych wewnętrznych i zewnętrznych poleceń systemu Windows nigdy nie wychodzi z wartością ujemną. Microsoft ostrzega każdego programisty, aby zakończył pracę z wartością ujemną, na przykład w artykule MSDN na temat właściwości Environment.ExitCode . W rzeczywistości zawsze trzeba dowiedzieć się, który kod zakończenia jest używany przez aplikację w przypadku powodzenia, a który w przypadku różnych błędów, zobacz Warsztat pomocy HTML zwraca błąd po pomyślnym skompilowaniu pliku .chm dla negatywnego przykładu MS na oczekiwania użytkownika.
Mofi
17

Oto program polyglot dla BASH i Windows CMD, który uruchamia serię poleceń i kończy działanie, jeśli któreś z nich się nie powiedzie:

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

W przeszłości używałem tego typu rzeczy do tworzenia skryptów ciągłej integracji na wielu platformach .

Erik Aronesty
źródło
10

Wolę formę OR, ponieważ uważam, że są one najbardziej czytelne (w przeciwieństwie do opcji if po każdym poleceniu). Jednak naiwny sposób robienia tego command || exit /b %ERRORLEVEL%jest niewłaściwy .

Wynika to z faktu, że partia rozszerza zmienne, gdy wiersz jest czytany po raz pierwszy, a nie kiedy są używane. Oznacza to, że jeśli commandpowyższy wiersz nie powiedzie się, plik wsadowy kończy się poprawnie, ale wychodzi z kodem powrotu 0, ponieważ taka była wartość %ERRORLEVEL%na początku wiersza. Jest to oczywiście niepożądane w naszym skrypcie, dlatego musimy włączyć opóźnioną ekspansję :

SETLOCAL EnableDelayedExpansion

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

Ten fragment kodu wykona polecenia 1-4, a jeśli którykolwiek z nich zawiedzie, zakończy działanie z tym samym kodem wyjścia, co polecenie nieudane.

Xarn
źródło
1

Nie zawsze możemy polegać na ERRORLEVEL, ponieważ wiele razy programy zewnętrzne lub skrypty wsadowe nie zwracają kodów wyjścia.

W takim przypadku możemy zastosować ogólne kontrole pod kątem takich awarii:

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

A jeśli program wypisze coś na konsolę, możemy to również sprawdzić.

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
źródło
1

Bez względu na to, jak próbowałem, poziom błędu zawsze pozostaje równy 0, nawet jeśli msbuild nie powiódł się. Więc zbudowałem swoje obejście:

Utwórz projekt i zapisz dziennik w Build.log

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

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

wyszukaj ciąg „0 Error” w dzienniku kompilacji, ustaw wynik na var

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

pobierz ostatni znak, który wskazuje, ile wierszy zawiera szukany ciąg

set result=%var:~-1%

echo "%result%"

jeśli ciąg nie zostanie znaleziony, to błąd> 0, kompilacja nie powiodła się

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

To rozwiązanie zostało zainspirowane postem Mechaflasha na stronie Jak ustawić dane wyjściowe poleceń jako zmienne w pliku wsadowym

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

E.Seven
źródło
1
działa tylko dla liczby lss większej niż dziesięć. Lepszy; for /f %%F in ('type build.log^|find /c /i "0 Error") do set result=%%F. Uwaga: find "0 Error"również znajdzie 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
Demian
źródło
4
Dodaj więcej informacji do swojej odpowiedzi. Sam blok kodu nie jest zbyt pomocny.
PoweredByOrange
Oprócz tego, że nie dodał żadnych komentarzy, fragment kodu nie wygląda na dobrego kandydata do wyjaśnienia funkcji: wydaje się, że zawiera wiele rzeczy, które są całkowicie nieistotne dla pytania.
Raúl Salinas-Monteagudo