Bash

如何使 bash glob 成為字元串變數?

  • March 7, 2022

系統資訊

作業系統: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

(我認為您可以使用-maxdepthGNU find 來限制搜尋的深度,如果您願意的話,但這是一個不同的問題。)

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