8 Min. Lesezeit

Entfernte Locale-Einstellungen in Agententests

Entfernte Locale-Einstellungen verändern Datum, Sortierung, Dezimalzahlen und Text. Fixieren, protokollieren und gezielt variieren.

Entfernte Locale-Einstellungen in Agententests

Ein Agent kann denselben Befehl mit denselben Argumenten und Dateien ausführen und auf einem entfernten Host trotzdem ein anderes Ergebnis erhalten. Der fehlende Eingabewert ist oft die Locale des Prozesses. Sie verändert, wie gängige Werkzeuge Text einordnen, Namen sortieren, Zahlen formatieren, Datumswerte ausgeben, eine Codierung wählen und Fehlermeldungen formulieren.

Behandle die Locale als Testdaten und nicht als Dekoration der Maschine. Fixiere sie, wenn ein Test ein stabiles Protokoll erwartet, variiere sie, wenn der Code menschliche Konventionen unterstützen soll, und protokolliere sie, sobald ein Agent eine Prozess- oder SSH-Grenze überschreitet. Sonst kann ein lokal grüner Test einen Fehler beim entfernten Parsen verbergen, bis der Agent auf die falsche Zeile, den falschen Betrag oder das falsche Datum reagiert.

Das ist kein Argument dafür, überall Englisch zu erzwingen. Eine Maschinenschnittstelle und eine Schnittstelle für Menschen haben verschiedene Aufgaben. Eine stabile Maschinenausgabe braucht ein ausdrücklich definiertes Format und eine festgelegte Umgebung. Eine Ausgabe für Benutzer sollte bewusst lokalisiert werden. Wer beides vermischt, lässt eine übersetzte Diagnose als Zustand parsen oder verwandelt ein Dezimalkomma unbemerkt in den falschen Wert.

Der entfernte Prozess bestimmt das Ergebnis

Die wirksame Locale gehört dem Prozess, der den Befehl ausführt. Die Locale deines Laptops steuert kein Programm, das über SSH läuft, sofern die Variablen nicht ausdrücklich weitergereicht oder gesetzt werden. Eine interaktive entfernte Shell kann sich außerdem von einer nicht interaktiven Befehlssitzung unterscheiden.

POSIX.1-2024 legt die Rangfolge eindeutig fest. Ein nicht leeres LC_ALL überschreibt alle Kategorien. Fehlt es, gilt eine Kategorievariable wie LC_TIME oder LC_COLLATE für ihre jeweilige Kategorie. LANG liefert den Standard für alle weiterhin ungesetzten Kategorien. Deshalb bewirkt LANG=C nichts, solange ein geerbtes LC_ALL=de_DE.UTF-8 vorhanden ist.

OpenSSH fügt eine weitere Grenze hinzu. Laut Client-Handbuch wählt SendEnv die lokalen Variablen für die Übertragung aus, doch der Server muss sie annehmen. Standardmäßig sendet der Client keine. Mit SetEnv lassen sich konkrete Werte anfordern, die der Server ebenfalls akzeptieren muss. Ein Entwickler kann SendEnv LANG LC_* in seiner persönlichen SSH-Konfiguration haben, während der zustandslose SSH-Helfer des Agenten diese Konfiguration nicht nutzt. Auch der umgekehrte Fall ist möglich. Auf keines der Ergebnisse sollte man sich ohne Prüfung verlassen.

Startdateien beim Anmelden machen das Bild komplizierter. Eine Distribution kann LANG über PAM oder die Systemkonfiguration setzen. Ein Benutzerprofil kann es für eine interaktive Anmeldung ändern. Ein entfernter Befehl durchläuft oft nicht denselben Startpfad. Ein von ihm gestarteter Container kann eigene Einstellungen einführen, und in einem minimalen Image ist die benannte Locale womöglich gar nicht installiert.

Die Korrektur besteht darin, die vorgesehene Umgebung am endgültigen Ausführungspunkt zu setzen. Verlasse dich nicht auf Weiterleitung, wenn der Befehl eine stabile Locale braucht. Schreibe die Zuweisung direkt zum Werkzeug:

ssh buildbox 'env LC_ALL=C.UTF-8 TZ=UTC command-to-test --format=plain'

Damit wird der Testvertrag sichtbar. Fehlt C.UTF-8, tritt der Fehler zudem an einer aussagekräftigen Stelle auf, statt stillschweigend die Wahl des Hosts zu übernehmen. Wenn dieser Locale-Name nicht vorausgesetzt werden kann, prüfe die verfügbaren Locales bei der Bereitstellung und nutze einen dokumentierten Ersatz.

