2016-12-06 151 views
2

讓我們想象一下,我們有這樣的代碼(script.sh):習慣的方法

#!/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) 

輸出:

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

我明白$(...)啓動子shell set -e沒有傳播,所以我的問題是:什麼是使這個運行如預期沒有太多混亂的慣用方法?我可以看到3種解決方案,我都不喜歡這些解決方案(但我確實無法確定它們確實可行):1)將set -e添加到f(以及該應用中的所有其他功能)的開頭。 2)運行$(set -e && f)。 3)將... || return 1添加到每個可能失敗的命令。

+2

選項3實際上是一個[好主意](http://mywiki.wooledge.org/BashFAQ/105)。 – chepner

+0

@chepner:很好閱讀。 'cmd ||返回1'是明確的,概念上沒問題,但這麼麻煩...... – tokland

回答

1

代替命令替換,你應該使用process substitution打電話給你的功能,使set -e仍然有效:

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

輸出:

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

非常有趣,我不知道這種方法。雖然寫起來有點麻煩。我會在'f'中添加一些回聲來使'declare -p out'顯示一些有意義的東西。 – tokland

+0

是的,這是正確的輸出,因爲之後'false'命令使功能'f'退出。 – anubhava

+1

有趣的替代方法--' set -e'確實也適用於_process_替換中的命令(與_command_替換相對),儘管失敗將不會中止整個腳本。請注意,流程替換中的命令在_subshel​​l中運行too_,您可以驗證如下:'f(){echo $((++ v)); }; V = 1;貓<(f);回聲「$ v」' – mklement0

3

這不是最漂亮的解決方案,但它確實允許您模擬set -e當前shell 以及任何函數和subs地獄

#!/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) 

輸出(標準錯誤),如果你把這個腳本(退出代碼後會1):

[f] Start 

注:

  • 根據設計,set -e/trap ERR只對不屬於條件的故障作出響應(參見man bash,在set(搜索文字「set [」)的描述下,確切的規則,在Bash 3.x和4.x之間略有改變)。

    • 因此,例如,f不會觸發陷阱中的以下命令:if ! f; then ...f && echo ok;以下觸發陷阱在子外殼(命令替換$(...),但不是在封閉條件([[ ... ]]):[[ $(f) == 'foo' ]] && echo ok,所以作爲一個整體腳本不會中止

    • 要退出的功能/子外殼明確在這種情況下,使用像|| return 1/|| exit 1,或調用函數/子外殼分別外部條件的第一;例如,在[[ $(f) == 'foo' ]]情況:res=$(f); [[ $res == 'foo' ]] - res=$(f)會觸發當前shell的陷阱。

  • 至於爲什麼trap代碼可以被調用多個倍:在手邊的情況下,false內部f()第一觸發陷阱,然後,由於阱的exit 1離開subshel​​l$(f)),陷阱被觸發再次目前殼(運行腳本的人)。

+1

謝謝!我用'trap ... ERR'進行了測試,但是'set -E'丟失了,無法工作。我認爲這是一個非常酷的解決方法,您不需要更改代碼本身。 – tokland

+0

當我從if調用'f'時有問題:'if! F;然後回顯「f失敗」; fi' - >'[f]失敗!我不希望這樣執行' – tokland

+1

@tokland:恐怕這是設計 - 請參閱我的更新。 – mklement0