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

システム情報

OS:OS X

bash:GNU bash、バージョン3.2.57(1) -リリース(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でのシェル補間を停止するため、変数を二重引用符で囲んでみてください。
  • @ThomasNいいえ、機能しません。kは計算された文字列であり、staが必要ですyループまでそのように。長いバージョンを確認してください。
  • @ThomasN短いバージョンを更新してわかりやすくしました。

回答

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の明確に文書化されています

展開の順序は次のとおりです。brace展開、tilde展開、パラメーターおよび変数展開、…

チルダ展開は変数展開の前に行われるため、変数内のチルダは展開されません。簡単な回避策は、代わりに$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 

ivを使用する必要があります以前と同じ理由で、~の代わりにid = “09073477e5”>

を使用し、$dirsの引用符を外す必要があります。findコマンドラインで分割されるため、$patternは引用符で囲んで、シェルによって誤って展開されないようにする必要があります。

(GNUfindで-maxdepthを試して、気になる場合は検索の深さを制限できると思いますが、それは「少し別の問題です。)

コメント

  • findの答えはあなたですか? forループが複雑になっているので、私も実際にそのルートを探索しています。しかし、’ -path ‘に問題があります。
  • チルダに関する情報としてのクレジット’〜’は主な問題により直接的です。最終的な台本と説明は別の回答で投稿します。しかし、あなたへの完全な信用:D
  • @JohnSiu、ええ、findを使用することが最初に頭に浮かんだものでした。正確なニーズによっては、それも使用できる場合があります。 (または、一部の用途ではさらに良いです。)
  • @kevinarpe、配列は基本的にそれだけを目的としていると思います。そうです、"${array[@]}"(引用符付き! )が文書化されており(ここおよびここを参照)、それらをさらに分割します。
  • @sixtyfive、まあ、[abc]グロブパターンの標準部分です。 div id = “716cf7cb1f”> 、私は’ここでそれらすべてをカバーする必要があるとは思わない’ 。

回答

文字列ではなく配列として保存して、後で使用することができます。定義するときにグロブを発生させます。たとえば、次のようになります。

 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  

コメント

  • $excludeにワイルドカードが含まれていることに注意してください。’ dは、 split + glob 演算子を使用する前にグロブを無効にし、$i/$jおよび使用しないために復元する必要があります。 evalただし、"$i"/$j
  • を使用します

  • あなたとilkkachuの両方が良い答えを出します。しかし、彼の答えが問題を特定しました。

回答

@ilkkachuの回答により、グロブの主な問題が解決しました。彼の功績です。

V1

ただし、excludeにはワイルドカード(*)の有無にかかわらずエントリが含まれているため、存在しない場合があります。全体として、$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 

上記は除外エントリ($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)として表示されます。ワイルドカードですが、ファイルとディレクトリが一致しません。$i/$jのグロブは、元の文字列を返すだけです。

V2

V2は一重引用符を使用し、 evalおよびshopt -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 

コメント

回答

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)は、その$^array1/$^array2 nullglob をオンにするグロブ修飾子です。

拡張。これにより、グロブが一致しない場合はゼロに拡張されます。また、~/code/private/foo/Thumbs.dbのような非グロブが1つになります。つまり、その特定のものが存在しない場合は、含まれていません。

コメント

  • これは本当に素晴らしいです。テストして動作しました。ただし、zshは改行に対してより敏感であるようです。一重引用符を使用します。excludeの囲み方が出力に影響します。
  • @JohnSiu、そうです、あなたは’正解です。空の要素を確実に破棄するには、split + globと$^arrayを2つの別々の手順で実行する必要があるようです(編集を参照)。これは少し似ています。 zshのバグ、’メーリングリストで問題を提起します。
  • v2を思いつきます。 bashの場合、よりクリーンですが、zshスクリプトほどコンパクトではありません。lol

回答

古い投稿しましたが、ここでつまずいたので、これが他の人に役立つかもしれないと思った:)

bashにはkinda-glob()関数があります…これはcompgen -Gと呼ばれます。行ごとに値を出力するため、動作するにはreadarrayが必要です。

これを試してください:

i="~/code/public/*" j="*.launch" k=$i/$j # $k="~/code/public/*/*.launch" readarray -t files < <(compgen -G "$k") # $k is globbed here for i in "${files[@]}" do echo "Found -$i-" done 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です