Inhaltsverzeichnis

Lyx – Exit-Codes

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


1. Der Rückgabewert von main

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.

Die drei Wege aus einem Programm

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.

main ohne Rückgabewert

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'.


2. Die Modulo-256-Falle

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.


3. Konventionen, an die man sich halten sollte

Unix / POSIX

Code Bedeutung
0 Erfolg
1125 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.

sysexits.h — die BSD-Empfehlung

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.

Windows


4. Eigene Exit-Codes entwerfen

  1. Exit-Codes sind eine API. Sobald sie dokumentiert und in fremden Skripten verbaut sind, lassen sie sich nicht mehr ändern. Deshalb klein halten und früh festlegen.
  2. Nur 1–125 verwenden. 126 und darüber ist unter Unix belegt, 259 unter Windows.
  3. Grobe Klassen statt Fehler-Enzyklopädie. Der Exit-Code beantwortet: Was soll der Aufrufer tun? (Wiederholen? Konfiguration reparieren? Bug melden?) Das Warum gehört nach stderr.
  4. Der interne Fehler bekommt einen eigenen Code. „Melde einen Bug„ ist eine andere Handlungsaufforderung als „deine Eingabe ist kaputt“. rustc macht das mit 101 vor.
  5. Temporär von dauerhaft trennen, wenn Wiederholungen denkbar sind — sonst versucht es der Aufrufer entweder nie oder immer.
  6. Keine Bitmasken. 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.
  7. Nie eine Anzahl (siehe Abschnitt 2).

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;
}


5. Exit-Codes von Kindprozessen lesen

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 liefert 0 — nicht 137 oder 143. Die Unit liest (status » 8) & 255; bei einem Signaltod steht dort nichts, das Ergebnis ist 0 und damit von einem Erfolg nicht zu unterscheiden. Nachgemessen: shell("kill -KILL $$") ergibt 0.

Das 128 + 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 rohen wait-Status selbst aus.

Zweite Folge derselben Mechanik: shell("exit 256") ergibt 0 — 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“.


6. Die Exit-Codes von lyxc

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ätzlich 3 (Eingabe fehlt/unlesbar), 4 (Konfigurationsfehler), 5 (Backend-/Umgebungsfehler, etwa fehlender Linker) und 70 (interner Compilerfehler, ICE — „Bug melden„, angelehnt an EX_SOFTWARE).

Gemessen liefert 1.0.16F für eine fehlende Eingabedatei 1 statt 3. Wer CI-Skripte schreibt, prüft deshalb heute nur auf 0 / 1 / 2 und 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


7. Fallen in Shell und CI


Geprüft mit lyxc 1.0.16F.

Letzte Aktualisierung: 2026-08-11