如何確定變數賦值的返回狀態?
我在腳本中看到了這樣的構造:
if somevar="$(somecommand 2>/dev/null)"; then ... fi
這是在某處記錄的嗎?如何確定變數的返回狀態以及它與命令替換有何關係?(例如,我會得到相同的結果
if echo "$(somecommand 2>/dev/null)"; then
嗎?)
它記錄在(針對 POSIX) 開放組基本規範的第 2.9.1 節簡單命令中。那裡有一堵文字牆;我請您注意最後一段:
如果有命令名稱,則應按照命令搜尋和執行中的說明繼續執行。如果沒有命令名稱,但該命令包含命令替換,則該命令應以執行的最後一個命令替換的退出狀態完成。否則,命令將以零退出狀態完成。
所以,例如,
Command Exit Status $ FOO=BAR 0 (but see also the note from icarus, below) $ FOO=$(bar) Exit status from "bar" $ FOO=$(bar)$(quux) Exit status from "quux" $ FOO=$(bar) baz Exit status from "baz" $ foo $(bar) Exit status from "foo"
這也是 bash 的工作方式。但另請參閱最後的“不那麼簡單”部分。
phk,在他的問題中,作業就像具有退出狀態的命令,除非有命令替換?, 建議
…看起來好像賦值本身算作命令…退出值為零,但在賦值右側之前應用(例如,命令替換呼叫…)
這不是一個可怕的看待它的方式。確定簡單命令(不包含
;
、&
、|
或)返回狀態的粗略方案是:&&``||
- 從左到右掃描該行,直到到達末尾或命令字(通常是程序名稱)。
- 如果您看到變數賦值,則該行的返回狀態可能為 0。
- 如果您看到命令替換——即
$(…)
——從該命令中獲取退出狀態。- 如果您到達實際命令(不是在命令替換中),請從該命令中獲取退出狀態。
- 該行的返回狀態是您遇到的最後一個數字。
命令替換作為命令的參數,例如,
foo $(bar)
不計算在內;您從中獲得退出狀態foo
。套用phk的符號,這裡的行為是temporary_variable = EXECUTE( "bar" ) overall_exit_status = EXECUTE( "foo", temporary_variable )
但這有點過於簡單化了。整體退貨狀況從
A=$( *cmd 1* ) B=$( *cmd 2* ) C=$( *cmd 3* ) D=$( *cmd 4* ) E=mc 2
是 的退出狀態。分配後發生的分配不會將整體退出狀態設置為 0。
*cmd4*``E=``D=
icarus在他對 phk 問題的回答中提出了一個重要的觀點:變數可以設置為只讀。POSIX 標準第 2.9.1 節的倒數第三段說,如果任何變數賦值嘗試為在目前 shell 環境中設置了只讀屬性的變數賦值(無論賦值是否在該環境中進行),將發生變數賦值錯誤。有關這些錯誤的後果,請參閱Shell 錯誤的後果。
所以如果你說
readonly A C=Garfield A=Felix T=Tigger
Garfield
返回狀態為 1。字元串,Felix
和/或Tigger
是否被命令替換替換並不重要- 但請參閱下面的註釋。第 2.8.1 節 Shell 錯誤的後果有另一組文本和一個表格,並以
在表中顯示的所有要求互動式 shell 不退出的情況下,shell 不應對發生錯誤的命令執行任何進一步的處理。
一些細節是有道理的;有些不:
- 正如最後一句似乎指定的那樣,賦值
A=
有時會*中止命令行。*在上面的範例中,C
設置為Garfield
,但未T
設置(當然,也不是A
)。- 同樣, 執行 但不執行。**但是,**在我的 bash 版本(包括 4.1.X 和 4.3.X)中,它確實執行. (順便說一句,這進一步彈劾了 phk 的解釋,即賦值的退出值適用於賦值的右側。)
C=$(*cmd1*) A=$(*cmd2*) T=$(*cmd3*)``*cmd1*``*cmd3*
*cmd2*
但這裡有一個驚喜:
在我的 bash 版本中,
只讀A C=*某事*A=*某事*T=*某事* *cmd 0*
確實執行。尤其,
*cmd0*
C=$( *cmd 1* ) A=$( *cmd 2* ) T=$( *cmd 3* ) *cmd 0*
執行 and ,但不執行. (請注意,這與沒有命令時的行為相反。)並且它設置(以及)在. 我想知道這是否是 bash 中的錯誤。
*cmd1*``*cmd3*``*cmd2*``T``C``*cmd0*
沒那麼簡單:
該答案的第一段是指“簡單命令”。 規範說,
“簡單命令”是一系列可選的變數分配和重定向,以任何順序,可選地後跟單詞和重定向,由控制運算符終止。
這些語句類似於我的第一個範例塊中的語句:
$ FOO=BAR $ FOO=$(bar) $ FOO=$(bar) baz $ foo $(bar)
其中前三個包括變數賦值,最後三個包括命令替換。
但是一些變數賦值並不是那麼簡單。 bash(1)說,
賦值語句也可以作為**
alias
,declare
,typeset
,export
,readonly
和local
**內置命令(聲明命令)的參數出現。對於
export
,POSIX 規範說,退出狀態
0所有名稱操作數均已成功導出。
>0無法導出至少一個名稱,或者
-p
指定了選項並發生錯誤。POSIX 不支持
local
,但是bash(1)說,**
local
不在函式內時使用是錯誤的。返回狀態為 0,除非local
**在函式外部使用、提供了無效名稱或name是只讀變數。在兩行之間閱讀,我們可以看到聲明命令像
export FOO=$(bar)
和
local FOO=$(bar)
更像
foo $(bar)
只要他們忽略退出狀態
bar
並根據主命令(、、或)為您提供export
退出local
狀態foo
。所以我們有奇怪的像Command Exit Status $ FOO=$(bar) Exit status from "bar" (unless FOO is readonly) $ export FOO=$(bar) 0 (unless FOO is readonly, or other error from “export”) $ local FOO=$(bar) 0 (unless FOO is readonly, statement is not in a function, or other error from “local”)
我們可以用它來證明
$ export FRIDAY=$(date -d tomorrow) $ echo "FRIDAY = $FRIDAY, status = $?" FRIDAY = Fri, May 04, 2018 8:58:30 PM, status = 0 $ export SATURDAY=$(date -d "day after tomorrow") date: invalid date ‘day after tomorrow’ $ echo "SATURDAY = $SATURDAY, status = $?" SATURDAY = , status = 0
和
myfunc() { local x=$(echo "Foo"; true); echo "x = $x -> $?" local y=$(echo "Bar"; false); echo "y = $y -> $?" echo -n "BUT! " local z; z=$(echo "Baz"; false); echo "z = $z -> $?" } $ myfunc x = Foo -> 0 y = Bar -> 0 BUT! z = Baz -> 1
幸運的是, ShellCheck擷取了錯誤並引發了SC2155,這表明
export foo="$(mycmd)"
應該改為
foo=$(mycmd) export foo
和
local foo="$(mycmd)"
應該改為
local foo foo=$(mycmd)
信用和參考
我得到了連接命令替換的想法
$(bar)$(quux)
——來自 Gilles 對 How can I get bash to exit on backtick failure 以類似於 pipefail 的方式的回答?,其中包含與此問題相關的大量資訊。