Batch-Datei verliert Fehlerlevel

8

Betrachten Sie die folgende Fledermaus, test.bat (PC01 ist aus):

%Vor%

Wenn ich diesen Schläger von einer Kommando-Shell aus starte:

%Vor%

Die Ausgabe ist nur 55. Nein 99. Es gibt einen Fehlerlevel, aber die '||' Betreiber hat es nicht gesehen.

Wenn ich diesen Schläger mit cmd / c laufen lasse -

%Vor%

Die Ausgabe ist leer. Errorlevel ist 0.

Wenn ich die '|| goto: eof ', alles funktioniert so, wie man es vorhergesagt hätte - d. h. der Output wäre 99 55

Weiß jemand, warum dieses halbstarke ERRORLEVEL-Verhalten auftritt?

    
Patrick 21.01.2016, 23:17
quelle

3 Antworten

13

In den meisten Fällen ist || die zuverlässigste Methode, einen Fehler zu erkennen . Aber Sie sind über einen der seltenen Fälle gestolpert, in denen ERRORLEVEL funktioniert, aber || nicht.

Das Problem rührt von der Tatsache her, dass Ihr Fehler innerhalb eines Batch-Skripts auftritt und || auf den Rückkehrcode des zuletzt ausgeführten Befehls reagiert. Sie denken an test.bat als einen einzelnen "Befehl", aber eigentlich ist es eine Folge von Befehlen. Der letzte im Skript ausgeführte Befehl lautet GOTO :EOF und wurde erfolgreich ausgeführt. Dein test.bat||echo 99 reagiert also auf den Erfolg von GOTO :EOF .

Wenn Sie die ||GOTO :EOF aus dem Skript entfernen, sieht Ihre test.bat||echo99 das Ergebnis der fehlgeschlagenen mkdir . Wenn Sie jedoch am Ende von test.bat einen REM -Befehl hinzufügen, antwortet test.bat||echo 99 auf den Erfolg von REM , und der Fehler wird wieder maskiert.

Der ERRORLEVEL ist nach test.bat||echo 99 immer noch nicht Null, weil Befehle wie GOTO und REM bei Erfolg keinen vorherigen ERRORLEVEL ungleich Null löschen. Dies ist einer von vielen Beweisen dafür, dass ERRORLEVEL und der Rückkehrcode nicht identisch sind. Es wird definitiv verwirrend.

Sie können test.bat als Einheitsbefehl behandeln und das gewünschte Verhalten mit CALL erreichen.

%Vor%

Dies funktioniert, weil der Befehl CALL die Kontrolle vorübergehend an das aufgerufene Skript überträgt. Wenn das Skript beendet wird, wird die Steuerung an den Befehl CALL zurückgegeben und das aktuelle ERRORLEVEL zurückgegeben. So antwortet ||echo 99 auf den Fehler, der vom Befehl CALL selbst zurückgegeben wurde, nicht auf den letzten Befehl innerhalb des Skripts.

Nun zum Problem CMD /C .

Der von CMD /C zurückgegebene Rückgabecode ist der Rückgabecode des zuletzt ausgeführten Befehls.

Das funktioniert:

%Vor%

, weil CMD /C das von der CALL -Anweisung

zurückgegebene ERRORLEVEL zurückgibt

Aber das scheitert ganz:

%Vor%

Ohne CALL gibt CMD /C den Rückkehrcode des zuletzt ausgeführten Befehls zurück, der GOTO :EOF ist. Das CMD /C setzt auch den ERRORLEVEL auf denselben Rückgabecode, so dass jetzt kein Hinweis darauf mehr besteht, dass jemals ein Fehler im Skript aufgetreten ist.

Und in das Kaninchenloch gehen wir

R. L. H., in seiner Antwort und in seinen Kommentaren zu meiner Antwort, ist besorgt, dass || manchmal den ERRORLEVEL löscht. Er liefert Beweise, die seine Schlussfolgerung zu untermauern scheinen. Aber die Situation ist nicht so einfach, und es stellt sich heraus, dass || die zuverlässigste (aber immer noch nicht perfekte) Möglichkeit ist, Fehler zu erkennen.

Wie bereits erwähnt, ist der Rückkehrcode, den alle externen Befehle beim Beenden zurückgeben, nicht derselbe wie der cmd.exe ERRORLEVEL.

Der ERRORLEVEL ist ein Zustand, der in der cmd.exe-Sitzung selbst beibehalten wird und sich von Rückkehrcodes vollständig unterscheidet.

Dies ist sogar in der Definition des exitCodes innerhalb der EXIT-Hilfe dokumentiert ( help exit oder exit /? )

%Vor%

