タイトルの通り。
$ 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