Protokolliere die Umgebung vor der Auswertung

Ein entfernter Fehlerbericht braucht die wirksamen Kategorien, die Codierung, die Zeitzone, die Identität des Werkzeugs und die ursprünglichen Bytes. Nur LANG zu erfassen reicht nicht, weil LC_ALL oder eine Kategorievariable es überschreiben kann. Wer lediglich decodierten Text speichert, kann den Beleg für einen Codierungsfehler beseitigen.

Führe vor dem untersuchten Befehl eine kleine Diagnose aus:

env | LC_ALL=C sort | sed -n '/^LANG=/p;/^LC_/p;/^TZ=/p'
printf 'charmap='; locale charmap
printf 'decimal='; locale -k decimal_point 2>/dev/null || true
printf 'date='; date +'%Y-%m-%dT%H:%M:%S%z'
printf 'tool='; command -v sort
sort --version 2>/dev/null | sed -n '1p'

Ein typisches Linux-Ergebnis kann diese Form haben:

LANG=de_DE.UTF-8
LC_NUMERIC=de_DE.UTF-8
TZ=Europe/Berlin
charmap=UTF-8
decimal=decimal_point="," 
date=2026-07-24T143105+0200
tool=/usr/bin/sort
sort (GNU coreutils) 9.5

Mache aus diesem Beispiel keinen Erwartungswert. Entscheidend ist die Menge der Felder. Manche locale-Implementierungen formatieren die Schlüsselwortausgabe anders, und andere Werkzeuge unterstützen --version möglicherweise nicht. Erfasse den Exit-Status und die Standardfehlerausgabe jeder Prüfung, damit eine fehlende Funktion nicht wie ein leerer Wert aussieht.

Bewahre bei Codierungsfehlern die Bytes vor dem Decodieren auf. Ein Testprogramm kann Standardausgabe und Standardfehler in getrennte Dateien schreiben, Prüfsummen bilden und anschließend eine Kopie mit der angegebenen Codierung decodieren. Eine hexadezimale Ansicht um das erste ungültige Byte ist wesentlich hilfreicher als ein von der Protokollschicht eingesetztes Ersatzzeichen.

Protokolliere auch den genauen Transportweg. ssh host command, ssh host sh -lc command, ein interaktives Terminal und ein durch ein Agentenwerkzeug gestarteter Prozess sind verschiedene Ausführungspfade. Sie können andere Shells, Startdateien, Pseudoterminals und Umgebungsfilter wählen. Wenn der fehlerhafte Weg eine Agentenaktion nutzt, reproduziere diesen Weg, statt nur zu beweisen, dass eine von Hand eingegebene Anmeldung funktioniert.

Dieses Diagnosepaket gehört bei jeder Abweichung zum Testartefakt. Es verwandelt „entferntes Sortieren ist unzuverlässig“ in einen Vergleich konkreter Eingaben.

Fixiere eine Locale für Protokolle, nicht für Menschen

Verwende eine feste Locale, wenn die Befehlsausgabe in einen Parser, Snapshot, Diff, Cache-Schlüssel, eine Bereitstellungsentscheidung oder ein anderes Programm fließt. Verwende die gewünschte menschliche Locale, wenn sich die Ausgabe an eine Person richtet. Das sind getrennte Schnittstellen, selbst wenn ein Befehl derzeit beide erzeugt.

Der verbreitete Rat, überall LC_ALL=C zu setzen, ist beliebt, weil er viele Unix-Werkzeuge berechenbar macht und auf POSIX-Systemen verfügbar ist. Als allgemeine Regel ist er falsch. Je nach System und Laufzeit kann die Locale C ein auf ASCII ausgerichtetes Zeichenmodell bedeuten. Ein Programm, das Namen wie Málaga liest, kann die Bytes ablehnen oder falsch behandeln, obwohl die Sortierung stabil geworden ist.

C.UTF-8 verbindet auf vielen aktuellen Unix-Systemen eine einfache Sortierung mit UTF-8 und ist deshalb eine praktische Test-Locale. POSIX schreibt diesen genauen Namen jedoch nicht vor. macOS, Linux-Distributionen, Container und Sprachlaufzeiten stellen nicht denselben Katalog bereit. locale -a zeigt das Angebot des Hosts, und bereitgestellte Test-Images sollten angeben, welchen Namen sie garantieren.

