2016-12-06 1 views
2

wir uns vor, wir diesen Code haben (script.sh):Idiomatic Weg Satz von Zwingen -e überall in einem Shell-Skript

#!/bin/bash 
set -e 

f() { 
    echo "[f] Start" >&2 
    echo "f:before-false1" 
    echo "f:before-false2" 
    false 
    echo "f:after-false" 
    echo "[f] Fail! I don't want this executed" >&2 
} 

out=$(f) 

Der Ausgang:

$ bash myscript.sh 
[f] Start 
[f] Fail! I don't want this executed 

Ich verstehe, dass $(...) startet eine sub- Shell, wo set -e nicht propagiert wird, so ist meine Frage: Was ist der idiomatische Weg, um diesen Lauf wie erwartet ohne zu viel Unordnung zu machen? Ich kann 3 Lösungen sehen, von denen ich keine mag (noch bin ich mir sicher, dass sie tatsächlich funktionieren): 1) Füge set -e zum Start von f hinzu (und jede andere Funktion in der App). 2) Führen Sie $(set -e && f). 3) Füge ... || return 1 zu jedem Befehl hinzu, der fehlschlagen kann.

+2

Option 3 ist eigentlich eine [gute Idee] (http://mywiki.wooledge.org/BashFAQ/105). – chepner

+0

@chepner: Gut gelesen. 'cmd || return 1' ist explizit und konzeptionell in Ordnung, aber so umständlich ... – tokland

Antwort

1

Statt Befehl Substitution, sollten Sie process substitution verwenden Ihre Funktion aufrufen, sodass set -e wirksam bleibt:

mapfile arr < <(f) # call function f using process substitution 
out="${arr[*]}"   # convert array content into a string 
declare -p out   # check output 

Ausgang:

[f] Start 
declare -- out="f:before-false1 
f:before-false2 
" 
+1

Sehr interessant, ich wusste nichts über diesen Ansatz. Ein bisschen umständlich zu schreiben, obwohl. Ich füge ein paar Echos zu 'f' hinzu, damit' declare -p out' etwas Bedeutungsvolles zeigt. – tokland

+0

Ja, das ist korrekt, denn nach diesem 'false' Befehl wird die Funktion' f' beendet. – anubhava

+1

Interessante Alternative - 'set -e 'gilt in der Tat auch für die Befehle innerhalb einer _process_-Substitution (im Gegensatz zu einer _command_substitution), obwohl ein Fehler dort das Skript als Ganzes nicht abbrechen wird. Beachten Sie, dass die Befehle in einer Prozesssubstitution in einer _subshell too_ ausgeführt werden, die Sie wie folgt überprüfen können: 'f() {echo $ ((++ v)); }; v = 1; Katze <(f); echo "$ v" ' – mklement0

3

Es ist nicht die schönste Lösung, aber es erlaubt Ihnen, set -e für die aktuelle Shell sowie alle Funktionen und Subs zu emulieren Höllen:

#!/bin/bash 

# Set up an ERR trap that unconditionally exits with a nonzero exit code. 
# Similar to -e, this trap is invoked when a command reports a nonzero 
# exit code (outside of a conditional/test). 
# Note: This trap may be called *multiple* times. 
trap 'exit 1' ERR 

# Now ensure that the ERR trap is called not only by the current shell, 
# but by any subshells too: 
# set -E (set -o errtrace) causes functions and subshells to inherit 
# ERR traps. 
set -E  

f() { 
    echo "[f] Start" >&2 
    echo "f:before-false1" 
    echo "f:before-false2" 
    false 
    echo "f:after-false" 
    echo "[f] Fail! I don't want this executed" >&2 
} 

out=$(f) 

Ausgang (auf stderr), wenn Sie dieses Skript aufrufen (Exit-Code wird später 1 sein):

[f] Start 

Hinweis:

  • konstruktions set -e/trap ERR reagieren nur auf Fehler, die nicht Teil von Bedingungen sind (siehe man bash, unter der Beschreibung set (Suche nach Literal "set ["), für die genauen Regeln, die leicht zwischen Bash 3.x und 4.x geändert).

    • So, zum Beispiel, habe f nicht den Fall in den folgenden Befehlen auslösen: if ! f; then ..., f && echo ok; Folgendes löst die Falle in der Subshell (Befehl Substitution $(...), aber nicht in den umschließenden bedingten ([[ ... ]]). [[ $(f) == 'foo' ]] && echo ok, so das Skript als Ganzes nicht abbrechen nicht

    • eine Funktion beenden/Subshell ausdrücklich in solchen Fällen verwenden etwas wie || return 1/|| exit 1, oder die Funktion aufrufen/Subshell separat, außerhalb einer bedingten ersten, zB im [[ $(f) == 'foo' ]] Fall: res=$(f); [[ $res == 'foo' ]] - res=$(f) wird dann auch die Falle für die aktuelle Shell auslösen.

  • Was, warum der trap Code kann mehr mal aufgerufen werden: Im Fall zur Hand, falseinnenf()erste die Falle auslöst, und dann, weil die exit 1 der Falle die verläßt Subshell ($(f)), die Falle ausgelöst wird wieder für die aktuelle Shell (die eine, die das Skript).

+1

Danke! Ich hatte mit "Trap ... ERR" getestet, aber 'set -E 'fehlte, damit es funktionierte. Ich denke, es ist ein ziemlich cooler Workaround, Sie müssen den Code nicht selbst ändern. – tokland

+0

Ich habe Probleme, wenn 'f' von einem if aufgerufen wird:' if! f; dann echo "f fehlgeschlagen"; fi' -> '[f] Fehlschlag! Ich will nicht, dass dies ausgeführt wird. – tokland

+1

@tokland: Ich fürchte, das ist von Entwurf - bitte sehen Sie mein Update. – mklement0

Verwandte Themen