タイトルの通り。
$ ls
a.tsv b.tsv
$ echo *.tsv
a.tsv b.tsv
$ echo *.txt
*.txt
最後のコマンド実行例を見ると、確かに該当するファイルが無い場合***.txt
という文字列1つ**がechoコマンドに渡されていることが分かる。
多分これは多くの人には嬉しくない挙動だろう。
さらに補足しておくと、ワイルドカード展開に限らずパス名展開の場合は全て、該当するファイルが無い場合パターン文字列が展開されずに残ってしまう。
$ ls
a.tsv b.tsv
$ echo ?.tsv
a.tsv b.tsv
$ echo ?.txt
?.txt
$ echo [a-z].tsv
a.tsv b.tsv
$ echo [a-z].txt
[a-z].txt
echoコマンドが実際はrmコマンドだったらと考えると、場合によっては良くないことが起こりそうである。
対処法
- 該当するファイルが無いパス名展開を「0個の文字列」として展開してほしい場合、
shopt -s nullglob
を.bashrcなどに書いておく - 該当するファイルが無いパス名展開をエラーにしてほしい場合、
shopt -s failglob
を.bashrcなどに書いておく
shopt -s nullglob
の場合こうなる。
$ ls
a.tsv b.tsv
$ shopt -s nullglob
$ echo *.txt
エラーにはならず、*.txt
の部分が存在しなかったのと同じ(この場合echo
が実行されたのと同じ)意味になる。
解除はshopt -u nullglob
。
shopt -s failglob
の場合こうなる。
$ ls
a.tsv b.tsv
$ shopt -s failglob
$ echo *.txt
-bash: no match: *.txt
エラーになり、コマンドは実行されない。
解除はshopt -u failglob
。
なお、List of shell options [Bash Hackers Wiki]によれば、shopt -s failglob
はbash 3.0-alpha以降でのみ使える。
zshの場合はどうなるの?
こうなる。
bash | zsh | |
---|---|---|
パターン文字列を残す | デフォルト | setopt NO_NOMATCH |
0個の文字列として展開 | shopt -s nullglob |
setopt NULL_GLOB |
エラーにする | shopt -s failglob |
デフォルト |
setopt NO_NOMATCH
の解除はunsetopt NO_NOMATCH
。
setopt NULL_GLOB
の解除はunsetopt NULL_GLOB
。
まとめ
shopt -s (null|fail)glob
を使うかzshに乗り換えるかしておくと事故らずに済みます。
2016/01/19追記: shopt -s (null|fail)glob
が起こすバグについて
shopt -s nullglob
またはshopt -s failglob
を使うと、一部の環境でタブ補完が動かなくなってしまう。
対象となる環境は、bashにzsh並みの高度な補完機能を提供するパッケージであるbash-completionをインストールしている環境。
対象となる環境では、failglobを有効にした状態でタブ補完を行うと次のようなエラーメッセージが出力されてタブ補完は実行できない。(nullglobを有効にしている場合はこのエラーメッセージすら出ない)
bash: no match: words[0]=${!ref}${COMP_WORDS[i]}
このバグはbash-completionの最新の開発版(master)では修正されているのだが、残念なことにこのバグが修正されている安定版は存在しない。
最新の安定版(2.1)でもこのバグは存在しており、結果としてbash-completionがインストールされている環境ではshopt -s nullglob
またはshopt -s failglob
はタブ補完機能を無効にしてしまう。
この問題を解決するには次のどちらかの方法を行えばよい。
- Install bash-completion:master for `shopt -s failglob` with TAB-completion.を参考にbash-completionの最新の開発版をソースコードからインストールする
- bash - nullglob disables pathname tab-completion - Stack Overflowを参考に問題を起こしている箇所にパッチを当てる
例として、後者の方法をUbuntu 14.04 LTSで行う方法を載せておく。
sudoedit /usr/share/bash-completion/bash_completion
--- /usr/share/bash-completion/bash_completion
+++ /usr/share/bash-completion/bash_completion
@@ -278,7 +278,7 @@
done
# Append word to current word
ref="$2[$j]"
- eval $2[$j]=\${!ref}\${COMP_WORDS[i]}
+ eval "$2[$j]=\${!ref}\${COMP_WORDS[i]}"
# Remove optional whitespace + word from line copy
line=${line#*"${COMP_WORDS[i]}"}
# Indicate new cword