Eine weitere Unterscheidung muss klar bleiben: Locale-Stabilität ist keine Formatstabilität. Ein festes LC_ALL garantiert nicht, dass zwei Werkzeugversionen dieselben Spalten, Abstände, Warnungen oder JSON-Felder ausgeben. Wenn das Werkzeug JSON, NUL-Trenner, Epochensekunden oder eine ausdrückliche Formatzeichenfolge anbietet, wähle auch diese Schnittstelle. Die Locale-Kontrolle entfernt eine Variable, friert das Programm aber nicht ein.

Ein guter Wrapper löscht mögliche Überschreibungen und ergänzt nur das Nötige:

run_stable() {
  env -u LANGUAGE -u LC_COLLATE -u LC_CTYPE -u LC_MESSAGES \
      -u LC_MONETARY -u LC_NUMERIC -u LC_TIME \
      LC_ALL=C.UTF-8 TZ=UTC "$@"
}
run_stable sort input.txt

Wenn die Zielsysteme ein env ohne -u umfassen, baue stattdessen eine minimale Umgebung. Setze PATH ausdrücklich und behalte nur Variablen, die die Anwendung benötigt. Kopiere nicht die gesamte Elternumgebung, um anschließend nur LANG zu ändern. Dabei bleiben Kategorievariablen erhalten.

Tests für die Ausgabe an Benutzer gehen umgekehrt vor. Sie wählen bewusst eine unterstützte Locale und prüfen die relevante Konvention. Ein deutscher Berichtstest darf ein Dezimalkomma und deutsche Monatsnamen erwarten. Der Parser hinter dem Bericht sollte intern weiterhin normalisierte Zahlen und Datumswerte austauschen.

Datumswerte brauchen Format und Zeitzone

Locale und Zeitzone verursachen unterschiedliche Datumsfehler. LC_TIME steuert Namen und übliche Darstellungen. TZ legt fest, welche bürgerliche Zeit zu einem Zeitpunkt gehört. Das Fixieren des einen fixiert das andere nicht.

GNU Coreutils warnt davor, dass sich die Ausgabe von date nicht immer später wieder parsen lässt. Das Handbuch empfiehlt für erzeugte Daten ein sprachunabhängiges Format, eine gregorianische Darstellung und eine eindeutige Zone wie UTC oder Z. Dieser Rat ist verlässlicher als ein Snapshot, der unter englischen Einstellungen zufällig besteht.

Wähle für ein Testprotokoll eine ausdrückliche Darstellung:

env LC_ALL=C.UTF-8 TZ=UTC date +'%Y-%m-%dT%H:%M:%SZ'

Die Ausgabe hat die Form 2026-07-24T12:31:05Z. Wenn der Test statt der aktuellen Uhrzeit einen festen Zeitpunkt braucht, übergib ihn über eine unterstützte Option oder injiziere eine Uhr in die Anwendung. Locale-Kontrolle kann die Zeit nicht anhalten.

Vermeide %c, %x, %X, %a und %b in Daten, die ein anderes Programm parst. Diese Direktiven fordern absichtlich regionale Konventionen oder übersetzte Namen an. Auch numerische Direktiven können auf manchen Systemen Kalenderbesonderheiten enthalten. Das GNU-Handbuch dokumentiert Locales, die für bestimmte Direktiven alternative Kalender verwenden. Ein scheinbar numerisches Jahr ist deshalb kein universeller Vertrag, solange Format und Locale es nicht festlegen.

Wochennummern sind eine weitere Falle. Kalenderjahr, ISO-Wochenjahr und lokale Wochenkonventionen beantworten rund um Neujahr verschiedene Fragen. Lege fest, welche davon die Geschäftsregel verwendet, und teste Grenzdaten. Eine feste Locale korrigiert keine falsche Kombination aus %Y-%V.

Berichte für Menschen sollten Datumswerte am Rand des Systems formatieren. Speichere oder übertrage den Zeitpunkt in stabiler Form und wende erst für die Anzeige Locale und Zone des Lesers an. Wenn ein Agent Zeitstempel mehrerer Hosts vergleichen muss, fordere Epochenwerte oder Zeichenfolgen im Stil von RFC 3339 mit Offset an. Lass ihn nicht raten, ob 03/04/26 den 3. April oder den 4. März meint.

Eine sinnvolle Datumsmatrix enthält eine Locale mit englischen Monatsnamen, eine mit anderen Namen, UTC, eine Zone mit Zeitumstellung sowie Daten nahe an der Umstellung und am Jahreswechsel. Es geht nicht darum, die ganze Welt aufzuzählen. Der Test soll Code aufdecken, der die Konventionen des Entwicklers verallgemeinert hat.

