シェルのイディオム
シェルは対話的なコマンドラインツールであると同時にプログラミング言語でもあります。 しかし、対話的なツールの利便性を優先しているので、プログラミング言語として見ると変態的です。 何物にも似ていません。なのですぐに忘れます。ぼくは相当、シェルスクリプトを書いてきた自負がありますが、それでも10回まわるループのコードすらすぐに忘れます。
最もポータブルなループのコードは次のようになるでしょう。ここでのポータブルの意味は、古いシェルでも動くという意味です。シェルはすべてbourneシェル系を前提にしています。1行で書いていますが、;(セミコロン)の部分を改行にして、複数行に書いても構いません。
i=0;while [ $i -lt 10 ]; do echo $i; i=`expr $i + 1`; done
この、意味が分かりそうに見えるコードにも、他の言語からすれば信じられない落し穴が隠れています。
最初の i=0 は、シェルを知らなくても変数iに値0を代入していると予想できるでしょう。なんとなく詰まって見づらいと思って次のように空白を入れると無情にもエラーになります。
i = 0 bash: i: command not found
エラーメッセージを見れば何が起きたか予想がつくと思います。変数への代入の時は = の前後に空白をいれてはいけないのです。
変数の左辺値が欲しい時は i とそのまま書いて、右辺値が欲しい時は $i と $ をつけるあたりも、そんなもの構文で判断してくれ、と文句を言いたくなりますが、そういうものです。ちなみに ${i} と中括弧をつけることもできます。中括弧は ${foo}bar のような場合、必要になります(変数fooの値と文字列barの連接になります)。
while の後ろの [ は有名なので知っている人が多いと思います。コマンドとしても存在しますが(手元のDebian etchでは/usr/bin/[)、いまどきのシェルでは組込みコマンドになっています。man [ すれば分かるように test コマンドの別名です。ちなみにtestコマンドはCプログラマには有名です。test.c(たいていprintfが書かれている)をコンパイルしたtestという実行ファイルを実行したのですが何も出ません、という泣ける話です。Unix Cの最初の洗礼です。
[ ] の中の意味は man test で調べてください。perlにもあるので lt は less than の略だと、初めて見ても気づく人はいるかもしれません。
doからdoneの間が、whileのループでまわる部分です。doとdoneはかわいいものですが、ifの場合、終わりがfiです。昔からあるから誰も文句を言いませんが、いまどきの言語がfiなんてキーワードを使ったら、ふざけているのかと問い詰められそうです。
バッククォートはコマンド実行結果の展開の意味です。exprは各種計算ができます。興味があれば man expr してください。expr は数値演算以外に文字列演算もできます。正規表現を使ったsedみたいなことまでできてしまいます。
/sbin/ifconfig -a| while read x; do echo `expr "$x" : '.*inet addr:\([0-9.]*\)'`; done
簡単に説明すると、expr A : B と書いた時、文字列Aに対して正規表現Bでマッチングして、正規表現の部分パターン(=丸カッコで囲ったマッチ部分)が返ります。上のコードを実行すると空行がたくさんでます。気になるなら、次のように空行を削ってください。
/sbin/ifconfig -a| while read x; do echo `expr "$x" : '.*inet addr:\([0-9.]*\)'`; done | tr -s '\n'
最初の例に戻ると、bashなら、`expr $i + 1`の代わりに次のようにも書けます。この方が短くて良いのですが覚えられません。
i=0;while [ $i -lt 10 ]; do echo $i; i=$((i+1)); done
${} の中で、思ったより色々なことができます。
まず、一番良く使うイディオムから。
: ${foo=bar}
変数がfooが未定義なら代入を行い、定義済みなら代入をしません。elispで (defvar foo "bar") と書くような感じです。 fooの部分に環境変数を使うコードは良く書きます。ユーザが環境変数を設定済みならそれを尊重し、未設定ならデフォルト値を使いたい場合に書くコードです。
以下はあまりポータブルでは無いので覚えられません。
文字列から部分文字列を取得するには次のようにします。
# 書き方: ${value:offset:length} foo='foobar'; echo ${foo:2:3} => oba
部分文字列の置換もできます(正規表現ではありません)。
# 置換の書き方: ${value/pattern/replace} # グローバル置換の書き方(いわゆる /g): ${value//pattern/replace} foo='inet addr:192.168.80.11'; echo ${foo/inet addr:/address=} => address=192.168.80.11
最後にシェルスクリプトのデバッグについて書きます。たいていは sh -x で実行すれば分かります。ファイル名がfoo.shなら sh -x foo.sh と実行します。
それでもダメならechoデバッグです。今はprintf(1)コマンドもありますが、echoデバッグです。なぜならechoは悲恋の精霊だからです(例: http://www.arakihp.jp/koramu/koramu34.html)。想いが届かず嘆きのあまりやせ細りついに身体が消え去るecho。でも声だけは残ったecho。printfのような無機質なコマンドとは一線を画す叙情的なコマンドです。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/shell-idioms/tbping
別解 (Re:シェルのイディオム)