すべての道はUNIXに通ず?
こんにちは。二卵性双生児(男女)でAB 型というなかなかレアな出生歴を持つ kyagiです。カードダースのキャラだったらプレミアものになっていたかもしれません。現実の人生は平凡そのものを歩んでいます。
最近シェルスクリプト内でのラッパ化(wrapper)を行う機会があったのですがそこでちょっとした落し穴にハマッてしまいました。今回はその落し穴についてのお話と最後に小咄をひとつ。
現在のコマンドを表示かつ終了ステータスのチェックを行うために下記のようなexecute() というラッパ関数を作成したのですが、これだと実行するコマンドラインにパイプ「|」やリダイレクト「>」がまじるとエラーになってしまいます。
#!/bin/sh
execute ()
{
echo "Executing $command"
$command
if [ $? -ne 0 ]; then
echo "ERROR: $command"
exit 1
fi
}
# OK
command='ls'
execute
# NG
command='ls -l | awk "{print $NF}"'
execute
# NG
command='echo "hello" >> outputfile'
execute
どうしたものかと 30分考えた挙句、 bash の eval という機能を思いだしました。evalは受け取った引数を結合してコマンドラインとして実行してくれます。すると、これがどうやらビンゴ!だったみたいでうまくいくようになりました。
$ diff a.sh b.sh
6c6
< $command
---
> eval $command
[kyagi@dhcp-0234 labo]$
ラッパ化の話はこれで終わりですが最後に雑談系の小咄をもうひとつ。
UNIXにみる世代間の断絶という興味深い記事がありますが、ふと世代別でプログラムの書き方はどう異なってくるのかと思いました。ここではkyagiが独断と偏見のもとシェルスクリプトのループ(0から9までを出力する)の書き方を、世代別でカテゴリ化してみます。
・旧人類(古き良き expr を使う)
$ i=0; while [ $i -lt 10 ]; do echo $i; i=`expr $i + 1`; done
・新人類と旧人類の中間(expr から let に移行)
$ i=0; while [ $i -lt 10 ]; do echo $i; let i=$i+1; done
・新人類(算術式展開を使う)
$ for ((i=0;i<10;i++)); do echo $i; done
・変人(タイプ数が少ないのがイチバン!)
$ for i in `seq 0 9`; do echo $i;done
いかがだったでしょうか。
上司や同僚の世代やTPOにあわせて書き方を変えてみると、世代間のギャップを埋めるのに役立つかもしれません。:-)



基本、旧人類のようです。
変人のseq の部分を 1 2 3 4 5 6 7 8 9 10 とベタで書くこともあるかな。
「UNIXにみる世代間の断絶」の内容は興味深いですな。
投稿: shigeonsd | 2006年10月27日 (金) 15:47
世代間の断絶を最も実感できるのはペアプログラミングでしょうか。ギャップは逆に色々吸収できるチャンスでもありますね。
投稿: kyagi | 2006年10月27日 (金) 18:46
・bash系新人類
bash3 -c 'for i in {0..9}; do echo $i; done'
投稿: hirose31 | 2006年10月30日 (月) 14:48
hirose31さん、コメントありがとうございます。
bash3ではperlのような N..Mといった書き方が使用可能なのですね。勉強になります。:-)
投稿: kyagi | 2006年10月30日 (月) 15:58
シェルで変数展開する場合、ダブルクォートで括るかどうか検討する癖をつけましょう。多くの場合、つけるべきです(私見。…といったようなこと社内メーリングリストで投げたような :-p)
$ touch blah-blah
$ command='echo "foo bar * hoge"'
$ eval "$command"
foo bar * hoge
$ eval $command
foo bar blah-blah hoge
わかりにくい例ですが、後者だとスペースが欠落したり、シェルのメタキャラクタ相当の文字が不意にシェルに解釈されてしまいます。
投稿: fumiyas | 2006年10月31日 (火) 11:46
先のコメントですが、複数のスペースが表示上はスペース 1つになってしまう…。ということで、コピー&ペーストして試してみてください。
以下、おまけ。
bash 依存が嫌、seq もない環境で、ループの度に expr などの fork/exec をせずに 100 回ループする:
$ tr '\0' '\n' |head -n 100 \
|cat -n \
|while read i; do echo "$i";done
ただの思いつきネタなので、実用的かどうかはわかりません :-)。0 から 99 とかできないし。
投稿: fumiyas | 2006年10月31日 (火) 12:05
fumiyas師匠、コメントありがとうございます。
ダブルクォートでの変数展開は確かに以前社内MLで流れていました。脊髄反射レベルになるまで癖として指に叩き込みます。m(_ _)m
投稿: kyagi | 2006年10月31日 (火) 17:49
tr からはじまるコマンドラインを打ってみたのですが動作しません。tr の引数にファイルか標準入力が必要でしょうか?
投稿: kyagi | 2006年10月31日 (火) 17:53
いちいち考えるのが大変なら「クォートしなければならない」として理由がなければ何も考えずにダブルクォートしてしまえば楽ですよ。
tr ですが、/dev/zero を食べさせてあげてください。コピー & ペーストしたときに抜けたか、TypePad が捨てたのかな?
投稿: fumiyas | 2006年11月 2日 (木) 18:50
tr で直に /dev/zero を読むとEOFが返ってこないため、はじめはcatにしたらうまくいきました。:-)
$ cat /dev/zero | tr '\0' '\n' | head -n 100 | cat -n | while read i; do echo "$i"; done
投稿: kyagi | 2006年11月 3日 (金) 11:03