Die Sortierung muss zum Verbraucher passen

Kontrolliere SSH an der Grenze
Der sp-ssh-Helfer führt entfernte Befehle aus, ohne SSH-Schlüssel im Agentenkontext abzulegen.

Text hat keine einzige natürliche Reihenfolge. Byte-Reihenfolge, Unicode-Codepunkte und sprachliche Sortierung erzeugen verschiedene Folgen. Tests scheitern, wenn sie eine erwarten und eine andere aufrufen.

Das Handbuch von GNU sort erklärt, dass Vergleiche normalerweise die durch LC_COLLATE gewählte Reihenfolge verwenden. Für Skripte, die die traditionelle Ordnung benötigen, empfiehlt es ausdrücklich LC_ALL=C. Es warnt auch davor, nur LC_COLLATE zu setzen, wenn LC_ALL sie überschreiben kann oder die Zeichenkategorien inkompatible Codierungen verwenden.

Betrachte dieses Fixture:

Zebra
apple
zebra
Ångström
ábaco

Eine Locale im Stil von C sortiert gewöhnlich nach codierten Bytes. ASCII-Großbuchstaben stehen dabei vor Kleinbuchstaben und UTF-8-Folgen außerhalb von ASCII später. Eine sprachliche Locale kann Großschreibung und Akzente auf unterschiedlichen Ebenen vergleichen. Übernimm keine vermutete Reihenfolge in einen plattformübergreifenden Artikel oder Test. Führe die tatsächlich unterstützte Locale aus und prüfe die benötigte semantische Eigenschaft.

Für ein reproduzierbares Manifest oder eine Referenzdatei ist die Byte-Reihenfolge meist richtig. Setze LC_ALL=C, wenn alle Pfade auf den portablen Zeichensatz begrenzt sind. Andernfalls verwende eine geprüfte UTF-8-Locale und definiere die Ordnungsfunktion im Programm. Für eine Liste, die spanische, schwedische oder deutsche Leser sehen, ist binäre Ordnung ungeeignet. Nutze eine Bibliothek für sprachliche Sortierung mit festgelegter Datenversion, denn die Sortierdaten des Betriebssystems können sich unabhängig vom Code ändern.

Sortieren und Verbinden müssen dieselben Regeln verwenden. GNU Coreutils verlangt konsistente Locales und Optionen für sort und join. Eine unter einer Ordnung sortierte Datei kann für join unter einer anderen unsortiert erscheinen, sodass Treffer fehlen oder Diagnosen entstehen. Dasselbe gilt für comm, Duplikatentfernung, Zusammenführung und jede Pipeline, die gleiche Werte nebeneinander erwartet.

Bevorzuge Assertions, die die Absicht ausdrücken. Wenn die Reihenfolge unwichtig ist, vergleiche Mengen oder Maps statt eines zufälligen Snapshots. Wenn Byte-Ordnung zum Protokoll gehört, berechne und benenne sie im Test. Wenn lokalisierte Sortierung die Funktion ist, füge Fixtures mit Akzenten, Groß- und Kleinschreibung sowie Satzzeichen hinzu, die sie von binärer Sortierung unterscheiden.

Ein Agent kann das Problem verschärfen, indem er versuchsweise sort ausführt und die erste Zeile dann als „kleinstes“ oder „nächstes“ Element behandelt. Schreibe die Ordnungsregel in seinen Aktionsvertrag. „Wähle das erste Release nach semantischer Version“ ist etwas anderes als „wähle den ersten Dateinamen unter der entfernten Locale“.

Dezimalkommas brechen Pipelines unbemerkt

LC_NUMERIC definiert Dezimalzeichen und Gruppierung für Locale-abhängige Funktionen und manche Befehlsoptionen. Eine als 1,25 dargestellte Zahl kann für einen Menschen richtig und für einen Parser, der 1.25 erwartet, ungültig sein. Schlimmer noch: Ein großzügiger Parser akzeptiert womöglich nur den Präfix und liefert ohne deutlichen Fehler 1.

GNU sort -n verwendet Tausendertrennzeichen und Dezimalzeichen der Locale, wenn es numerische Präfixe erkennt. Pythons locale.format_string, locale.atof und verwandte Funktionen folgen ebenfalls LC_NUMERIC. Das normale float() von Python und viele Datenformate tun das nicht. Text ohne klare Grenze zwischen diesen Familien zu übergeben erzeugt einen Fehler, der nur unter bestimmten Einstellungen sichtbar wird.

