bash globを文字列変数にする方法は?


14

システム情報

OS:OS X

bash:GNU bash、バージョン3.2.57(1)-release(x86_64-apple-darwin16)

バックグラウンド

タイムマシンで、すべてのgit / nodejsプロジェクトからディレクトリとファイルのセットを除外する必要があります。私のプロジェクトディレクトリはしている~/code/private/~/code/public/私はやってループのbashを使用しようとしているのでtmutil

問題

短縮版

計算された文字列変数を持っている場合k、forループの前または直前にどのようにグロブするのですか?

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

一重引用符はBashのシェル補間を停止するため、変数を二重引用符で囲むことをお勧めします。
トーマスN

@ThomasNいいえ、それは機能しません。kは計算された文字列であり、ループまでそのようにしておく必要があります。長いバージョンを確認してください。
ジョン・シウ

@ThomasNわかりやすくするために短いバージョンを更新しました。
ジョン・シウ

回答:


18

を使用して別の評価ラウンドを強制できますがeval、実際には必要ありません。(そしてeval、ファイル名に$。などの特殊文字が含まれていると、深刻な問題が発生し始めます。)問題は、グロブではなく、チルダの展開にあります。

ここで(*)のように、変数が引用符で囲まれていない場合、変数展開後にグロビングが発生ます。

$ x="/tm*" ; echo $x
/tmp

したがって、同じように、これはあなたがやったことに似ており、動作します:

$ 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代わりにフルパスを使用することです。

(*変数からのグロブの展開は、通常は望みのものではありません)


別物:

次のようにパターンをループするとき:

exclude="foo *bar"
for j in $exclude ; do
    ...

$exclude引用符で囲まれていないように、それは分割されており、この時点でグロブされていることに注意してください。したがって、現在のディレクトリにパターンに一致するものが含まれている場合、そのディレクトリに展開されます。

$ i="$HOME/public/foo"
$ exclude="*.launch"
$ touch $i/real.launch
$ for j in $exclude ; do           # split and glob, no match
    echo "$i"/$j ; done
/home/foo/public/foo/real.launch

$ touch ./hello.launch
$ for j in $exclude ; do           # split and glob, matches in current dir!
    echo "$i"/$j ; done
/home/foo/public/foo/hello.launch  # not the expected result

これを回避するには、分割された文字列の代わりに配列変数を使用します。

$ exclude=("*.launch")
$ exclude+=("something else")
$ for j in "${exclude[@]}" ; do echo "$i"/$j ; done
/home/foo/public/foo/real.launch
/home/foo/public/foo/something else

追加のボーナスとして、配列エントリには分割の問題なしに空白を含めることもできます。


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代わりに使用する必要があり、コマンドラインで引用符で囲まないで分割する必要がありますが、シェルによって誤って展開されないように引用符で囲む必要があります。~$dirsfind$pattern

(気にするなら-maxdepth、GNU findで遊んで検索の深さを制限できると思いますが、それは少し異なる問題です。)


あなたは唯一の答えfindですか?forループが複雑になっているため、私も実際にそのルートを探索しています。しかし、「-path」に問題があります。
ジョン・シウ

チルダ「〜」に関する情報は、メインの問題により直接的であるため、あなたに感謝します。最終的なスクリプトと説明を別の回答に掲載します。しかし、あなたにフルクレジット:D
ジョン・シウ

@JohnSiu、ええ、findを使用することが最初に思いついたものです。正確なニーズに応じて、それも使用できる場合があります。(または、より良い、いくつかの用途のため。)
ilkkachu

1
@kevinarpe、私は配列は基本的にはそのためのものですと思うし、そう、"${array[@]}"(引用符付き!)(参照文書化され、ここここにさらに分割することなく、明確な言葉などの要素にそれらを展開します)。
-ilkkachu

1
@sixtyfive、ウェル、[abc]標準の一部であるグロブパターンのような、?、私はそれがここにすべての彼らのカバーに行く必要はないと思います。
イルカチュウ

4

多くの場合、後で使用するために文字列ではなく配列として保存し、それを定義するときにグロビングを発生させることができます。あなたの場合、例えば:

