Shell

在shell變數比較的兩側添加前綴到字元串文字的目的是什麼?

  • November 4, 2020

多年來,我多次遇到變數與字元串文字的比較,其中有一個字元作為變數和文字的前綴,例如

if [ "x$A" = "xtrue" ]; then

為了檢查是否$A"true".

我認為這樣做是為了實現 shell 兼容性或解決長期錯誤、不直覺的行為等。沒有什麼明顯的想法出現在腦海中。

今天我想我想知道原因,但我的研究沒有發現任何結果。或者,也許只是我從相當頻繁地接觸罕見事件中做出了一些事情。

這種做法是否仍然有用,甚至可能是最好的?

這裡要理解的重要一點是,在大多數 shell 中,[它只是一個由 shell 解析的普通命令,就像任何其他普通命令一樣。

然後 shell 呼叫帶有參數列表的[(aka test) 命令,然後將[它們解釋為條件表達式。

那時,這些只是一個字元串列表,並且關於哪些字元串是由某種形式的擴展產生的資訊會失去,即使在那些[內置的 shell 中(這些天都是類似 Bourne 的 shell)。

[實用程序過去很難分辨它的哪些參數是運算符,哪些是操作數(運算符工作的東西)。語法本質上是模棱兩可的,這並沒有幫助。例如:

  • [ -t ]曾經是(並且仍然在某些 shell/ [s 中)來測試 stdout 是否是終端。
  • [ x ][ -n x ]: 測試是否x是非空字元串的縮寫(所以你可以看到與上面的衝突)。
  • 在某些 shell/ [s 中,-a並且-o可以是一元的([ -a file ]用於可訪問文件(現在替換為[ -e file ]),[ -o option ]是否啟用了該選項?)和二元運算符(andor)。同樣,! -a x可以是and(nonempty("!"), nonempty("x"))not(isaccessible("x"))
  • ()!添加更多問題。

在 C 或 等普通程式語言perl中,在:

if ($a eq $b) {...}

$a不可能將or的內容$b作為運算符,因為條件表達式在它們之前被解析$a並被$b擴展。但在貝殼中,在:

[ "$a" = "$b" ]

shell首先擴展變數²。例如,如果$acontains($bcontains ,則)所有[命令看到的是[(=和參數。這是否意味著(在詞法上相等)或(是非空字元串)。)``]``"(" = ")"``(``)``( -n = )``=

歷史實現(test出現在 70 年代末期的 Unix V7 中)過去常常失敗,即使只是因為它們處理參數的順序並不模棱兩可。

在 PDP11 模擬器中使用版本 7 Unix:

$ ls -l /bin/[
-rwxr-xr-x 2 bin      2876 Jun  8  1979 /bin/[
$ [ ! = x ]
test: argument expected
$ [ "(" = x ]
test: argument expected

大多數 shell 和[實現已經或曾經遇到過這些問題或其變體。今天有bash4.4:

bash-4.4$ a='(' b=-o c=x
bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
bash: [: `)' expected, found =

POSIX.2(於 90 年代初發布)設計了一種算法,當以最常見的使用模式(例如仍未指定)傳遞最多 4 個參數(除了和)時,該算法將使[’ 的行為明確和確定。它棄用了, ,和, 並刪除了沒有操作數。確實在2.0中實現了該算法(或至少嘗試過) 。[``]``[ -f "$a" -o "$b" ]``(``)``-a``-o``-t``bash``bash

因此,在符合 POSIX 的[實現中,[ "$a" = "$b" ]保證比較內容$a$b是否相等,無論它們是什麼。沒有-o,我們會寫:

[ "$a" = "$b" ] || [ "$a" = "$c" ]

也就是說,呼叫[兩次,每次使用少於 5 個參數。

但是所有[實現都需要相當長的時間才能變得合規。bash’s 直到 4.4 才符合要求(儘管最後一個問題是[ '(' ! "$var" ')' ]沒有人會在現實生活中真正使用它)

Solaris 10 和更早版本,/bin/sh它不是 POSIX shell,但 Bourne shell 仍然存在以下問題[ "$a" = "$b" ]

$ a='!' b='!'
$ [ "$a" = "$b" ]
test: argument expected

[ "x$a" = "x$b" ]由於沒有以 .[開頭的運算符,因此使用可以解決該問題x。另一種選擇是使用case

case "$a" in
 "$b") echo same;;
    *) echo different;;
esac

(引用是必要的$b,而不是圍繞$a)。

無論如何,它不是空值,也從來不是空值。人們在忘記引用變數時會遇到空值問題[,但這不是問題[

$ a= b='-o x'
[ $a = $b ]

預設值為$IFS

[ = -o x ]

這是對是否為非空字元串的測試=x但沒有任何前綴會有所幫助³[ x$a = x$b ]仍然會:[ x = x-o x ]這會導致錯誤,並且可能會變得更糟,包括 DoS 和帶有其他值的任意命令注入,例如bash

bash-4.4$ a= b='x -o -v a[`uname>&2`]'
bash-4.4$ [ x$a = x$b ]
Linux

正確的解決方案是始終引用

[ "$a" = "$b" ]   # OK in POSIX compliant [ / shells
[ "x$a" = "x$b" ] # OK in all Bourne-like shells

請注意,expr有類似(甚至更糟)的問題。

expr還有一個=運算符,儘管它用於測試兩個操作數在看起來像十進制整數時是否為相等的整數,或者在不是時排序相同。

在許多實現中,expr + = +, or expr '(' = ')'orexpr index = index不進行相等比較。expr "x$a" = "x$b"可以解決它進行字元串比較,但前綴 anx可能會影響排序(例如在具有排序元素的區域設置中x),並且顯然不能用於數字比較expr "0$a" = "0$b"不適用於比較負整數。expr " $a" = " $b" 適用於某些實現中的整數比較,但不適用於其他實現(因為a=01 b=1,有些會返回真,有些會返回假)。


¹ksh93是一個例外。in ksh93,[可以看作是一個保留字 in ,[ -t ]它實際上不同於var=-t; [ "$var" ], or 來自""[ -t ]or cmd='['; "$cmd" -t ]。這是為了保持向後兼容性,並且在重要的情況下仍然符合 POSIX。僅當-t它是文字時才在此處作為運算符,並ksh93檢測到您正在呼叫該[命令。

² ksh 添加了一個[[...]]條件表達式運算符,它有自己的語法解析規則(以及它自己的一些問題)來解決這個問題(在其他一些 shell 中也有,但有一些不同)。

³ 除非在參數擴展時不呼叫 split+glob,但zsh仍然可以進行空刪除,或者在全域禁用 split+glob 時在其他 shell 中使用set -o noglob; IFS=

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