Halte Protokollzahlen normalisiert. JSON-Zahlen verwenden einen Punkt, Befehlsoptionen dokumentieren gewöhnlich eine feste Grammatik und Datenbankformate definieren ihre eigene Darstellung. Formatiere Komma und Gruppen erst für die Anzeige, nachdem Berechnung und Serialisierung abgeschlossen sind.

Teste Parser mit Werten, an denen stilles Abschneiden auffällt:

0.5
1.25
1234.75
-0.125

Führe denselben Vorgang anschließend unter einer Locale mit Dezimalkomma aus. Wenn das Werkzeug lokalisierte Eingaben absichtlich akzeptiert, liefere entsprechende Komma-Fixtures und lehne mehrdeutige Gruppierung ab. Wenn es eine feste Grammatik verspricht, setze die Locale des Befehls und prüfe, dass eine Kommaeingabe klar scheitert.

„Repariere“ beliebige Ausgabe nicht, indem du jedes Komma durch einen Punkt ersetzt. Ein Komma kann Felder trennen, Tausender gruppieren oder im Text stehen. Nutze strukturierte Ausgabe oder einen Parser, der die deklarierte Locale kennt. Wenn der Erzeuger keine Grammatik veröffentlicht, ist seine Ausgabe für Menschen nicht für Automatisierung geeignet.

Auch Shell-Arithmetik vermittelt falsche Sicherheit. Die Shell kann eine feste Syntax verwenden, während ein aufgerufenes awk, printf, Tabellenkonverter oder eine Sprachlaufzeit die Locale in einzelnen Operationen anwendet. Teste die gesamte Pipeline unter einer Umgebung, statt jeden Befehl einzeln in der Login-Shell zu prüfen.

Geld verlangt noch strengere Behandlung. Speichere die kleinste Einheit oder einen Dezimaltyp mit ausdrücklicher Währung und lokalisiere nur den angezeigten Wert. Ein Agent, der einen Betrag mit einem Grenzwert vergleicht, sollte die normalisierte Zahl erhalten und keinen Bericht für Menschen auslesen.

Codierungsfehler beginnen vor dem Decodieren

Genehmige jeden sensiblen Aufruf
Ein Schlüssel pro Aufruf kann vor jeder entfernten Nutzung Klick oder Touch ID verlangen.

Die Locale kann einem Prozess sagen, wie er Bytefolgen als Zeichen interpretiert. LC_CTYPE beeinflusst die Zeichenklassifizierung und ist oft an einen Zeichensatz gebunden. Das betrifft Werkzeuge, die Text teilen, Zeichenklassen erkennen, Großschreibung ändern, Anzeigebreiten berechnen oder Bytes und Zeichenfolgen umwandeln.

UTF-8 auf beiden Maschinen beweist nicht, dass jeder Prozess UTF-8 verwendet. Ein entfernter Dienst kann in der Locale C starten, einem minimalen Container können generierte Locale-Daten fehlen oder eine Sprachlaufzeit kann einen eigenen UTF-8-Modus einschalten. Die Python-Dokumentation ist deutlich: Auf manchen Systemen ist die bevorzugte Codierung nur eine Schätzung, und Python UTF-8 Mode kann die Locale-Codierung bei dieser Abfrage ignorieren.

Decodiere an Grenzen ausdrücklich. Wenn der Befehlsvertrag UTF-8 festlegt, lies Bytes und decodiere sie als UTF-8 mit strikter Fehlerbehandlung. Rufe nicht den Plattformstandard auf und hoffe auf das richtige Ergebnis. Wenn Unix beliebige Dateinamen erlaubt, denke daran, dass sie an der Betriebssystemgrenze Bytefolgen sind. Eine erzwungene Umwandlung in normalen Text kann Informationen verlieren. Verwende die besondere Dateisystemcodierung und eine umkehrbare Fehlerstrategie der Laufzeit, sofern vorhanden.

Fehlerersatz hilft bei der Anzeige, ist für Entscheidungen aber gefährlich. Zwei verschiedene ungültige Bytefolgen können zum gleichen sichtbaren Ersatzzeichen werden. Ein Test sollte mit dem Byte-Offset scheitern, die Originalausgabe bewahren und ein kurzes Hex-Fenster zeigen. Diese Daten verraten, ob der Erzeuger eine alte Codierung ausgegeben, eine Folge abgeschnitten oder Binärdaten durch einen Textkanal geschickt hat.

