Umleiten der Standardfehlerausgabe an die Bash-Variable

7

Hier ist ein C-Code-Snippet, das Folgendes verursacht segfault:

// segfault.c

#include <string.h>

int main()
{
    memset((char *)0x0, 1, 100);
    return 1;
}

Kompilieren Sie es mit:

gcc segfault.c -o segfault

Wenn von Bash ausgeführt:

$ ./segfault
Segmentation fault (core dumped)

Jetzt habe ich den Anruf in ein Bash-Skript eingeschlossen. Es gibt drei aufeinanderfolgende Versuche. Ich möchte die Fehlerausgabe innerhalb der Variablen erhalten retund anzeigen.

#!/bin/bash

# segfault.sh

ret=`./segfault 2>&1`
echo "1) " $ret
ret=`eval ./segfault 2>&1`
echo "2) " $ret
ret=`eval ./segfault 2>&1 | cat`
echo "3) " $ret

Wenn ich das Skript von bash ausführe:

1) 
2) 
3)  ./segfault.sh: line 7: 28814 Segmentation fault (core dumped) ./segfault

Natürlich funktioniert nur die dritte Form des Aufrufs. Meine Frage ist, warum konnten die ersten beiden Formulare die Fehlerausgabe nicht erfassen?

Holmes.Sherlock
quelle

Antworten:

7

Es funktioniert bei mir mit vereinfachtem Bash-Skript (nur stderr):

$ cat seg.sh 
#!/bin/bash
echo "Segfault" 1>&2
$ test=`./seg.sh`; echo "x$test"
Segfault
x
$ test=`./seg.sh 2>&1`; echo "x$test"
xSegfault
$ test=`eval ./seg.sh 2>&1`; echo "x$test"
xSegfault

Das Problem in Ihrem Fall wird durch die Tatsache verursacht, dass Segmentation fault (core dumped)es nicht von Ihrem Programm geschrieben wurde (weil es vom Kernel getötet wird), sondern vom übergeordneten Prozess, der die Informationen über den Tod seines Kindes erhält. Dieser Effekt wird ausgeblendet, indem Sie ihn in einen anderen Prozess einfügen und mit catin Ihrem letzten Beispiel weiterleiten. Sie sollten sich lieber auf den Exit-Code verlassen als auf stderr:

$ ./segfault; echo $?
Segmentation fault (core dumped)
139
Jakuje
quelle
2

Die Meldung "Segmentierungsfehler (Core Dumped)" wird von bash ausgegeben, nicht von dem Programm, das abgestürzt ist (wenn die Meldung ausgegeben wird, ist das Programm bereits tot!). Die Umleitung gilt nur für das Programm selbst.

Um Nachrichten von der Shell selbst über das Programm umzuleiten, führen Sie das Programm in einem Shell-Gruppierungskonstrukt aus und leiten Sie die Ausgabe der gesamten Gruppe um. Das grundlegendste Shell-Gruppierungskonstrukt, das nur eine Gruppe ausführt, sind geschweifte Klammern.

ret=`{ ./segfault; } 2>&1`

Das Formular ret=`eval ./segfault 2>&1`wendet die Umleitung auf die gesamte Auswertung des evalBefehls an, daher sollte es im Prinzip funktionieren, und es funktioniert tatsächlich auf meinem Computer mit Bash 4.3.30 und älteren Versionen. Was möglicherweise passiert (und ich kann es mit ksh reproduzieren), ist, dass Ihre Version von bash einige Optimierungen vornimmt, um zu vermeiden, dass Unterprogramme verzweigt werden, wenn sie der letzte Befehl in einer Unterschale sind. Die nominelle Art, den Befehl auszuführen, ret=`eval ./segfault`ist:

  • Erstellen Sie eine Pipe.
  • Fork, dh einen Shell-Unterprozess erstellen. Im Unterprozess (Prozess 1):
    • Leiten Sie den Ausgang zum Rohr um.
    • evalFühren Sie das eingebaute aus.
    • Gabel. Im Unterprozess (Prozess 2):
      • Führen Sie die Datei aus ./segfault, dh ersetzen Sie das derzeit in diesem Prozess ausgeführte Shell-Programm durch das segfaultProgramm.
    • (In Prozess 1) Warten Sie, bis Prozess 2 abgeschlossen ist.
    • Prozess 1 wird beendet.
  • (Im ursprünglichen Shell-Prozess) Lesen Sie aus der Pipe und akkumulieren Sie die Daten in der retVariablen.
  • Wenn das Rohr geschlossen ist, fahren Sie mit der Ausführung fort.

Wie Sie sehen können, erstellt Prozess 1 einen weiteren Prozess, wartet dann auf den Abschluss und wird sofort beendet. Es wäre effizienter, wenn Prozess 1 sich selbst recyceln würde. Einige Shells (und Shell-Versionen) sind besser als andere darin, solche Situationen zu erkennen und eine Tail-Call-Optimierung durchzuführen . Im Fall von ret=`{ ./segfault; } 2>&1`Prozess 2 wird der Standardfehler jedoch in Dateideskriptor 1 umgeleitet, Prozess 1 jedoch nicht. In der Shell-Version, die Sie ausprobiert haben, hat das Optimierungsprogramm diese Situation nicht erkannt (es hätte einen Endaufruf ausführen können, aber die Umleitung hätte anders eingerichtet werden müssen).

Gilles 'SO - hör auf böse zu sein'
quelle