私は 2 つのテキスト ファイルがあるディレクトリにいます:
$ touch test1.txt
$ touch test2.txt
何らかのパターンを使用して(Bash で)ファイルを一覧表示しようとすると、次のように動作します。
$ ls test?.txt
test1.txt test2.txt
$ ls test{1,2}.txt
test1.txt test2.txt
ただし、 で囲まれたコマンドによってパターンが生成された場合$()
、機能するパターンは 1 つだけです。
$ ls $(echo 'test?.txt')
test1.txt test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory
ここで何が起こっているのでしょうか? なぜパターンが{1,2}
機能しないのでしょうか?
答え1
これは2つの要素を組み合わせたものです。まず、括弧の展開はファイル名に一致するパターンではありません。純粋にテキストの置換です。`a[bc]d` (角括弧) と `a{b,c}d` (中括弧) の違いは何ですか?2 番目に、コマンド置換の結果を二重引用符 ( ls $(…)
) の外側で使用すると、パターン マッチング (および単語分割: 「split+glob」演算子) のみが実行され、完全な再解析は実行されません。
を使用するとls $(echo 'test?.txt')
、コマンドはecho 'test?.txt'
文字列 を出力しますtest?.txt
(最後に改行があります)。コマンド置換の結果は文字列 にtest?.txt
なります (コマンド置換は末尾の改行を削除するため、最後の改行はありません)。この引用符で囲まれていない置換では単語が分割され、test?.txt
空白文字 (より正確には 内の文字) がないため、単一の文字列 で構成されるリストが生成されます$IFS
。次に、この 1 要素リストの各要素に対して条件付きワイルドカード展開が実行され、?
文字列にワイルドカード文字があるため、ワイルドカード展開が行われます。パターンはtest?.txt
少なくとも 1 つのファイル名に一致するため、リスト要素はパターンに一致するファイル名のリストに置き換えられ、と をtest?.txt
含む 2 要素のリストが生成されます。最後に、が 2 つの引数とで呼び出されます。test1.txt
test2.txt
ls
test1
test2
を使用するとls $(echo 'test{1,2}')
、コマンドはecho 'test{1,2}'
文字列 を出力しますtest{1,2}
(最後に改行が付きます)。コマンド置換の結果は文字列 にtest{1,2}
なります。この引用符で囲まれていない置換では単語が分割され、単一の文字列 からなるリストが生成されますtest{1,2}
。次に、この 1 要素リストの各要素に対して条件付きワイルドカード展開が行われますが、文字列にはワイルドカード文字がないため、何も行われません (要素はそのまま残ります)。したがって、 はls
単一の引数 で呼び出されますtest{1,2}
。
比較のために、 の場合の動作を以下に示しますls $(echo test{1,2})
。 コマンドはecho test{1,2}
文字列 を出力しますtest1 test2
(最後に改行あり)。 コマンド置換の結果は文字列 になります (最後に改行なし)。 この引用符で囲まれていない置換では単語分割が行われ、2 つの文字列とtest1 test2
が生成されます。 次に、どちらの文字列にもワイルドカード文字が含まれていないため、そのまま残され、 が2 つの引数とで呼び出されます。test1
test2
ls
test1
test2
答え2
展開の順序は、中括弧展開、チルダ展開、パラメータと変数の展開、算術展開、コマンド置換(左から右に実行)、単語分割、ファイル名展開です。
コマンド置換後は括弧の展開は行われません。eval を使用して、もう一度展開を強制することができます。
eval echo $(echo '{1,2}lala')
結果は次のとおりです:
1lala 2lala
答え3
この問題は に特有のものであり、 では中括弧の展開をファイル名の展開 (グロブ) から分離し、他のすべての展開の前に最初に実行することにしたbash
ためです。bash
マンページからbash
:
展開の順序は、中括弧展開、チルダ展開、パラメータと変数の展開、算術展開、コマンド置換(左から右に実行)、単語分割、パス名展開です。
あなたの例では、bash
コマンド置換 ( $(echo ...)
) を実行した後にのみ中括弧が表示されますが、これでは遅すぎます。
これは、パス名展開 (グロブ) の直前 (または一部はその一部として) に中括弧展開を実行する他のすべてのシェルとは異なります。これには、csh
中括弧展開が最初に発明された場所が含まれますが、これに限定されません。
$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt test2.txt
$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2
後者の例はcsh
、、、、またはでも同様です。zsh
ksh93
mksh
fish
また、ブレースの拡張にも注意してくださいグロビングの一環としてglob(3)
は、ライブラリ関数 (少なくとも Linux およびすべての BSD 上)経由でも利用でき、他の独立した実装でも利用できます(例: perl の場合: perl -le 'print join " ", <test{1,2}.txt>'
)。
なぜそれが違ったやり方で行われたのかについては、bash
おそらく何らかの背景があるのでしょうが、参考までに、私は論理的な説明を見つけることができず、事後的な合理化はすべて説得力がないと思います。
答え4
してみてください:::
ls $(エコーテスト{1,2}\.txt)
バックスラッシュを使用。これで動作します。また、前の投稿者が言ったように、引用符を削除します。ドットはパターンを一致させるためのものではなく、ここでは文字通りピリオドとして解釈されます。