Zeichenklassen brauchen direkte Fixtures. Unter verschiedenen Locales können [[:alpha:]], Großschreibung und Leerraumerkennung andere Zeichen umfassen. POSIX erklärt, dass LC_CTYPE bestimmt, wie Bytefolgen zu Zeichen werden und welche Zeichen zu Klassen gehören. Ein Skript, das Namen mit einem Locale-abhängigen Bereich bereinigt, kann entfernt anderen Text annehmen oder entfernen.

Nutze Unicode-bewussten Anwendungscode für menschlichen Text und ausdrückliche ASCII-Regeln für Protokollbezeichner. Lass die Umgebungs-Locale nicht entscheiden, was als Variablenname, Token oder Protokollfeld gilt. Umgekehrt ist ein auf ASCII beschränkter Filter für Personennamen keine Validierung.

Ein kompakter Codierungstest enthält reines ASCII, vorgefertigten Text mit Akzenten, denselben sichtbaren Text mit kombinierenden Zeichen, ein anderes Schriftsystem und eine absichtlich ungültige Bytefolge, wenn die Schnittstelle rohe Bytes zulässt. Prüfe Bytes an Protokollgrenzen und Zeichen nach der Decodierung. Diese Trennung macht Fehler verständlich.

Eine kleine Locale-Matrix deckt Annahmen auf

Genehmige riskante Wiederholungen
Verlange vor jeder Nutzung eine Freigabe, wenn ein Locale-Test externen Zustand ändern kann.

Führe die meisten deterministischen Befehlstests unter einer fixierten Locale aus und anschließend eine kleinere Variantensuite, die Annahmen gezielt bricht. Jede installierte Locale zu testen kostet Zeit und liefert dennoch schwache Abdeckung, weil viele dieselben relevanten Konventionen teilen.

Wähle Varianten nach Verhalten:

  1. Verwende C für portables Byteverhalten und übersetzte Diagnosen, die nicht geparst werden dürfen.
  2. Verwende eine verfügbare UTF-8-Locale mit Dezimalpunkt und nicht trivialer Sortierung.
  3. Verwende eine UTF-8-Locale mit Dezimalkomma und anderen Datumsnamen.
  4. Ergänze eine Locale oder einen Laufzeitmodus, der Codierungsannahmen aufdeckt, falls das Produkt ihn unterstützt.
  5. Kombiniere Datumstests mit UTC und einer Zone mit Zeitumstellung.

Stelle diese Locales im Test-Image bereit. Ein übersprungener Test, weil dem Runner Locale-Daten fehlen, ist kein Erfolg. Gib bei einem Einrichtungsfehler locale -a aus und mache den benötigten Katalog zum Teil der Image-Definition.

Halte die Matrix nahe am Prozessstart. Übergib in Python eine Kopie der Umgebung an subprocess.run, statt die globale Locale in einem Runner mit Threads zu ändern:

import os
import subprocess

def run_case(locale_name):
    child_env = os.environ.copy()
    child_env.update({"LC_ALL": locale_name, "TZ": "UTC"})
    return subprocess.run(
        ["./agent-command", "inspect", "fixtures/names.txt"],
        env=child_env,
        check=False,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )

Das Python-Handbuch sagt, dass setlocale() auf den meisten Systemen nicht threadsicher ist und eine Eigenschaft des gesamten Programms ändert. Ein Wechsel zwischen Tests kann parallele Fälle gegenseitig beeinflussen. Eine Umgebung für den Kindprozess isoliert den getesteten Befehl und entspricht eher der entfernten Ausführung.

Assertions sollten Exit-Status, Bytes der Standardausgabe, Bytes der Fehlerausgabe und die geparste Bedeutung trennen. LC_MESSAGES kann die Sprache der Diagnose ändern, ohne den Fehler zu verändern. Ein Test auf den englischen Satz „No such file“ prüft einen Übersetzungskatalog und nicht die Fehlerbedingung. Bevorzuge Exit-Codes, strukturierte Fehlerfelder oder stabile Bezeichner.

Wenn eine Variante scheitert, grenze nach Kategorie ein. Lösche zunächst LC_ALL und setze dann LANG und einzelne Kategorien, um Zeit, Sortierung, Zahlenformat, Meldungen oder Zeichenbehandlung als Ursache zu finden. Kategorievariablen sind hervorragende Diagnosewerkzeuge, auch wenn die Produktion ein einziges LC_ALL verwendet.

Führe die kleine Matrix für Änderungen an Parsing, Prozessstart, SSH, Berichten oder Fixtures aus. Ein geplanter Lauf kann mehr Betriebssysteme und Werkzeugversionen abdecken. Speichere die Umgebungsdiagnose bei jedem Fehler, damit eine Wiederholung nicht vom Gedächtnis abhängt.

