Bash

如何擴展波浪號〜作為變數的一部分?

  • October 21, 2017

當我打開 bash 提示符並輸入:

$ set -o xtrace
$ x='~/someDirectory'
+ x='~/someDirectory'
$ echo $x
+ echo '~/someDirectory'
~/someDirectory

我希望上面的第 5 行會發生+ echo /home/myUsername/someDirectory。有沒有辦法做到這一點?在我原來的 Bash 腳本中,變數 x 實際上是通過如下循環從輸入文件中的數據填充的:

while IFS= read line
do
   params=($line)
   echo ${params[0]}
done <"./someInputFile.txt"

儘管如此,我還是得到了類似的結果,echo '~/someDirectory'而不是echo /home/myUsername/someDirectory.

POSIX 標準強制按以下順序進行單詞擴展(強調是我的):

  1. 波浪號展開(參見波浪號展開)、參數展開(參見參數展開)、命令替換(參見命令替換)和算術展開(參見算術展開)應從頭到尾執行。請參閱令牌辨識中的第 5 項。
  2. 除非 IFS 為空,否則應在步驟 1 生成的欄位部分上執行欄位拆分(請參閱欄位拆分)。
  3. 除非 set -f 生效,否則應執行路徑名擴展(請參閱路徑名擴展)。
  4. 報價刪除(請參閱報價刪除)應始終最後執行。

這裡唯一讓我們感興趣的是第一個:正如您所見,波浪號擴展是在參數擴展之前處理的:

  1. shell 嘗試在 上進行波浪號擴展echo $x,但找不到波浪號,因此繼續。
  2. shell 嘗試對 進行參數擴展echo $x$x找到並擴展,命令行變為echo ~/someDirectory
  3. 處理繼續,波浪號擴展已經被處理,~字元保持原樣。

通過在分配 時使用引號$x,您明確要求不要擴展波浪號並將其視為普通字元。經常錯過的一件事是,在 shell 命令中,您不必引用整個字元串,因此您可以在變數賦值期間進行擴展:

user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$

並且您也可以在echo命令行上進行擴展,只要它可以在參數擴展之前發生:

user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$

如果由於某種原因您確實需要在$x不擴展的情況下影響變數的波浪號,並且能夠在echo命令中擴展它,則必須分兩次強制執行$x變數的兩次擴展:

user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$ 

但是,請注意,根據您使用此類結構的上下文,它可能會產生不必要的副作用。根據經驗,eval當您有其他方式時,寧願避免使用任何需要的東西。

如果您想專門解決波浪號問題而不是任何其他類型的擴展,那麼這種結構會更安全和便攜:

user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
>     x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$ 

此結構明確檢查前導是否存在,~如果找到,則將其替換為使用者主目錄。

根據您的評論,x="${HOME}/${x#"~/"}"對於未在 shell 程式中使用過的人來說,這可能確實令人驚訝,但實際上與我上面引用的相同 POSIX 規則相關聯。

正如 POSIX 標準所規定的,引號刪除發生在最後,參數擴展發生得非常早。因此,${#"~"}在評估外部引號之前進行評估和擴展。依次,如參數擴展規則中所定義:

在需要 word 的值的每種情況下(基於參數的狀態,如下所述),word 應進行波浪號擴展、參數擴展、命令替換和算術擴展。

#因此,必須正確引用或轉義運算符的右側以避免波浪擴展。

因此,換一種說法,當 shell 解釋器查看 時x="${HOME}/${x#"~/"}",他看到:

  1. ${HOME}並且${x#"~/"}必須擴大。
  2. ${HOME}擴展為$HOME變數的內容。
  3. ${x#"~/"}觸發嵌套擴展:"~/"被解析,但被引用,被視為文字1。您可以在此處使用單引號獲得相同的結果。
  4. ${x#"~/"}表達式本身現在被擴展,導致前綴~/$x.
  5. 上面的結果現在被連接起來了: 的擴展${HOME}、 文字/、 擴展${x#"~/"}
  6. 最終結果用雙引號括起來,在功能上防止分詞。我在這裡說的是功能性的,因為這些雙引號在技術上不是必需的(例如,請參見此處此處),但作為一種個人風格,一旦作業得到任何超出a=$b我通常會發現添加雙引號更清晰。

順便說一句,如果仔細觀察case語法,你會看到"~/"*依賴於我上面解釋的相同概念的構造x=~/'someDirectory'(這裡再次,雙引號和單引號可以互換使用)。

不要擔心這些東西在第一眼看起來很模糊(甚至可能在第二或以後的視線中!)。在我看來,參數擴展是使用子shell 時使用shell 語言程式時要掌握的最複雜的概念之一。

我知道有些人可能會強烈反對,但如果您想更深入地學習 shell 程式,我鼓勵您閱讀Advanced Bash 腳本指南:它教授 Bash 腳本,因此有很多擴展和鈴鐺–與 POSIX shell 腳本相比,吹口哨,但我發現它寫得很好,有很多實際的例子。一旦你管理了這一點,很容易在需要時將自己限制在 POSIX 功能中,我個人認為直接進入 POSIX 領域對於初學者來說是不必要的陡峭學習曲線(比較我的 POSIX 波浪號替換與@m0dular 的類似正則表達式的 Bash相當於了解我的意思;)!)。


1:這導致我在 Dash 中找到一個錯誤,該錯誤在此處未正確實現波浪號擴展(可使用 驗證x='~/foo'; echo "${x#~/}")。參數擴展對於使用者和 shell 開發者本身來說都是一個複雜的領域!

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