シェルスクリプトのタブ文字と変数展開

先日、シェルスクリプトの動作でハマったので書き留めておく。

やりたいのはこういうことだ。

log.txt

aaa	bbb
cccddd
eee	fff
ggg	hhh

こういうlogファイルがあり、「aaa bbb」と「eee fff」をgrepしたかった。
ちなみに、「aaa bbb」、「eee fff」、「ggg hhh」の間にあるのはタブ文字だ。

タブ文字の入力の仕方

まず試しにターミナル上で、
$ grep aaa"\t"bbb log.txt
とやってみたが、引っかからない。
ターミナル上からタブ文字を入力するには、"\t"ではダメで、
$ grep aaa$'\t'bbb log.txt
のように $'\t' とやって、シェルの機能でタブ文字に置換してもらわないといけない。
もしくはターミナル上で「Ctrl+V, タブキー」を打つとタブ文字が入力できる。
$ grep 'aaa bbb' log.txt

参考: http://d.hatena.ne.jp/iww/20120915/asciicode

タブ文字(あるいはスペース)の変数展開

現実は悲しいことにgrepするだけの処理だけではなくgrepしたあとawkで集計しメールするといったことをやる必要がある。
ただここでは話を簡単にするためにgrepするだけのシェルスクリプトを用意する。
そしてgrepする文字はあとで変更可能なように変数に入れておく。
grep.sh

#!/bin/bash
INCLUDE='aaa	bbb\|eee	fff'
grep $INCLUDE log.txt

grepでOR検索する際には「\|」で区切ると出来る。
さて、grep.shを実行する。zshだとちゃんと動くが、bashだと動かない。

bash-3.2$ /bin/sh ./grep.sh
grep: bbb\|eee: No such file or directory
grep: fff: No such file or directory
log.txt:aaa	bbb

エラーがおきている。なにがおきているのだろう。
「-x」オプションをつけて、動作をみてみよう。

bash-3.2$ /bin/sh -x ./grep.sh
+ INCLUDE='aaa	bbb\|eee	fff'
+ grep aaa 'bbb\|eee' fff log.txt
grep: bbb\|eee: No such file or directory
grep: fff: No such file or directory
log.txt:aaa	bbb

2行目がおかしい。本当は、「grep 'aaa bbb\|eee fff' log.txt」となってほしいが、そうなっていない。
ではどうすればいいか。
grep.sh の
grep $INCLUDE log.txt

grep "$INCLUDE" log.txt
に修正すればいい。
そうすればきちんと動く

bash-3.2$ /bin/sh -x ./grep.sh
+ INCLUDE='aaa	bbb\|eee	fff'
+ grep 'aaa	bbb\|eee	fff' log.txt
aaa	bbb
eee	fff

なんでこういう動作になるかというと、Bashでは展開する変数をクォートしなければ単語分割が起きるからだ。
以下の記事に詳しく書かれている。
http://qiita.com/uasi/items/82b7708d5da213ba7c31

まとめ

Rubyで書いたほうがはやい。