如何使 bash glob 成為字元串變數?
系統資訊
作業系統:OS X
bash:GNU bash,版本 3.2.57(1)-release (x86_64-apple-darwin16)
背景
我希望時間機器從我的所有 git/nodejs 項目中排除一組目錄和文件。我的項目目錄在裡面*
~/code/private/
,~/code/public/
*所以我正在嘗試使用 bash 循環來執行tmutil
.問題
簡潔版本
如果我有一個計算的字元串變數
k
,我如何在 for 循環中或之前使它成為 glob:i='~/code/public/*' j='*.launch' k=$i/$j # $k='~/code/public/*/*.launch' for i in $k # I need $k to glob here do echo $i done
在下面的長版本中,您將看到
k=$i/$j
. 所以我不能硬編碼for循環中的字元串。長版
#!/bin/bash exclude=' *.launch .classpath .sass-cache Thumbs.db bower_components build connect.lock coverage dist e2e/*.js e2e/*.map libpeerconnection.log node_modules npm-debug.log testem.log tmp typings ' dirs=' ~/code/private/* ~/code/public/* ' for i in $dirs do for j in $exclude do k=$i/$j # It is correct up to this line for l in $k # I need it glob here do echo $l # Command I want to execute # tmutil addexclusion $l done done done
輸出
它們不是萬用字元。不是我想要的。
~/code/private/*/*.launch ~/code/private/*/.DS_Store ~/code/private/*/.classpath ~/code/private/*/.sass-cache ~/code/private/*/.settings ~/code/private/*/Thumbs.db ~/code/private/*/bower_components ~/code/private/*/build ~/code/private/*/connect.lock ~/code/private/*/coverage ~/code/private/*/dist ~/code/private/*/e2e/*.js ~/code/private/*/e2e/*.map ~/code/private/*/libpeerconnection.log ~/code/private/*/node_modules ~/code/private/*/npm-debug.log ~/code/private/*/testem.log ~/code/private/*/tmp ~/code/private/*/typings ~/code/public/*/*.launch ~/code/public/*/.DS_Store ~/code/public/*/.classpath ~/code/public/*/.sass-cache ~/code/public/*/.settings ~/code/public/*/Thumbs.db ~/code/public/*/bower_components ~/code/public/*/build ~/code/public/*/connect.lock ~/code/public/*/coverage ~/code/public/*/dist ~/code/public/*/e2e/*.js ~/code/public/*/e2e/*.map ~/code/public/*/libpeerconnection.log ~/code/public/*/node_modules ~/code/public/*/npm-debug.log ~/code/public/*/testem.log ~/code/public/*/tmp ~/code/public/*/typings
您可以使用 強制進行另一輪評估
eval
,但這實際上不是必需的。(並且eval
在您的文件名包含特殊字元(如 .)的那一刻開始出現嚴重問題$
。)問題不在於萬用字元,而在於波浪號擴展。如果變數未加引號,則在變數擴展後發生通配,如下所示(*):
$ x="/tm*" ; echo $x /tmp
不帶引號的擴展發生的另一件事是分詞,如果有問題的模式包含 中的字元
IFS
,通常是空格,這將是一個問題。為防止出現此問題,需要通過設置IFS
為空字元串來禁用分詞。因此,同樣,這與您所做的類似,並且有效:
$ IFS= $ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch $ i="$HOME/public/*"; j="*.launch"; k="$i/$j" $ echo $k /home/foo/public/foo/x.launch
但是使用波浪號它不會:
$ i="~/public/*"; j="*.launch"; k="$i/$j" $ echo $k ~/public/*/*.launch
這清楚地記錄在 Bash 中:
展開順序為:大括號展開;波浪號擴展,參數和變數擴展,…
波浪號擴展發生在變數擴展之前,因此變數內部的波浪號不會擴展。簡單的解決方法是使用
$HOME
或使用完整路徑。(* 從變數擴展 glob 通常不是您想要的)
另一件事:
當您遍歷模式時,如下所示:
exclude="foo *bar" for j in $exclude ; do ...
請注意,正如未引用的那樣
$exclude
,它既是拆分的,也是全域的。因此,如果目前目錄包含與模式匹配的內容,則將其擴展為:$ IFS= $ i="$HOME/public/foo" $ exclude="*.launch" $ touch $i/real.launch $ for j in $exclude ; do # glob, no match printf "%s\n" "$i"/$j ; done /home/foo/public/foo/real.launch $ touch ./hello.launch $ for j in $exclude ; do # glob, matches in current dir! printf "%s\n" "$i"/$j ; done /home/foo/public/foo/hello.launch # not the expected result
要解決此問題,請使用數組變數而不是拆分字元串:
$ IFS= $ exclude=("*.launch") $ exclude+=("*.not this") $ for j in "${exclude[@]}" ; do printf "%s\n" "$i"/$j ; done /home/foo/public/foo/real.launch /home/foo/public/foo/some file.not this
儘管請注意,如果模式不匹配任何內容,則預設情況下它們將保持原樣。因此,如果目錄為空,
.../*.launch
則會列印等。
find -path
如果您不介意目標文件應該是什麼目錄級別,則可以使用. 例如,查找以 結尾的任何路徑/e2e/*.js
:$ dirs="$HOME/public $HOME/private" $ pattern="*/e2e/*.js" $ find $dirs -path "$pattern" /home/foo/public/one/two/three/e2e/asdf.js
出於與以前相同的原因,我們必須使用
$HOME
而不是,並且需要在命令行上不加引號以便將其拆分,但應該加引號以防止外殼意外擴展。~``$dirs``find``$pattern
(我認為您可以使用
-maxdepth
GNU find 來限制搜尋的深度,如果您願意的話,但這是一個不同的問題。)