Agentenaktionen brauchen einen Ausführungsvertrag

Ein autonomer Agent verstärkt Locale-Mehrdeutigkeit, weil er eine plausible Ausgabe mit einer folgenreichen Aktion verbinden kann. Ändert sich die Sortierung, wählt er vielleicht eine andere Datei. Schneidet ein Parser eine Dezimalzahl ab, vergleicht er den falschen Grenzwert. Verschiebt eine Zone das Datum, handelt er am Datensatz des falschen Tages.

Gib entfernten Befehlswerkzeugen einen ausdrücklichen Vertrag mit vier Teilen: die gesetzte Umgebung, die zurückgegebenen Bytes oder strukturierten Daten, die bewahrten Statusinformationen und die verwendete Shell-Semantik. Ergänze Werkzeugversionen oder Funktionsprüfungen, wenn die Ausgabe je nach Implementierung variiert. Ein Prompt kann keine unbeschriebene Prozessgrenze reparieren.

Definiere Erfolg, bevor der Agent die Ausgabe sieht. Exit-Status null kann bedeuten, dass der Befehl endete, nicht dass er einen Datensatz fand. Manche Werkzeuge melden Teilergebnisse mit einer Warnung, andere schreiben Fortschritt auch bei Erfolg in den Standardfehler. Bewahre alle drei Kanäle und lass einen Parser mit festgelegter Grammatik entscheiden, ob das Ergebnis nutzbar ist. Lass ein Sprachmodell Erfolg nicht aus dem Ton einer lokalisierten Meldung ableiten.

Eine nicht unterstützte Locale muss die Einrichtung scheitern lassen und darf keine Überraschung während der Aktion werden. Startet ein Befehl mit LC_ALL=fr_FR.UTF-8 auf einem Host ohne diese Locale, können Shell oder Laufzeit warnen und zurückfallen, oder das Programm kann abbrechen. Das Testprogramm sollte die Locale zuerst über locale -a oder eine Funktionsprüfung bestätigen, den Namen protokollieren und stoppen, wenn das gewünschte Verhalten nicht geprüft werden kann. Ein bei der Bereitstellung gewählter Ersatz ist kontrolliert. Ein mitten in der Agentenaktion gewählter Ersatz ist versteckter Zustand.

Prüfe auch die Interpreterebenen getrennt. Der lokale Prozess baut ein SSH-Argument, der entfernte Anmeldedienst startet die Benutzer-Shell, und diese parst die Befehlszeichenfolge, bevor das Zielprogramm seine Argumente liest. Umgebungszuweisungen und Quoting können sich auf jeder Ebene ändern. Bevorzuge eine argumentbasierte API für entfernte Ausführung, wenn es sie gibt. Falls nur eine Shell-Zeichenfolge möglich ist, teste die exakte Serialisierung mit Leerzeichen, einfachem Anführungszeichen, Zeilenumbruch und Text außerhalb von ASCII. Eine feste Locale korrigiert kein Quoting, doch ein Quotingfehler kann die Zuweisung auf den falschen Befehl anwenden.

Behandle häufig geparste Befehlsausgabe wie ein kleines versioniertes Protokoll. Speichere ein Fixture der Bytes, dokumentiere erwartete Locale und Werkzeugfamilie und lehne unbekannte Formen ab. Wenn ein Upgrade die Form ändert, aktualisiere Parser und Fixture gemeinsam. Das wirkt weniger spektakulär, als den Agenten eine weitere Anzeige „verstehen“ zu lassen, und ist für Befehle vor Schreibaktionen deutlich sicherer.

Bevorzuge bei SSH einen Befehl, der die Umgebung entfernt setzt, statt auf passende Client-Weiterleitung zu hoffen. Setze Quoting in der richtigen Ebene und teste Leerzeichen, Anführungszeichen und Text außerhalb von ASCII. Füge nicht nur für die gewünschte Locale eine Login-Shell hinzu, denn sie importiert auch Aliase, Startskripte und weiteren Zustand.

Halte die ursprünglichen Ergebnisse für eine Prüfung bereit. Sallyport kann SSH-Aktionen über den enthaltenen Helfer sp-ssh ausführen, während die SSH-Schlüssel im verschlüsselten Tresor bleiben, und das Activity-Journal protokolliert einzelne Aufrufe. Das macht die Befehlsausgabe nicht Locale-unabhängig, erhält aber eine nützliche Grenze: Der Agent bekommt Ergebnisse, jedoch nicht die dafür eingesetzten Zugangsdaten.

