web-dev-qa-db-ja.com

ワイルドカード展開とブレース展開を組み合わせたbash

ワイルドカードと中かっこ内で指定された拡張子のコレクションを含む文字列を展開しようとしています。以下の例が示すように、何も機能しないようです。変数firstListは問題なく拡張されますが、secondListthirdListfourthListは適切に拡張されません。 evalのさまざまなバージョンも試しましたが、どれも機能しません。どんな助けでもいただければ幸いです

#!/bin/bash
touch a.ext1
touch b.ext1
firstList='*.ext1'
ls  $firstList
touch a.ext2
touch b.ext2
secondList='*.{ext1,ext2}'
ls $secondList 
ls '$secondList'
ls "$secondList"
thirdList=*.{ext1,ext2}
ls $thirdList  
ls '$thirdList'
ls "$thirdList"
fourthList="*.{ext1,ext2}"
ls $fourthList
ls '$fourthList'
ls "$fourthList"
8
Leo Simon

Shellは、引用符で囲まれていない場合にのみ*を展開します。引用符を付けると、シェルによる展開が停止します。

また、ブレース展開は、シェルによって展開されるために引用符で囲まれていない必要があります。

この作業(エコーを使用してシェルの動作を確認します):

$ echo *.{ext1,ext2}
a.ext1 b.ext1 a.ext2 b.ext2

他の名前のファイルがあったとしても:

$ touch {a,b}.{ext1,ext2} {c,d}.{ext3,ext4} none
ls
a.ext1  a.ext2  b.ext1  b.ext2  c.ext3  c.ext4  d.ext3  d.ext4  none

$ echo *.{ext1,ext2}
a.ext1 b.ext1 a.ext2 b.ext2

なぜそれが機能するのですか?

それが機能する理由を理解することが重要です。それは拡張の順序のためです。最初に「ブレース展開」、その後(最後の展開)「パス名展開」(別名glob-expansion)。

Brace --> Parameter (variable) --> Pathname

「パス名拡張」を少しの間オフにすることができます:

$ set -f
$ echo *.{ext1,ext2}
*.ext1 *.ext2

「パス名拡張」は、2つの引数*.ext1および*.ext2を受け取ります。

$ set +f
$ echo *.{ext1,ext2}
a.ext1 b.ext1 a.ext2 b.ext2

問題は、ブレース展開に変数を使用できないことです。
以前に何度も説明されてきましたが、 「ブレース拡張」内で変数を使用する

「変数拡張」の結果である「ブレース拡張」を拡張するには、evalを使用してコマンドラインをシェルに再送信する必要があります。

$ list={ext1,ext2}
$ eval echo '*.'"$list"

ブレース->変数->グロブ|| ->Brace->変数->Glob
........ここで引用-> ^^^^^^ ||評価^^^^^^^^^^^^^^^^^^^^^^^^^

ファイル名の値はevalの実行問題を引き起こしません:

$ touch 'a;date;.ext1'
eval echo '*.'"$list"
a;date;.ext1 a.ext1 b.ext1 a.ext2 b.ext2

しかし、$listの値は安全ではない可能性があります。ただし、$listの値はスクリプトライターによって設定されます。スクリプトライターはevalの制御下にあります:$listに外部から設定された値を使用しないでください。これを試して:

#!/bin/bash
touch {a,b,c}.ext{1,2}
list=ext{1,2}
eval ls -l -- '*.'"$list"

より良い代替案。

代替(evalなし)は、 Bashの「拡張パターン」 を使用することです。

#!/bin/bash
shopt -s extglob
list='@(ext1|ext2)'
ls -- *.$list

注:両方のソリューション(評価とパターン)(記述どおり)は、スペースまたは改行のあるファイル名に対して安全であることに注意してください。ただし、$listは引用符で囲まれていないか、evalが引用符を削除するため、スペースを含む$listの場合は失敗します。

7
user79743

考慮してください:

secondList='*.{ext1,ext2}'
ls $secondList 

問題は、ブレース展開が行われることですbefore変数展開。つまり、上記ではブレースの展開は行われません。

これは、bashが最初にコマンドラインを表示するとき、中かっこがないためです。 secondListを展開した後では、手遅れです。

以下が機能します:

$ s='*'
$ ls $s.{ext1,ext2}
a.ext1  a.ext2  b.ext1  b.ext2

ここでは、コマンドラインに中かっこが付いているため、中かっこ展開を最初のステップとして実行できます。その後、$sの値が(変数展開)に代入され、最後にパス名展開が実行されます。

ドキュメンテーション

man bashは、展開の順序を説明しています。

展開の順序は次のとおりです。チルダ展開、パラメーターと変数の展開、算術展開、およびコマンド置換(左から右に実行)。単語分割;およびパス名の展開。

2
John1024