説明と背景情報
OPの問題は、グロブ自体にはありませんでした-グロブ(パターン)が機能するためには、一部が一重引用符または二重引用符で囲まれた文字列でも機能するなどの特殊なパターン文字が機能し*
ますunquoted、OPが正しく行ったように彼の質問:
"$dir/"*.jar # OK, because `*` is unquoted
むしろ、問題はbash
の - やや驚くべき -パターンを展開しない(そのままにしておく)というデフォルトの動作であり、パターンが何にも一致しない場合、事実上、実際のファイルシステム項目を表さない文字列になります。
- 手元のケースでは、
"$dir"
たまたまファイルを含まないディレクトリに展開されたため*.jar
、渡された結果の文字列はリテラル( ) でjava
終わりました。これは、実際のファイルを参照していないため、質問で引用されているエラーが発生しました。 *.jar
'<value of $dir>/*.jar'
.jar
シェル オプションは、グロビングを制御します (正式にはパス名展開と呼ばれます)。
set -f
( shopt -so noglob
)はグロブを完全にオフにするため、通常は文字列がグロブとして扱われる原因となる引用符で囲まれていない文字列 (文字) がリテラルとして扱われます。
shopt -s nullglob
デフォルトの動作を、一致しないグロブを空の文字列に展開するように変更します。
shopt -s failglob
デフォルトの動作を、エラーを報告し、一致しない glob の場合に終了コードを 1 に設定するように変更します。手元にあるコマンドを実行することさえしません - 落とし穴については以下を参照してください。
- この議論とは関係のないグロビング関連のオプションが他にもあります。それらすべてのリストを表示するには、 を実行してください
{ shopt -o; shopt; } | fgrep glob
。説明については、 で名前で検索してman bash
ください。
堅牢なグロビング ソリューション
注:シェル オプションをグローバルに設定すると、現在のシェルに影響を与えます。これには問題があります。これは、サードパーティのコードが通常、デフォルトが有効であるという妥当な仮定を行うためです。したがって、シェル オプションを一時的に変更 (変更、アクションの実行、復元) するか、サブシェル ( (...)
)を使用して効果の変更をローカライズすることをお勧めします。
shopt -s nullglob
- ループ内の一致を列挙するのに役立ち
for
ます- 一致がない場合、ループに入らないようにします:
shopt -s nullglob # expand non-matching globs to empty string
for f in "$dir/"*.jar; do
# If the glob matched nothing, we never get here.
# !! Without `nullglob`, the loop would be entered _once_, with
# !! '<value of $dir>/*.jar'.
done
- ファイル名引数がない場合にデフォルトの動作をするコマンドの引数で使用すると、予期しない動作が発生する可能性があるため、問題があります。
shopt -s nullglob # expand non-matching globs to empty string
wc -c "$dir/"*.jar # !! If no matches, expands to just `wc -c`
グロブが何にも一致しない場合は、wc -c
が実行されますが、これは失敗せず、代わりに入力の読み取りを開始しstdin
ます (インタラクティブに実行すると、 で終了するまでインタラクティブな入力行を待つだけですCtrl-D)。
shopt -s failglob
- 特定のエラー メッセージを報告するのに役立ちます。特に、 と組み合わせて
set -e
、グロブが何も一致しない場合にスクリプトを自動的に中止させる場合に役立ちます。
set -e # abort automatically in case of error
shopt -s failglob # report error if a glob matches nothing
java -jar "$dir/"*.jar # script aborts, if this glob doesn't match anything
|| <command in case of failure>
エラーの特定の原因を知る必要がある場合、およびイディオムと組み合わせると問題が発生します。
shopt -s failglob # report error if a glob matches nothing
# !! DOES NOT WORK AS EXPECTED.
java -jar "$dir/"*.jar || { echo 'No *.jar files found.' >&2; exit 1; }
# !! We ALWAYS get here (but exit code will be 1, if glob didn't match anything).
failglob
on を使用するとbash
、グロビングが失敗した場合に手元のコマンドを実行することさえないため、||
句も実行されず、全体的な実行が続行されます。
失敗したグロブによって終了コードが に設定され1
ますが、一致しないグロブによる失敗とコマンドによって報告された失敗 (グロビングが成功した後) を区別することはできません。
シェル オプションを変更しない代替ソリューション:
もう少し努力すれば、一致しないグロブを独自にチェックできます。
アドホック:
glob="$dir/*.jar"
[[ -n $(shopt -s nullglob; echo $glob) ]] ||
{ echo 'No *.jar files found.' >&2; exit 1; }
java -jar $glob
$(shopt -s nullglob; echo $glob)
サブシェルが一致するファイル名を返すか、何も一致しない場合は空の文字列を返すようにnullglob
、グロブを設定して展開します。その出力は、コマンド置換 ( ) のおかげで、文字列が空かどうかをテストする に渡されます。これにより、全体的な条件の終了コードは、何かが一致した (終了コード) か一致しなかった (終了コード) かを反映します。echo
$(...)
-n
[[ ... ]]
0
1
コマンド置換内のコマンドはすべてサブシェルで実行されることに注意してください。これにより、 の効果がそのshopt -s nullglob
サブシェルにのみ適用され、グローバルな状態が変更されないことが保証されます。
また、変数割り当ての右側全体が二重引用符で囲まれていることにも注意してください。これは、変数が定義されたときではなく、後で参照glob="$dir/*.jar"
されるときにグロビングに関する引用符が重要であることを示しています。引用符で囲まれていない later への参照により、文字列全体がグロブとして解釈されることが保証されます。$glob
小さなヘルパー関数を使用:
# Define simple helper function.
exists() { [[ -e $1 ]]; }
glob="$dir/*.jar"
exists $glob || { echo 'No *.jar files found.' >&2; exit 1; }
java -jar $glob
ヘルパー関数は、シェルが関数の呼び出し時にグロビングを適用し、グロビング (パス名展開)の結果を引数として渡すことを利用します。次に、関数は、最初の結果の引数 (存在する場合) が既存のアイテムを参照しているかどうかをテストし、それに応じて終了コードを設定します (これは、nullglob
たまたま有効かどうかに関係なく機能します)。