@Kusalananda は基本的な問題とその解決方法をすでに説明しており、@ glenn jackmannからリンクされているBash FAQエントリも多くの有用な情報を提供しています。これらのリソースに基づいて、私の問題で何が起こっているのかを詳しく説明します。
物事を説明するために、各引数を別々の行に出力する小さなスクリプトを使用します(argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
「手動で」オプションを渡す:
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
予想どおり、部分-rnv
と--exclude='.*'
は、引用符で囲まれていない空白で区切られているため、2つの引数に分割されます(これは単語分割と呼ばれます)。
また、引用符は、周りのことに注意して.*
削除されています:単一引用符は、その内容を渡すためにシェルを伝える特別な解釈をせずに、しかし、引用符自体はコマンドに渡されません。
オプションを文字列として変数に保存する場合(配列を使用するのではなく)、引用符は削除されません。
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
これは、次の2つの理由によるものです。定義時に使用される二重引用符$OPTS
は、単一引用符の特別な扱いを妨げるため、後者は値の一部です。
$ echo $OPTS
--exclude='.*'
$OPTS
コマンドの引数として使用する場合、引用符はパラメーター展開の前に処理されるため、引用符は$OPTS
「遅すぎる」ことになります。
これは、(私の元の問題では)rsync
パターンの'.*'
代わりに除外パターン(引用符付き!)を使用することを意味し.*
ます-名前が単一引用符で始まり、その後にドットが続き、単一引用符で終わるファイルを除外します。明らかにそれは意図したものではありません。
回避策は、定義するときに二重引用符を省略することでした$OPTS
。
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
ただし、より複雑なケースでは微妙な違いがあるため、常に変数の割り当てを引用することをお勧めします。
@Kusalanandaが指摘したように、引用.*
も機能しなかったでしょう。パターンの展開を防ぐために引用符を追加しましたが、この特別なケースでは厳密には必要ありませんでした。
$ ./argtest.bash --exclude=.*
--exclude=.*
Bash はパターン展開を実行しますが、パターン--exclude=.*
はどのファイルにも一致しないため、パターンはコマンドに渡されます。比較:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
ただし、パターンを引用しないことは危険です。(何らかの理由で)ファイルが一致した--exclude=.*
場合、パターンが展開されるためです。
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
最後に、配列を使用して引用の問題を防ぐ理由を見てみましょう(配列を使用してコマンド引数を保存する他の利点に加えて)。
配列を定義するとき、単語分割と引用符の処理は期待どおりに行われます。
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
オプションをコマンドに渡すとき、構文を使用します"${ARRAY[@]}"
。これは、配列の各要素を個別の単語に展開します。
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*