Wenn ein externer Befehl von CMD.EXE ausgeführt wird, erkennt er den Rückgabecode des Executable und setzt den ERRORLEVEL auf Übereinstimmung. Beachten Sie, dass es nur eine Konvention ist, dass 0 Erfolg bedeutet und nicht Null bedeutet Fehler. Einige externe Befehle folgen möglicherweise nicht dieser Konvention. Zum Beispiel folgt der Befehl HELP (help.exe) nicht der Konvention - er gibt 0 zurück, wenn Sie einen ungültigen Befehl wie in help bogus angeben, gibt aber 1 zurück, wenn Sie um Hilfe zu einem gültigen Befehl bitten, wie in help rem .

Der Operator || löscht niemals den ERRORLEVEL, wenn ein externer Befehl ausgeführt wird. Der Prozessbeendigungscode wird erkannt und löscht || , wenn er nicht null ist, und der ERRORLEVEL wird weiterhin mit dem Beendigungscode übereinstimmen. Das heißt, die Befehle, die nach && und / oder || erscheinen, können das ERRORLEVEL verändern, also muss man vorsichtig sein.

Aber es gibt noch viele andere Situationen außer den externen Befehlen, bei denen sich Entwickler Gedanken über Erfolg / Fehler und Rückkehrcodes / ERRORLEVELs machen.

  • Ausführung von internen Befehlen
  • Umleitungsoperatoren < , > und >>
  • Ausführung von Batch-Skripten
  • fehlgeschlagene Ausführung ungültiger Befehle

Leider ist CMD.EXE überhaupt nicht konsistent in der Behandlung von Fehlerbedingungen für diese Situationen. CMD.EXE hat mehrere interne Punkte, wo es Fehler erkennen muss, vermutlich durch eine Form von internem Rückgabecode, der nicht unbedingt der ERRORLEVEL ist, und an jedem dieser Punkte ist CMD.EXE in der Lage, den ERRORLEVEL abhängig von dem zu setzen, was er findet .

Beachten Sie bei meinen unten angegebenen Testfällen, dass (call ) mit einem Leerzeichen eine arkane Syntax ist, die den ERRORLEVEL vor jedem Test auf 0 setzt.Später werde ich auch (call) ohne Leerzeichen verwenden, um ERRORLEVEL auf 1 zu setzen

Beachten Sie auch, dass die verzögerte Erweiterung in meiner Befehlssitzung aktiviert wurde, indem Sie vor dem Ausführen meiner Tests das Programm ' cmd /v: on ' verwenden

Die überwiegende Mehrheit der internen Befehle setzt den ERRORLEVEL bei einem Fehler auf einen Wert ungleich Null, und die Fehlerbedingung löst auch || aus. Das || löscht oder ändert das ERRORLEVEL in diesen Fällen nie. Hier sind ein paar Beispiele:

%Vor%

Dann gibt es mindestens einen Befehl RD (möglicherweise mehr) sowie die Umleitungsoperatoren, die || bei einem Fehler auslösen, aber nicht ERRORLEVEL, wenn || nicht verwendet wird.

%Vor%

Siehe Batch: Exit-Code für "rd" ist auch 0 bei Fehler und Dateiumleitung in Windows und% errorlevel% für weitere Informationen.

Ich kenne einen internen Befehl (es können andere sein) plus grundlegende fehlgeschlagene E / A-Operationen, die Fehlermeldungen an stderr ausgeben können, aber sie feuern weder || , noch setzen sie einen ERRORLEVEL ungleich Null / p>

Der Befehl DEL kann einen Fehler ausgeben, wenn die Datei schreibgeschützt ist oder nicht existiert, aber || nicht auslöst oder ERRORLEVEL nicht auf Null gesetzt ist

%Vor%

Weitere Informationen zu DEL-Fehlern finden Sie Ссылка .

Wenn stdout erfolgreich in eine Datei auf einem USB-Gerät umgeleitet wurde, das Gerät jedoch entfernt wird, bevor ein Befehl wie ECHO versucht, auf das Gerät zu schreiben, schlägt ECHO mit einem Fehler fehl Nachricht an stderr, aber || wird nicht ausgelöst und ERRORLEVEL wird nicht auf ungleich Null gesetzt. Weitere Informationen finden Sie Ссылка .

Dann haben wir den Fall, in dem ein Batch-Script ausgeführt wird - das eigentliche Thema der OP-Frage. Ohne CALL , Der Operator || antwortet auf den letzten im Skript ausgeführten Befehl. Mit CALL reagiert der Operator || auf den Wert, der vom Befehl CALL zurückgegeben wird. Dies ist der letzte ERRORLEVEL, der bei der Stapelbeendigung existiert.

Schließlich haben wir den Fall, dass R.L.H. Berichte, in denen ein ungültiger Befehl normalerweise als ERRORLEVEL 9009 gemeldet wird, aber als ERRORLEVEL 1, wenn || verwendet wird.

%Vor%

