Kontrollsequenz für Fehler in unbenannten Pipes

Kontrollsequenz für Fehler in unbenannten Pipes
$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0

Im obigen Beispiel mit der unbenannten Pipe awkist der Fehler der unbenannten Pipe nicht bekannt.

$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'

Um awkauf diesen Fehler aufmerksam zu machen, könnte ich den obigen Code verwenden, indem ich eine Steuersequenz und einige Fehlerinformationen sende.

Da filebereits viele Dateitypen bekannt sind, gibt es eine sinnvolle Steuersequenz, die für diese Anwendung verwendet werden kann, sodass awkdie Fehlersteuersequenz nicht als von einer legitimen Datei stammend missbraucht wird? Danke.

Antwort1

Handelt es sich um einen Standard-Unix-Befehl, sollte es ausreichen, commandmayfailder Steuersequenz Ihren eigenen komplexen Text voranzustellen (beispielsweise ), um den Unterschied verständlich zu machen.__ERROR__CMDFAIL__:awk

Wenn Sie jedoch auch Ihre eigene und/oder proprietäre Software einbinden, ist es schwierig, Ihnen eine allgemeine Zeichenfolge zu geben. Es ist möglich (wenn auch unwahrscheinlich), dass einer Ihrer proprietären Befehle eine solche Zeichenfolge verwendet. Sie sollten sich den allgemeinen Aufbau der Fehlermeldungen ansehen und eine Zeichenfolge erstellen, die wahrscheinlich nicht verwendet wird.

Wenn es commandmayfailsich file, wie die Frage nahelegt, um handelt, kann es ausreichend sein, eine Zeichenfolge ohne zu verwenden :.

Antwort2

Wenn die Ausgabe cmdmayfailrelativ klein ist und der Befehl selbst unabhängig von anderen Teilen Ihres Codes beendet wird, können Sie seine Ausgabe in einer Variablen speichern und den Beendigungsstatus als allererste Zeile übergeben. Der Code in <()würde dann etwa so aussehen:

out="$(cmdmayfail)"; printf '%s\n' "$?" "$out"

Sie awksollten getline<fden Beendigungsstatus abrufen. Anschließend getline<fwird die tatsächliche Ausgabe von gelesen cmdmayfail.

Einschränkungen:

  • Variablen in Bash können keine Nullzeichen speichern.
  • $()entfernt alle nachfolgenden Zeilenumbruchzeichen und printffügt dann genau eines hinzu. Ein umständlicher Trick, um dies zu vermeiden:

    out="$(cmdmayfail; s="$?"; printf X; exit "$s")"; printf '%s\n%s' "$?" "${out%X}"
    

cmdmayfailDie Tatsache, dass Sie die Ausgabe im Block verarbeiten, BEGINkann darauf hinweisen, cmdmayfaildass Sie mit einer vorzeitigen Beendigung rechnen. Vielleicht reicht diese Lösung aus.


Im Allgemeinen cmdmayfailkann es sogar „endlos“ laufen (also bis Sie es beenden) und Sie möchten möglicherweise seine Ausgabe lesen, während Sie den („endlosen“) Standard-DIN von verarbeiten awk. In diesem Fall funktioniert die obige Lösung nicht.

Sie können jeder Zeile der Ausgabe von cmdmayfaileine feste Statuszeile voranstellen (z. B. OK) und schließlich eine Zeile mit dem Beendigungsstatus von hinzufügen cmdmayfail. Der Code in <()würde etwa so aussehen:

 cmdmayfail | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

Beispiel:

$ (printf '%s\n' foo "bar baz"; exit 7) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
OK
foo
OK
bar baz
7

Dann awksollte Ihr Code getline<fprüfen, ob es so ist OK. Wenn ja, getline<fist die nächste Zeile (wieder) cmdmayfailmit Sicherheit von . Führen Sie eine Schleife durch, um alle Zeilen zu analysieren, bis kein „ OKwenn Sie es erwarten“ erscheint. Dann ist es der Beendigungsstatus.

Dies funktioniert einwandfrei, es sei denn, cmdmayfailes entsteht einunvollständige Zeile. Beispiel:

$ (printf 'foo\nincomplete line'; exit 22) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

Abhängig von der Implementierung von sedkann das Tool

  1. die unvollständige Zeile überhaupt ignorieren oder
  2. verarbeiten und das fehlende Newline-Zeichen hinzufügen, oder
  3. verarbeiten Sie es so wie es ist.

Tatsächlich werden Sie

  1. einen Teil der Ausgabe verpassen oder
  2. nicht wissen, dass die Zeile unvollständig war, oder
  3. Holen Sie sich die Zeile mit dem daran angehängten Beendigungsstatus.

