如何擴展波浪號〜作為變數的一部分?
當我打開 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 標準強制按以下順序進行單詞擴展(強調是我的):
- 波浪號展開(參見波浪號展開)、參數展開(參見參數展開)、命令替換(參見命令替換)和算術展開(參見算術展開)應從頭到尾執行。請參閱令牌辨識中的第 5 項。
- 除非 IFS 為空,否則應在步驟 1 生成的欄位部分上執行欄位拆分(請參閱欄位拆分)。
- 除非 set -f 生效,否則應執行路徑名擴展(請參閱路徑名擴展)。
- 報價刪除(請參閱報價刪除)應始終最後執行。
這裡唯一讓我們感興趣的是第一個:正如您所見,波浪號擴展是在參數擴展之前處理的:
- shell 嘗試在 上進行波浪號擴展
echo $x
,但找不到波浪號,因此繼續。- shell 嘗試對 進行參數擴展
echo $x
,$x
找到並擴展,命令行變為echo ~/someDirectory
。- 處理繼續,波浪號擴展已經被處理,
~
字元保持原樣。通過在分配 時使用引號
$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#"~/"}"
,他看到:
${HOME}
並且${x#"~/"}
必須擴大。${HOME}
擴展為$HOME
變數的內容。${x#"~/"}
觸發嵌套擴展:"~/"
被解析,但被引用,被視為文字1。您可以在此處使用單引號獲得相同的結果。${x#"~/"}
表達式本身現在被擴展,導致前綴~/
從$x
.- 上面的結果現在被連接起來了: 的擴展
${HOME}
、 文字/
、 擴展${x#"~/"}
。- 最終結果用雙引號括起來,在功能上防止分詞。我在這裡說的是功能性的,因為這些雙引號在技術上不是必需的(例如,請參見此處和此處),但作為一種個人風格,一旦作業得到任何超出
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 開發者本身來說都是一個複雜的領域!