LoginSignup
23
17

More than 5 years have passed since last update.

bashのワイルドカード展開は該当するファイルが無いと*が展開されずに残る

Last updated at Posted at 2016-01-05

タイトルの通り。

$ 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はタブ補完機能を無効にしてしまう。

この問題を解決するには次のどちらかの方法を行えばよい。

例として、後者の方法を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
23
17
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
17