$() 内のコマンドによって印刷された {1,2} が補間されないのはなぜですか?

$() 内のコマンドによって印刷された {1,2} が補間されないのはなぜですか?

私は 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.txttest2.txtlstest1test2

を使用すると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 つの引数とで呼び出されます。test1test2lstest1test2

答え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、、、、またはでも同様です。zshksh93mkshfish

また、ブレースの拡張にも注意してくださいグロビングの一環としてglob(3)は、ライブラリ関数 (少なくとも Linux およびすべての BSD 上)経由でも利用でき、他の独立した実装でも利用できます(例: perl の場合: perl -le 'print join " ", <test{1,2}.txt>')。

なぜそれが違ったやり方で行われたのかについては、bashおそらく何らかの背景があるのでしょうが、参考までに、私は論理的な説明を見つけることができず、事後的な合理化はすべて説得力がないと思います。

答え4

してみてください:::

ls $(エコーテスト{1,2}\.txt)

バックスラッシュを使用。これで動作します。また、前の投稿者が言ったように、引用符を削除します。ドットはパターンを一致させるためのものではなく、ここでは文字通りピリオドとして解釈されます。

関連情報