Wenn eine Aktion externen Zustand ändern kann, validiere den geparsten Wert vor dem Schreiben. Verlange ein ausdrückliches Format vom Lesebefehl, lehne nicht decodierbare Bytes ab und füge die protokollierte Locale zur vorgeschlagenen Aktion hinzu. Eine menschliche Freigabe ist nur sinnvoll, wenn die Karte den tatsächlich vom System geparsten Wert zeigt.

Die erste Reparatur einer bestehenden Suite ist konkret. Suche alle entfernten Befehle, deren Ausgabe geparst oder als Snapshot gespeichert wird. Ergänze die Diagnose bei Fehlern, fixiere Locale und Zeitzone am entfernten Prozess und füge eine Locale mit Dezimalkomma sowie eine mit anderer Sortierung als Gegenproben hinzu. Die überraschenden Fehler sind genau die Annahmen, die deine lokale Shell bisher verborgen hat.

FAQ

Kann SSH meine lokale Locale automatisch zum entfernten Host kopieren?

Nur wenn der Client ausgewählte Variablen sendet und der Server sie annimmt. OpenSSH sendet standardmäßig keine Umgebungsvariablen, setze die nötige Locale daher im entfernten Befehl.

Sollten Tests LC_ALL=C oder C.UTF-8 verwenden?

Verwende C, wenn die Eingabe auf portables ASCII beschränkt ist und Byte-Ordnung zum Vertrag gehört. Für UTF-8 ist eine verfügbare Locale wie C.UTF-8 sinnvoll, doch prüfe den Namen bei der Bereitstellung, weil POSIX ihn nicht vorschreibt.

Warum macht LANG=C die Befehlsausgabe nicht stabil?

Ein nicht leeres LC_ALL überschreibt LANG, und Kategorievariablen können ihr jeweiliges Verhalten ändern. Lösche Konflikte oder setze LC_ALL am tatsächlich gestarteten Prozess.

Welche Locale-Variablen beeinflussen Sortierung und Zahlen?

LC_COLLATE steuert die Sortierung und LC_NUMERIC Dezimalzeichen sowie Gruppierung in Locale-abhängigen Operationen. LC_CTYPE zählt ebenfalls, weil das Werkzeug Zeichen vor dem Vergleich interpretieren muss.

Behebt eine feste Locale auch Zeitzonenunterschiede?

Nein. Setze TZ getrennt und wähle ein ausdrückliches Datumsformat. Ein stabiler Test braucht meist sowohl eine Locale wie C.UTF-8 als auch eine Zone wie UTC.

Wie teste ich Locales, die im CI-Image fehlen?

Stelle den genauen Locale-Katalog im Image bereit und lass die Einrichtung scheitern, wenn er fehlt. locale -a hilft bei der Diagnose, doch stilles Überspringen verbirgt nur das Risiko.

Ist JSON-Ausgabe immer unabhängig von der Locale?

Die JSON-Grammatik verwendet feste Zeichen, aber ein Erzeuger kann lokalisierte Daten oder Zahlen in Textfelder schreiben. Teste den Feldvertrag und den Parser, nicht nur die geschweiften Klammern.

Warum sollten Tests rohe Befehlsbytes speichern?

Ein Decoder kann ungültige Folgen vor der Protokollierung ersetzen oder verwerfen. Rohe Ausgabe und Fehler bewahren die Belege, um das erste falsche Byte und die tatsächliche Codierung zu finden.

Kann ich die Prozess-Locale in parallelen Tests ändern?

Vermeide es. Viele Laufzeiten behandeln die Locale als globalen Prozesszustand, und Python dokumentiert setlocale() auf den meisten Systemen als nicht threadsicher. Gib jedem Kindprozess eine eigene Umgebung.

Wie viele Locale-Varianten sollte eine Testsuite ausführen?

Führe die Hauptsuite unter einer fixierten Locale aus und wähle eine kleine Matrix nach Verhalten: Byte-Ordnung, Dezimalkomma, andere Datumsnamen und UTF-8. Mehr Namen helfen nicht, wenn sie dieselbe Annahme prüfen.

Sallyport

Sallyport führt API-Aufrufe und SSH-Befehle für Ihren KI-Agenten aus. Die Schlüssel bleiben in einem lokalen Tresor auf Ihrem Mac; Sie geben jeden Lauf frei, und jede Aktion landet in einem versiegelten Journal.

© 2026 Sallyport · Open Source unter Apache-2.0 · Oleg Sotnikov