Bash

Bash 在基於字元串的超時選項規範上讀取內置錯誤,但不是基於數組的。為什麼?

  • May 20, 2022

在閱讀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_VERSIONis 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_stringandopt_array現在指定-dnot-t並且我在每種情況下都呼叫datenot )。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命令將看到四個參數:Myfavoritesongs.txtplaylists/嘗試將三個不存在的文件My和移動favoritesongs.txt目錄playlists/中。顯然不是你想要的。

  • 相反,如果您將$fname引用放在雙引號中,如
mv "$fname" playlists/

它確保 shell 將包括空格在內的整個字元串作為一個單詞傳遞給mv,以便它辨識出它只是一個需要移動的文件(儘管名稱中有空格)。

現在您有一種情況,您希望將選項參數儲存在 shell 變數中。這些很棘手,因為有時它們很長,有時很短,有時它們會取值。關於如何指定帶參數的選項有很多方法,通常如何解析它們完全由程序員自行決定(參見此問答)進行討論)。因此,Bash 的read內置命令和date命令反應不同的原因可能在於這兩者如何解析其命令行參數的內部工作。不過,我們可以稍微推測一下。

  • 當儲存-t 0.05在一個標量 shell 變數中並將其作為 傳遞時"$opt_string",接收者會將其視為一個包含空格的字元串(見上文)。
  • 當將-tand儲存0.05在數組變數中並將其作為"${opt_array[@]}"接收者傳遞時,會將其視為兩個單獨的項目,the-t0.05. (1) (2)
  • 許多程序將使用getopt()GNU C 庫中的函式來解析命令行參數,正如 POSIX 指南所推薦的那樣。
  • 區分“getopt()短”選項和“長”選項格式,例如date -udate --utcdate命令的情況下。選項(例如/ )的選項的解釋方式通常是或用於短期期權和或用於長期期權。-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 不帶引號傳遞。在這種情況下,我們將在空格處進行分詞,並且再次將兩個項目-t0.05分別傳遞給程序。但是,這不是推薦的方式,因為有時您的參數值會包含需要保留的顯式空格。

引用自:https://unix.stackexchange.com/questions/667139