Ich kann das nicht beweisen, aber ich vermute, dass die Erkennung von Befehlsfehlern und die Einstellung von ERRORLEVEL auf 9009 sehr spät im Befehlsausführungsprozess erfolgt. Ich nehme an, dass || die Fehlererkennung abfängt, bevor 9009 gesetzt wird, und setzt sie stattdessen auf 1. Ich glaube also nicht, dass || den Fehler 9009 löscht, sondern es ist ein alternativer Weg, mit dem der Fehler behandelt und gesetzt wird.

Unabhängig davon ist mir keine andere Situation bekannt, in der ein ERRORLEVEL-Ergebnis ungleich Null abhängig davon ist, ob || verwendet wurde oder nicht.

Damit wird sichergestellt, was passiert, wenn ein Befehl fehlschlägt. Aber was ist, wenn ein interner Befehl erfolgreich ist? Leider ist CMD.EXE noch weniger konsistent als bei Fehlern. Es hängt vom Befehl ab und hängt auch davon ab, ob es von der Eingabeaufforderung, von einem Stapel-Skript mit der Erweiterung .bat oder von einem Stapel-Skript mit der Erweiterung .cmd ausgeführt wird.

Ich stütze die gesamte folgende Diskussion auf Windows 10-Verhalten. Ich bezweifle, dass es Unterschiede zu früheren Windows-Versionen gibt, die cmd.exe verwenden, aber es ist möglich.

Die folgenden Befehle löschen den ERRORLEVEL immer bei Erfolg, unabhängig vom Kontext:

  • CALL: Löscht ERRORLEVEL, wenn der Befehl CALLed es nicht anders setzt.
    Beispiel: call echo OK
  • CD
  • CHDIR
  • FARBE
  • KOPIEREN
  • DATUM
  • DEL: Löscht immer ERRORLEVEL, selbst wenn die DEL fehlschlägt
  • DIR
  • ERASE: Löscht immer ERRORLEVEL, auch wenn ERASE fehlschlägt
  • MD
  • MKDIR
  • Mlinklink
  • BEWEGEN
  • PUSHD
  • REN
  • UMBENENNEN
  • SETLOCAL
  • ZEIT
  • TYP
  • VER
  • ÜBERPRÜFEN
  • VOL

Die nächste Gruppe von Befehlen löscht den ERRORLEVEL bei Erfolg niemals auf 0, unabhängig vom Kontext, sondern behält stattdessen einen vorhandenen Wert ungleich ERRORLEVEL bei:

  • BREAK
  • CLS
  • ECHO
  • ENDLOCAL
  • EXIT: Offensichtlich löscht EXIT /B 0 das ERRORLEVEL, aber EXIT /B ohne Wert behält das vorherige ERRORLEVEL bei.
  • FÜR
  • GOTO
  • IF
  • SCHLÜSSEL
  • PAUSE
  • POPD
  • RD
  • REM
  • RMDIR
  • UMSCHALTEN
  • START
  • TITEL

Und dann gibt es diese Befehle, die ERRORLEVEL bei Erfolg nicht löschen, wenn sie von der Befehlszeile oder innerhalb eines Skripts mit einer .bat -Erweiterung ausgegeben werden, aber den ERRORLEVEL auf 0 zurücksetzen, wenn sie von einem Skript mit einem .cmd ausgegeben werden. Erweiterung. Siehe Ссылка und Ссылка für weitere Informationen.

  • ASSOC
  • DPATH
  • FTYPE
  • PFAD
  • PROMPT
  • SET

Unabhängig von einem beliebigen ERRORLEVEL-Wert erkennt der Operator && , ob der vorherige Befehl erfolgreich war und führt nur die nachfolgenden Befehle aus, falls dies der Fall war. Der Operator && ignoriert den Wert von ERRORLEVEL und ändert ihn niemals.

Hier sind zwei Beispiele, die zeigen, dass && immer dann feuert, wenn der vorherige Befehl erfolgreich war, auch wenn ERRORLEVEL ungleich Null ist. Der CD-Befehl ist ein Beispiel, bei dem der Befehl jedes vorherige ERRORLEVEL löscht, und der ECHO-Befehl ist ein Beispiel, bei dem der Befehl das vorherige ERRORLEVEL nicht löscht. Hinweis: Ich verwende (call) , um ERRORLEVEL auf 1 zu setzen, bevor der Befehl erfolgreich ausgeführt wird.

%Vor%

In all meinen Codebeispielen für die Fehlererkennung habe ich mich auf die Tatsache verlassen, dass ECHO niemals ein zuvor existierendes ERRORLEVEL ungleich Null löscht. Das folgende Skript ist ein Beispiel dafür, was passieren kann, wenn andere Befehle nach && oder || verwendet werden.

%Vor%

