Bash 在基於字元串的超時選項規範上讀取內置錯誤,但不是基於數組的。為什麼?
在閱讀fff的原始碼以了解有關 Bash 程式的更多資訊時,我看到一個超時選項
read
作為數組傳遞給這裡:read "${read_flags[@]}" -srn 1 && key "$REPLY"
的值
read_flags
設置如下:read_flags=(-t 0.05)
(因此,
read
預期的結果呼叫是read -t 0.05 -srn 1
)。我不太明白為什麼不能使用字元串,即:
read_flags="-t 0.05" read "$read_flags" -srn 1 && key "$REPLY"
這種基於字元串的方法會導致“無效的超時規範”。
調查後,我想出了一個測試腳本
parmtest
:show() { for i in "$@"; do printf '[%s]' "$i"; done printf '\n' } opt_string="-t 1" opt_array=(-t 1) echo 'Using string-based option...' show string "$opt_string" x y z read "$opt_string" echo echo 'Using array-based option...' show array "${opt_array[@]}" x y z read "${opt_array[@]}"
bash parmtest
使用($BASH_VERSION
is 5.1.4(1)-release)執行此命令,可得出:Using string-based option... [string][-t 1][x][y][z] parmtest: line 11: read: 1: invalid timeout specification Using array-based option... [array][-t][1][x][y][z] (1 second delay...)
我可以從調試輸出中看到,
1
基於數組的方法中的值是獨立的並且沒有空格。我還可以從錯誤消息中看到1
:之前有一個額外的空格read: 1: invalid timeout specification
。我的懷疑就在那個領域。奇怪的是,如果我將這種方法與另一個命令一起使用,例如
date
,問題就不存在了:show() { for i in "$@"; do printf '[%s]' "$i"; done printf '\n' } opt_string="-d 1" opt_array=(-d 1) echo 'Using string-based option...' show string "$opt_string" x y z date "$opt_string" echo echo 'Using array-based option...' show array "${opt_array[@]}" x y z date "${opt_array[@]}"
(唯一的區別是
opt_string
andopt_array
現在指定-d
not-t
並且我在每種情況下都呼叫date
not )。read
執行時
bash parmtest
會產生:Using string-based option... [string][-d 1][x][y][z] Wed Sep 1 01:00:00 UTC 2021 Using array-based option... [array][-d][1][x][y][z] Wed Sep 1 01:00:00 UTC 2021
沒有錯。
我已經搜尋過,但徒勞無功,以找到答案。而且作者一口氣直接寫了這個位,馬上就用了一個數組,這讓我很納悶。
先感謝您。
9 月 3 日更新:這是我寫了到目前為止從閱讀中學到的知識的部落格文章,
fff
我也引用了這個問題以及其中的精彩答案:探索 fff 第 1 部分 - 主要。
原因是
read
內置函式和date
命令解釋它們的命令行參數的方式不同。但是,第一件事。在您的兩個範例中,您按照建議在 shell 變數的取消引用周圍放置引號,無論是
"${read_flags[@]}"
在數組情況下還是"$read_flags"
在標量情況下。建議始終引用您的 shell 變數的主要原因是為了防止不必要的分詞。考慮以下
- 您有一個名為的文件
My favorite songs.txt
,其中包含空格,並希望將其移動到目錄playlists/
中。- 如果將文件名儲存在變數中
$fname
並呼叫mv $fname playlists/
該
mv
命令將看到四個參數:My
、favorite
和songs.txt
並playlists/
嘗試將三個不存在的文件My
和移動favorite
到songs.txt
目錄playlists/
中。顯然不是你想要的。
- 相反,如果您將
$fname
引用放在雙引號中,如mv "$fname" playlists/
它確保 shell 將包括空格在內的整個字元串作為一個單詞傳遞給
mv
,以便它辨識出它只是一個需要移動的文件(儘管名稱中有空格)。現在您有一種情況,您希望將選項參數儲存在 shell 變數中。這些很棘手,因為有時它們很長,有時很短,有時它們會取值。關於如何指定帶參數的選項有很多方法,通常如何解析它們完全由程序員自行決定(參見此問答)進行討論)。因此,Bash 的
read
內置命令和date
命令反應不同的原因可能在於這兩者如何解析其命令行參數的內部工作。不過,我們可以稍微推測一下。
- 當儲存
-t 0.05
在一個標量 shell 變數中並將其作為 傳遞時"$opt_string"
,接收者會將其視為一個包含空格的字元串(見上文)。- 當將
-t
and儲存0.05
在數組變數中並將其作為"${opt_array[@]}"
接收者傳遞時,會將其視為兩個單獨的項目,the-t
和0.05
. (1) (2)- 許多程序將使用
getopt()
GNU C 庫中的函式來解析命令行參數,正如 POSIX 指南所推薦的那樣。- 區分“
getopt()
短”選項和“長”選項格式,例如date -u
或date --utc
在date
命令的情況下。選項(例如/ )的選項值的解釋方式通常是或用於短期期權和或用於長期期權。-o``--option``getopt``-o*value*``-o *value*``--option=*value*``--option *value*
- 當
-t 0.05
作為兩個單詞傳遞給使用 的工具時getopt()
,它將把 as 之後的第一個字元-
作為選項名稱,將下一個單詞作為選項值(語法)。因此,將作為選項名稱和選項值。-o *value*``read``t``0.05
-t 0.05
當作為一個單詞傳遞時,它將被解釋為語法:將(再次)將後面的第一個字元作為選項名稱,並將字元串的其餘部分作為選項值,因此該值將帶有前導空格。-o*value*``getopt()``-
0.05
- 該
read
命令顯然不接受帶有前導空格的超時規範。事實上,如果你打電話read -t " 0.05" -srn 1
其中值明確是一個帶有前導空格的字元串,
read
也會抱怨這一點。作為結論,當涉及到選項值時,該
date
命令顯然是以更寬鬆的方式編寫的,-d
並且不關心值字元串是否以空格開頭。這可能並不出人意料,因為日期規範可以採用的值非常多樣化,而不是(顯然)需要是數字的超時規範的情況。(1) 請注意,在此處使用
@
( 與 相對*
)有很大的不同,因為當引用數組引用時,所有數組元素將顯示為好像它們被單獨引用,因此可以包含空格本身而無需進一步拆分。(2) 原則上,還有第三種選擇:儲存
-t 0.05
在標量變數$opt_string
中,但$opt_string
不帶引號傳遞。在這種情況下,我們將在空格處進行分詞,並且再次將兩個項目-t
和0.05
分別傳遞給程序。但是,這不是推薦的方式,因為有時您的參數值會包含需要保留的顯式空格。