Im Falle von (3) printf '\n%s\n' "${PIPESTATUS[0]}"kann Folgendes helfen. Es wird eine zusätzliche leere Zeile generiert, wenn die letzte Zeile von abgeschlossen ist; auf diese Weise kann cmdmayfailIhr Code dies erkennen.awk

Stellen Sie sich einen Fall vor, in dem cmdmayfailmitten in der Zeile zwangsweise beendet wurde. Dann möchten Sie die unvollständige Zeile möglicherweise nicht analysieren. Das Problem ist: Um zu wissen, awkob die Zeilenform cmdmayfailvollständig war oder nicht, müssen Sie die nächste (Status-)Zeile testen. Die Implementierung einer nützlichen Logik hierfür awkkann zumindest unbequem sein.


Es ist gut, eine unvollständige Zeile so schnell wie möglich zu erkennen,readin Bash kann dies tun. Der Nachteil ist, readdass es langsam ist (und denken Sie daran, dass Bash-Variablen keine Nullzeichen speichern können). Beispiellösung:

# define this helper function in the main shell
encerr () { ( eval "$@" ) | (while IFS= read -r line; do printf 'C\n%s\n' "$line"; done; [ -n "$line" ] && printf 'I\n%s\n' "$line") ; printf 'E\n%s\n' "${PIPESTATUS[0]}"; }
# this part you want to put in <()
encerr cmdmayfail

Dann müssen Sie das benutzerdefinierte Protokoll darin dekodieren awk. Die Zeilen kommen paarweise. (Sehen Sie sich die Beispiele weiter unten an, um das Protokoll intuitiver zu verstehen.)

  1. Lesen Sie die erste Zeile eines Paars ( getline<f).
  2. Speichern Sie die erste Zeile in einer Variablen ( first=$0).
  3. Lesen Sie die zweite Zeile eines Paars ( getline<f).
  4. Analysieren Sie die erste Zeile ( $first).
    • Wenn dies Cder Fall ist, ist die zweite (aktuelle $0) eine vollständige Zeile von cmdmayfail, die Sie analysieren können.
    • Wenn dies der IFall ist, ist die zweite Zeile eine unvollständige Zeile aus cmdmayfail. Sie können sie analysieren, müssen es aber nicht. Erwarten Sie Edas nächste Paar.
    • Wenn dies der EFall ist, ist der zweite der Beendigungsstatus von cmdmayfail. Sie sollten keine weiteren Paare erwarten.
  5. Schleife.

Beachten Sie, dass ich eval "$@"innerhalb der Funktion verwendet habe. Was Sie danach schreiben, encerrwird zum zweiten Mal ausgewertet, daher möchten Sie normalerweise etwas wie

encerr 'cmd1 -opt foo'

oder

encerr "cmd1 -opt foo"

oder auch

encerr 'cmd1 -opt foo | cmd2'

Dies ist im Grunde das Formular, das Sie zum Ausführen von Remote-Befehlen verwenden ssh. Vergleichen Sie:

ssh a@b 'cmd1 -opt foo | cmd2'

Oder Sie können die Funktion folgendermaßen erstellen:

encerr () { "$@" | …

und nenne es so:

encerr cmd1 -opt foo

Vergleichen mit sudo:

sudo cmd1 -opt foo

Beispiele (unter Verwendung der Originalfunktion mit eval):

  • Erfolg mit leerer Ausgabe

    $ encerr true
    E
    0
    
  • Fehler mit leerer Ausgabe

    $ # I'm redirecting stderr so "command not found" doesn't obfuscate the example
    $ encerr nonexisting-command-foo1231234 2>/dev/null
    E
    127
    
  • Erfolg nach kompletten Zeilen

    $ encerr 'date; sleep 1; date'
    C
    Mon Sep 30 09:07:40 CEST 2019
    C
    Mon Sep 30 09:07:41 CEST 2019
    E
    0
    
  • Ausfall nach kompletten Zeilen

    $ encerr 'printf "foo\nbar\n"; false'
    C
    foo
    C
    bar
    E
    1
    
  • Erfolg nach einer unvollständigen Zeile

    $ encerr 'printf "foo bar\n89 baz"'
    C
    foo bar
    I
    89 baz
    E
    0
    
  • Fehler nach einer unvollständigen Zeile

    $ encerr 'printf "\nThe first line was empty and this one was interru"; exit 33'
    C
    
    I
    The first line was empty and this one was interru
    E
    33
    

verwandte Informationen