
Ich versuche, Daten aus einer Postgres-Datenbank in eine Datei in Bash zu exportieren. Ich möchte jedoch sicherstellen, dass die Datei nur überschrieben wird, wenn die Verbindung zur Datenbank nicht fehlschlägt (d. h. ich erhalte einige Daten zurück).
Versuchte die Verwendung derRohrfehlerOption, aber wenn der erste Befehl mit einem Fehler fehlschlägt (Host existiert beispielsweise nicht), wird der Cat-Befehl trotzdem ausgeführt und generiert eine leere Datei (wobei der letzte gute Inhalt gelöscht wird, was ich verhindern möchte). Im folgenden Beispiel ist myhost ein ungültiger Host, sodass der psql-Befehl einfach fehlschlagen würde.
Die größere Frage ist also, wie sichergestellt werden kann, dass bei gesetztem Pipefail nachfolgende Befehle nicht ausgeführt werden, wenn der erste Befehl fehlschlägt.
#!/bin/sh
set -o nounset
set -o errexit
set -o pipefail
PG_HOST=myhost
psql $PG_HOST -At -F$'\t' -c "SELECT * FROM mytable" | cat > /tmp/mytable.txt
Antwort1
set -o pipefail -o errexit
verhindert zwar die Ausführung nachfolgender Befehle, aber das hilft Ihnen nicht, da Sie nicht versuchen, einenanschließendBefehl wird nicht ausgeführt. In einer Pipeline werden producer | consumer
die Befehle producer
und consumer
ausgeführtparallel zu. Sie können den Start nicht verhindern, consumer
wenn producer
ein Fehler auftritt, da der Motor, sofern es nicht zu einem unerwarteten Zeitunfall kommt, bereits gestartet ist.
Wenn die einzigen beiden Möglichkeiten „ consumer
erfolgreich und erzeugt eine nicht leere Ausgabe“ und „ consumer
fehlschlägt und erzeugt keine Ausgabe“ sind, können Sie verwendenifne
von Joey Hess' moreutils.
producer | ifne consumer
Ich glaube allerdings nicht, dass das in Ihrem Anwendungsfall funktioniert – es könnte sein, dass keine übereinstimmenden Zeilen vorhanden sind (falsch-negativ und Sie erhalten veraltete Daten), die Datenbankverbindung könnte mittendrin verloren gehen (falsch-positiv und Sie erhalten abgeschnittene Daten).
Wenn Sie wissen müssen, ob der Produzent erfolgreich war, müssen Sie warten, bis er fertig ist, bevor Sie den Verbraucher starten. Und da der Verbraucher noch nicht da ist, muss die Ausgabe irgendwo gespeichert werden.
Wenn die Ausgabe keine Nullbytes enthält, mit genau einem Zeilenumbruchzeichen endet und nicht zu groß ist, können Sie sie in einer Shell-Variable speichern.
output=$(producer); producer_status=$?
if [ "$producer_status" -ne 0 ]; then
echo >&2 "Producer failed with status $producer_status"
exit "$producer_status"
fi
printf '%s\n' "$output" | consumer
In zsh und einigen anderen Shells, einschließlich ksh93 und bash, kann die letzte Zeile vereinfacht werden zu consumer <<<"$output"
.
Beachten Sie, dass die Befehlsersetzung nachfolgende Zeilenumbrüche entfernt. Wenn nachfolgende leere Zeilen relevant sind, besteht eine Problemumgehung darin, die erste Zeile in
output=$(producer; ret=$?; echo .; exit "$?")
producer_status=$? output=${output%?}
$output
enthält dann die vollständige Ausgabe, einschließlich der nachfolgenden Zeilenumbruchzeichen (sofern vorhanden). Verwenden Sie dann printf %s "$output"
anstelle von , printf '%s\n' "$output"
um es an weiterzugeben consumer
.
Wenn die Ausgabe möglicherweise zu groß ist oder Nullbytes enthält, speichern Sie sie in einer temporären Datei.
Antwort2
Wie DopeGhoti sagte:pipefail
... bedeutet lediglich, dass Fehler an jedem Punkt einer Pipe-Kette für den Exit-Code gespeichert werden.[einer Pipeline].
Um das Skript bei einem Fehler zu beenden, verwenden Sie set -e
.
Um die Erstellung der Datei zu verhindern, erstellen Sie eine temporäre Datei und benennen Sie sie bei Erfolg um, und zwar:
set -e
psql $PG_HOST -At -F$'\t' -c \
"SELECT * FROM mytable" > /tmp/mytable.txt~
# ^^^ cf. Useless Use of Cat
mv /tmp/mytable.txt~ /tmp/mytable.txt
Ich benutze immermachenfür diese Art von Dingen, weil es bei einem Fehler stoppt und mir ermöglicht, neu startbare Pipelines zu erstellen.