bash には glob というものがあります。glob ってなによっていう人も ls *.sh
とかを展開する bash の機能ですよっていうと分かるかと思います。
この glob の機能って多用されますがあまりマニュアルとか読んだ人もいないと思うので、簡単にまとめてみます。
1. Pattern Matching
Glob のパターンマッチングに使用できる文字のパターンっていうのは、通常、次の文字です。
*
: 何にでもマッチする?
: 任意の一文字にマッチする[...]
:[
と]
の間に記述された任意の文字にマッチする。これはちょっとややこしいので、もうちょっとまとめます。
[...]
[...]
の ...
には通常として文字の集合を指定できますが、多少表現力のあるものも指定できます。
次のように、ハイフン-
で指定された範囲のみにマッチさせたりできますし、
$ ls test[1-4].txt test1.txt test2.txt test3.txt test4.txt
[
の直後に !
あるいは ^
を指定することで、文字集合を除外させることもできます。
$ ls test[!1-4].txt test5.txt test6.txt test7.txt test8.txt test9.txt $ ls test[^1-4].txt test5.txt test6.txt test7.txt test8.txt test9.txt
[...]
の中には、文字クラス名を [:class:]
の形で指定できます。[:alnum:]
とか [:punct:]
とかですね。
$ ls test[[:digit:]].txt
2. Glob の拡張表現
だいたい *.txt
とかで用が足りるときは多いのですが、「このファイルとはマッチさせたくない」(not 条件) とか、正規表現を使いたいとか、そういう要望はデフォルトの Pattern Matching 用の文字では満たせません。このままでは、毎日歯軋りをしながら眠りにつくしかない。
Bash にはこの Pattern Matching の機能を、以下のようにして拡張することができます。
$ setopt -s extglob
これにより、正規表現の表現力や not 表現が使えるようになります。
?(pattern-list)
:pattern-list
に記述されたパターン 0 回または 1 回の出現とマッチ。*(pattern-list)
:pattern-list
に記述されたパターンの 0 回以上の出現とマッチ。*(pattern-list)
:pattern-list
に記述されたパターンの 1 回以上の出現とマッチ。@(pattern-list)
:pattern-list
に記述されたパターンの 1 回の出現とマッチ。!(pattern-list)
:pattern-list
に記述されたパターンのいずれでもないものとマッチ。
$ shopt -s extglob $ ls test+([[:digit:]]).txt test1.txt test10.txt test2.txt test3.txt test4.txt test5.txt test6.txt test7.txt test8.txt test9.txt $ ls test!(1|2|5|8|9).txt test10.txt test3.txt test4.txt test6.txt test7.txt
特に、not 表現が記述できるようになるのは大きい。
例えば、"カレントディレクトリの hoge.sh
以外のスクリプト" は次のようにして取得できます。
$ ls !(hoge).sh
3. ls | grep
パターンには気をつけよう
シェルスクリプトの中に ls | grep
パターンがあるときは、glob に置き換えられないかを検討する価値があります。
ls | grep
パターンが良くないケースとしては、ファイル名にスペース等が入っているような場合が挙げられます。
たとえば、hello world.txt
hello-world.txt
が存在するディレクトリがあるとしましょう。
$ ls 'hello world.txt' hello-world.txt
このディレクトリに対して、以下のスクリプトを実行してみます。
#!/bin/bash for f in $(ls | grep \\.txt$); do echo $f done
結果はこちらで、"hello world.txt" が 2 行に分割されてしまっていることがわかります。
hello world.txt hello-world.txt
ls | grep
の結果がスペースで分割されてしまってるわけですね。
このような問題は、Glob にすることで解決します。
for f in *.txt; do echo $f done # 実行すると: hello world.txt hello-world.txt
hoge.txt
以外を処理したいのであれば、拡張 Glob を使用して !(hoge).txt
にすれば良いですし、Glob の使いどころは非常に多いと思います。