Der Exit-Code ist die einzige Information, die ein beendetes Programm seinem Aufrufer maschinenlesbar hinterlässt. Alles andere — Meldungen, Logs, Reports — muss gelesen werden; der Exit-Code lässt sich abfragen. Plattformübergreifend gilt eine einzige feste Regel:
0 heißt Erfolg. Alles andere heißt Fehler, Warnung oder anwendungsspezifischer Status.
Eine allgemeingültige Tabelle für Anwendungen gibt es nicht — nur Konventionen. Diese Seite beschreibt, wie Lyx den Exit-Code setzt, welche Konventionen gelten und wie man eigene Codes entwirft, ohne in die üblichen Fallen zu laufen.
→ Fehlerbehandlung · std.process · std.io · Compiler-Parameter
Jedes Lyx-Programm beginnt bei main. Deklariert man sie mit Rückgabetyp int64, ist ihr Rückgabewert der Exit-Code des Prozesses:
fn main(): int64 {
return 0; // Erfolg
}
$ ./programm
$ echo $?
0
Der Weg dahin: Der vom Compiler erzeugte _start-Stub ruft main auf, nimmt deren Rückgabewert und übergibt ihn unverändert an den exit_group-Syscall. Es gibt keine Laufzeitschicht dazwischen, die den Wert noch verändert — was main zurückgibt, geht direkt an den Kernel.
| Weg | Exit-Code |
|---|---|
return N aus main | N (auf 8 Bit gestutzt, siehe unten) |
exit(N) — Builtin, überall aufrufbar, kein Import nötig | N — beendet sofort, auch aus einer tief verschachtelten Funktion |
panic(…), fehlgeschlagenes assert, Bereichsverletzung, ungefangenes throw | 1 |
import std.io;
fn PruefeKonfiguration(pfad: pchar): void {
if (!FileExists(pfad)) {
PrintLn("Konfiguration nicht lesbar");
exit(3); // beendet das Programm hier, kein return noetig
}
}
std.io bietet dafür zusätzlich ExitProc(code) — ein reiner Namensalias für exit.
fn main(): void ist zulässig und ergibt immer Exit-Code 0. Wer einen anderen Code braucht, nimmt int64 oder exit().
fn main(): int64 ohne return im Rumpf wird dagegen abgewiesen — seit der Typprüfung von 1.0.14 meldet der Compiler Funktion ohne return, Rueckgabetyp verlangt einen Wert 'main'.
Unter Linux und macOS überträgt der Kernel nur die unteren 8 Bit des Werts. Größere Zahlen laufen still über:
fn main(): int64 { return 256; } // -> Exit-Code 0 (!)
Nachgemessen mit lyxc 1.0.16F:
return | tatsächlicher Exit-Code |
|---|---|
0 | 0 |
1 | 1 |
42 | 42 |
255 | 255 |
256 | 0 |
257 | 1 |
300 | 44 |
1000 | 232 |
-1 | 255 |
-2 | 254 |
Nie eine Anzahl als Exit-Code zurückgeben.return fehlerAnzahl;sieht sinnvoll aus und funktioniert 255 Läufe lang — beim 256. gemeldeten Fehler meldet das Programm Erfolg. Dasselbe gilt für Summen, Zeilennummern und alles andere, was wachsen kann.
Der einzige verlässliche Weg: eine kleine, feste Menge von Codes, jede mit einer Bedeutung. Die Anzahl gehört in die Ausgabe, nicht in den Exit-Code.
Auf Windows ist der Exit-Code ein 32-Bit-Wert und wird nicht gestutzt. Wer beide Zielplattformen bedient, hält sich trotzdem an den Unix-Bereich — sonst verhält sich dasselbe Programm je nach Ziel anders.
| Code | Bedeutung |
|---|---|
0 | Erfolg |
1–125 | Allgemeiner oder anwendungsspezifischer Fehler — hier gehören eigene Codes hin |
126 | Kommando gefunden, aber nicht ausführbar (Shell-Konvention) |
127 | Kommando nicht gefunden (Shell-Konvention) |
128 + n | Durch Signal n beendet: 130 = SIGINT (Strg+C), 137 = SIGKILL, 143 = SIGTERM |
Die Codes ab 126 sind belegt: Ein eigenes exit(137) ist von einem OOM-Kill nicht mehr zu unterscheiden. Der Bereich 1–125 ist der einzige, der einem allein gehört.
Wo eine feinere Einteilung gebraucht wird, ist sysexits.h die verbreitetste Konvention (verpflichtend ist sie nirgends):
| Konstante | Wert | Bedeutung |
|---|---|---|
EX_OK | 0 | Erfolg |
EX_USAGE | 64 | Falsche Kommandozeilenverwendung |
EX_DATAERR | 65 | Ungültige Eingabedaten |
EX_NOINPUT | 66 | Eingabedatei fehlt oder ist unlesbar |
EX_UNAVAILABLE | 69 | Dienst nicht verfügbar |
EX_SOFTWARE | 70 | Interner Softwarefehler |
EX_OSERR | 71 | Betriebssystemfehler |
EX_CANTCREAT | 73 | Ausgabedatei nicht erstellbar |
EX_IOERR | 74 | I/O-Fehler |
EX_TEMPFAIL | 75 | Temporärer Fehler — ein Wiederholungsversuch ist sinnvoll |
EX_NOPERM | 77 | Fehlende Berechtigung |
EX_CONFIG | 78 | Konfigurationsfehler |
EX_TEMPFAIL ist der nützlichste Eintrag der Liste: Er beantwortet die Frage, die ein automatischer Aufrufer wirklich hat — noch einmal versuchen oder aufgeben? Mailserver werten ihn bis heute so aus.
GetExitCodeProcess.STILL_ACTIVE. Ein Prozess, der mit 259 endet, gilt für jede Überwachung als noch laufend.3010 heißt bei Installern „Erfolg, Neustart erforderlich„; 9009 meldet cmd.exe für „Befehl nicht gefunden“.return: -1073741819 ist 0xC0000005, eine Access Violation.rustc macht das mit 101 vor.robocopy liefert 1 = kopiert, 2 = Extras, 4 = Unterschiede, und erst ab 8 ist es ein Fehler. Damit ist es mit der gesamten „≠ 0 = Fehler„-Welt inkompatibel und muss überall gesondert behandelt werden.
import std.io;
con EXIT_OK: int64 := 0;
con EXIT_EINGABE: int64 := 1; // Datei fehlt oder ist unlesbar
con EXIT_FORMAT: int64 := 2; // Datei da, Inhalt unbrauchbar
con EXIT_TEMPORAER: int64 := 3; // Netz/Dienst weg — Wiederholung sinnvoll
con EXIT_INTERN: int64 := 70; // Bug in diesem Programm
fn main(): int64 {
var daten: int64 := LadeEingabe();
if (daten == 0) {
PrintLn("Eingabe nicht lesbar"); // das Warum nach stderr/stdout
return EXIT_EINGABE; // das Was-tun als Exit-Code
}
return EXIT_OK;
}
std.process liefert den Exit-Code eines Kindprozesses bereits ausgepackt — die Unit wendet WEXITSTATUS an, man bekommt also direkt die 0–255 und nicht den rohen wait-Status:
import std.io;
import std.process;
fn main(): int64 {
var code: int64 := shell("exit 42"c);
PrintLn(IntToStr(code)); // 42
return 0;
}
Ein per Signal beendetes Kind liefert0— nicht 137 oder 143. Die Unit liest(status » 8) & 255; bei einem Signaltod steht dort nichts, das Ergebnis ist0und damit von einem Erfolg nicht zu unterscheiden. Nachgemessen:shell("kill -KILL $$")ergibt0.
Das128 + n-Schema kennt nur die Shell, die es aus der Signalinformation zusammensetzt. Wer erkennen muss, ob ein Kind abgeschossen wurde, führt den Aufruf über eine Shell (shell("...; echo $?")) oder wertet den rohenwait-Status selbst aus.
Zweite Folge derselben Mechanik:shell("exit 256")ergibt0— die Modulo-256-Falle wirkt auch hier.
Schlägt execve in einem mit spawn gestarteten Kind fehl, beendet sich das Kind mit 127 — dieselbe Konvention wie die Shell für „Kommando nicht gefunden“.
Der Compiler selbst benutzt (Stand 1.0.16F, nachgemessen):
| Code | Bedeutung | Was der Aufrufer tun sollte |
|---|---|---|
0 | Erfolg — auch mit Warnungen | weitermachen |
1 | Übersetzung fehlgeschlagen: Parse-, Sema- oder Typfehler; ebenso fehlende Eingabedatei und Aufruf ohne Argumente | Quellcode bzw. Pfad prüfen |
2 | Ungültige Kommandozeile: unbekannter Schalter, --target-energy außerhalb 1..5, -O ohne Stufe | Aufruf korrigieren |
Syntax- und Semantikfehler werden bewusst nicht über verschiedene Codes getrennt: Kein Aufrufer handelt darauf unterschiedlich, der Unterschied gehört in die Fehlermeldung. Warnungen ergeben 0.
Die Spezifikation sieht mehr Codes vor, als 1.0.16F umsetzt. Vorgesehen sind zusätzlich3(Eingabe fehlt/unlesbar),4(Konfigurationsfehler),5(Backend-/Umgebungsfehler, etwa fehlender Linker) und70(interner Compilerfehler, ICE — „Bug melden„, angelehnt anEX_SOFTWARE).
Gemessen liefert 1.0.16F für eine fehlende Eingabedatei1statt3. Wer CI-Skripte schreibt, prüft deshalb heute nur auf0/1/2und nicht auf feinere Codes.
Für ein Build-Skript heißt das:
lyxc main.lyx -o main
case $? in
0) echo "gebaut" ;;
1) echo "Quellcode fehlerhaft"; exit 1 ;;
2) echo "Aufruf falsch — lyxc --help"; exit 2 ;;
esac
$? gilt nur für das zuletzt ausgeführte Kommando. Wer ihn zweimal braucht, sichert ihn sofort in einer Variablen.lyxc x.lyx | tee log meldet Erfolg, auch wenn lyxc scheitert. Gegenmittel: set -o pipefail oder ${PIPESTATUS[@]}.set -e hat so viele Ausnahmen (Bedingungen, &&/||-Ketten, Subshells), dass es kein Sicherheitsnetz ist.ssh benutzt 255 für eigene Verbindungsfehler und reicht sonst den entfernten Status durch — ein Programm, das selbst mit 255 endet, ist über ssh nicht mehr von einem Verbindungsabbruch zu unterscheiden.137 fast immer OOM-Kill und 143 regulärer Shutdown per SIGTERM. Beides sind keine Anwendungsfehler — vor dem Debuggen den OOMKilled-Status prüfen.cmd.exe bedeutet IF ERRORLEVEL 5 größer oder gleich 5. Genau prüfen mit IF %ERRORLEVEL% EQU 5.$? ein Wahrheitswert des letzten PowerShell-Ausdrucks; der Exit-Code eines nativen Programms steht in $LASTEXITCODE.Geprüft mit lyxc 1.0.16F.
Letzte Aktualisierung: 2026-08-11