Hier ist die Ausgabe, wenn das Skript eine .bat Erweiterung hat:

%Vor%

Und hier ist die Ausgabe, wenn das Skript eine .cmd Erweiterung hat:

%Vor%

Denken Sie daran, dass jeder ausgeführte Befehl das ERRORLEVEL ändern kann. Auch wenn && und || die zuverlässigsten Methoden sind, Erfolg oder Fehlschlagen eines Befehls zu erkennen, muss man vorsichtig sein, welche Befehle nach diesen Operatoren verwendet werden, wenn man sich um den ERRORLEVEL-Wert kümmert.

Und jetzt ist es Zeit, aus diesem stinkenden Kaninchenloch zu klettern und frische Luft zu holen!

Was haben wir gelernt?

Es gibt keine einzige perfekte Methode, um zu erkennen, ob ein beliebiger Befehl erfolgreich war oder fehlgeschlagen ist. % Co_de% und && sind jedoch die zuverlässigsten Methoden zum Erkennen von Erfolg und Misserfolg.

Im Allgemeinen ändern weder || noch && den ERRORLEVEL direkt. Aber es gibt ein paar seltene Ausnahmen.

  • || setzt den ERRORLEVEL richtig, der andernfalls übersehen würde, wenn RD oder Umleitung fehlschlägt
  • || setzt bei fehlgeschlagener Ausführung eines ungültigen Befehls einen anderen ERRORLEVEL, wenn% code% nicht verwendet wird (1 vs. 9009).

Schließlich erkennt || ein von einem Stapelscript zurückgegebenes ERRORLEVEL ungleich Null nicht als Fehler, es sei denn, der CALL-Befehl wurde verwendet.

Wenn Sie sich strikt auf || oder || verlassen, um Fehler zu entdecken, riskieren Sie, Fehler zu verpassen, die RD und Redirection (und andere?) verursachen könnten, und Sie laufen auch Gefahr, fälschlicherweise bestimmte interne zu denken Befehle fehlgeschlagen, wenn es in Wirklichkeit ein Holdover von einem vorherigen Befehl war, der fehlgeschlagen ist.

    
dbenham 22.01.2016, 01:53
quelle
3

Um eine Lösung für Batch-Dateien zu erstellen, um den Rückgabecode festzulegen, während goto :eof verwendet wird, können Sie Ihr Skript ein wenig ändern.

%Vor%

Jetzt können Sie

verwenden %Vor%

Der einzige Nachteil hier ist der Verlust des Fehlerlevels.
In diesem Fall wird der Rückgabecode auf false gesetzt, aber der Fehlerlevel wird immer auf 1 gesetzt, durch die ungültige (call)
Momentan kann ich keine Möglichkeit finden, sowohl den Errorlevel als auch den Returncode auf benutzerdefinierte Werte zu setzen

    
jeb 25.01.2016 10:21
quelle
1

Der wahre Errorlevel übersteht den Doppelrohr (bei Fehler) -Operator nicht.

Verwenden Sie stattdessen Logik. Zum Beispiel ist unten eine Möglichkeit, wie Sie es tun könnten:

%Vor%

Bearbeiten:

Wenn der Befehl, der diesem vorangeht, kein einzelner Befehl ist, muss mehr getan werden, um den Fehlerlevel zu verfolgen. Beispiel, wenn Sie ein Skript aufrufen, dann müssen Sie in diesem Skript die Übergabe von Errorlevel an diesen Code zurückverwalten.

Bearbeiten Sie 2:

Hier sind Testszenarien, bei denen der ERRORLEVEL '9009' und deren Ausgabe sein sollte.

1) Kein Fehler bei der Verrohrung, wenn Logik verwendet wird.

%Vor%
  

'mybad.exe' wird nicht als interner oder externer Befehl erkannt,   ausführbares Programm oder Batch-Datei.

     

Errorlevel ist jetzt 9009

     

Errorlevel ist jetzt 9009

2) Fehler bei Rohrleitungen, Echo

%Vor%
  

'mybad.exe' wird nicht als interner oder externer Befehl erkannt,   ausführbares Programm oder Batch-Datei.

     

Errorlevel ist jetzt 1

     

Errorlevel ist jetzt 1

3) Fehler bei der Verrohrung, goto

%Vor%
  

'mybad.exe' wird nicht als interner oder externer Befehl erkannt,   ausführbares Programm oder Batch-Datei.

     

Errorlevel ist jetzt 1

Wenn alles, was Sie interessiert, "Etwas" ist, dann ist es OK. Code 1 ist so gut wie alles andere. Aber wenn Sie wissen müssen, "was" fehlgeschlagen ist, dann können Sie nicht nur eine Fehlfunktion pipe und lassen Sie es das tatsächliche Ergebnis löschen.

    
RLH 22.01.2016 00:08
quelle

Tags und Links