k=(~/code/public/*/*.launch)
for i in "${k[@]}"; do

または後の例でevalは、いくつかの文字列が必要になります

dirs=(~/code/private/* ~/code/public/*)
for i in "${dirs[@]}"; do
    for j in $exclude; do
        eval "for k in $i/$j; do tmutil addexclusion \"\$k\"; done"
    done
done

1
どのように注意$excludeワイルドカードが含まれている、あなたが使用する前に無効にグロブに必要があると思いスプリット+グロブそれにオペレーターをし、それを復元$i/$jしていない使用しますevalが、使用"$i"/$j
ステファン・Chazelas

あなたとイルカチュウの両方が良い答えを与えます。しかし、彼の答えは問題を特定しました。彼に感謝します。
ジョン・シウ

2

@ilkkachuの回答により、主要な問題が解決されました。彼への完全な信用。

V1

ただし、excludewildcard(*)の有無にかかわらずエントリが含まれているため、それらがすべて存在しない場合があるため、のグロビング後に追加のチェックが必要です$i/$j。ここで私の調査結果を共有しています。

#!/bin/bash
exclude="
*.launch
.DS_Store
.classpath
.sass-cache
.settings
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="
$HOME/code/private/*
$HOME/code/public/*
"

# loop $dirs
for i in $dirs; do
    for j in $exclude ; do
        for k in $i/$j; do
            echo -e "$k"
            if [ -f $k ] || [ -d $k ] ; then
                # Only execute command if dir/file exist
                echo -e "\t^^^ Above file/dir exist! ^^^"
            fi
        done
    done
done

出力の説明

以下は、状況を説明するための部分的な出力です。

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/a.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/b.launch
    ^^^ Above file/dir exist! ^^^
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.DS_Store
    ^^^ Above file/dir exist! ^^^

上記は自明です。

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.classpath
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.sass-cache
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/.settings
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/Thumbs.db
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/bower_components
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/build
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/connect.lock
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/coverage
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/dist

exclude entry($j)にはワイルドカードがないため、上記が表示され$i/$j、プレーンな文字列連結になります。ただし、ファイル/ディレクトリは存在しません。

/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.js
/Volumes/HD2/JS/code/public/simple-api-example-ng2-express/e2e/*.map

上記$jは、ワイルドカードを含むexclude entry()として表示されますが、一致するファイル/ディレクトリがなく$i/$j、元の文字列を返すだけです。

V2

V2は一重引用符を使用evalshopt -s nullglobて、クリーンな結果を取得します。ファイル/ディレクトリの最終チェックは不要です。

#!/bin/bash
exclude='
*.launch
.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='
$HOME/code/private/*
$HOME/code/public/*
'

for i in $dirs; do
    for j in $exclude ; do
        shopt -s nullglob
        eval "k=$i/$j"
        for l in $k; do
            echo $l
        done
        shopt -u nullglob
    done
done

問題の1つは、でfor j in $excludeのglobが$exclude、その$exclude拡張時に拡張される可能性があることです(それを呼び出すとeval、トラブルが発生します)。for i in $dir、およびに対してグロビングを有効にしたいのですが、では有効にしfor l in $kませんfor j in $exclude。あなたset -fは後者の前set +fに、もう一方には欲しいでしょう。より一般的には、split + glob演算子を使用する前に調整する必要があります。いずれの場合も、のsplit + globは不要なecho $lので、$l引用符で囲む必要があります。
ステファンシャゼラス

@StéphaneChazelasはv1かv2ですか?v2では、excludeとの両方dirsが一重引用符で囲まれています(), so no globbing till eval`。
John Siu

グロビングは、リストコンテキスト内の引用符で囲まれていない変数の展開時に行われます(変数を引用符で囲まずに残す)は、スプリット+グロブ演算子と呼ばれることもあります。スカラー変数への代入にはグロビングはありません。foo=*そしてfoo='*'同じです。しかしecho $foo、そうでecho "$foo"はありません(などのシェルではbash、zsh、fish、rcなどのシェルで修正されています。上記のリンクも参照してください)。ここではないが、唯一の分割部分いくつかの場所で、他のみグロブ部分に、その演算子を使用します。
ステファンシャゼラス

@StéphaneChazelas情報ありがとうございます!!! いつか連れて行ってくれましたが、今はその懸念を理解しています。これは非常に貴重です!! ありがとうございました!!!
ジョン・シウ

1

zsh

exclude='
*.launch
.classpath
.sass-cache
Thumbs.db
...
'

dirs=(
~/code/private/*
~/code/public/*
)

for f ($^dirs/${^${=~exclude}}(N)) {
  echo $f
}

${^array}stringとして展開すること$array[1]string $array[2]string...です。$=var、変数(何か他のシェルはデフォルトでやる!)に単語分割を実行することですが$~var、あなたが一般的にそれらをしたくないとき、あなたが引用しなければならなかったと思います(デフォルトでも(変数に何か他のシェルグロブない$fで上他のシェル))。

(N)は、その展開の結果として生じるこれらの各グロブに対してnullglobをオンにするグロブ修飾子です$^array1/$^array2。これにより、グロブは一致しないときに何も展開されません。また、それはたまたま非グロブのようなもの~/code/private/foo/Thumbs.dbを1つに変えます。つまり、その特定のものが存在しない場合、それは含まれません。


これは本当にいいです。私はテストして働いています。ただし、一重引用符を使用する場合、zshは改行の影響を受けやすいようです。exclude囲まれている方法が出力に影響しています。
ジョン・シウ

@JohnSiu、そうそう、あなたは正しい。split + globの$^arrayようで、空の要素が確実に破棄されるようにするには、2つの別々のステップで行う必要があります(編集を参照)。これはのバグに少し似ていzshますが、メーリングリストで問題を取り上げます。
ステファンシャゼル

bashのv2を思い付きます。これはよりクリーンですが、それでもzshスクリプトほどコンパクトではありません。lol
John Siu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.