あなたはこれでCシェルを捨てられる (preprint)

1. まえがき

このドキュメントは、次のような方を対象に書かれています。

このドキュメントは、次のような方を対象とはしていません。

拙訳「 なぜcshでプログラムを書くのが良くないのか」を公開後、Cシェルプロ グラミングを戒める声を聞く機会が心なしか増えたようで、地味ながら確実な 手応えが感じられます。しかし、これには次のような反応が多いのも事実です。

「なるほど、Cシェルプログラミングが悪いのはわかった。でも、これまで 書いてきたCシェルスクリプトと同じことを、これからは一体どうやってやれ ばいいんだ?」

Cシェルを捨てようとする人の多くは、代わりにBourneシェルでプログラムを 組もうとします。これは最良の選択肢のひとつと言ってよいでしょう。Bourne シェルはスクリプト言語として必要十分の機能を持ちポータビリティに優れる、 今日もっともメジャーなプログラミングシェルの一つです。

しかし、勘違いしないでください。Bourneシェルの持ち味は、徹底して必要十 分な所にあります。シェルが持つべきでない機能は、一切持っていません。で すから、シェルスクリプトだけで問題を解決しようとするのではなく、他の便 利なUNIXツールとうまく連携させることが重要なのです。

Bourneシェルのお供として、あるいはもうひとつのスクリプト言語として、こ のドキュメントではPerlを取り上げます。PerlはCライクな構文のプログラミ ング言語で、苦手な分野というのが特にありません。単純な作業は簡単に、プ ログラム次第では高度な作業もできます。1ヶ月あれば修得できますが、ここ ではたった5分で覚えられ、すぐに役立つ使い方をご紹介します。

2. ループ

「あるプログラムhoeは、引数としてデータファイルの名前を与 えると結果を出力する。これを、 datafile1からdatafile123まで 123個のデータファイルに対して実行し、結果はそれぞれ result1からresult123という名前で保存したい。」
私達のように実験のためにUNIX上で計算をする時には、よくある話ですね。 これをhoe datafile1 >result1 などと123回も手で実行しようとする人はいないでしょう。Cシェルでは、このよ うに書くことができます。

#!/bin/csh -f
@ i = 1
while ($i <= 123)
  hoe datafile$i >result$i
  @ i ++
end

元Cシェルユーザーが決まってする質問は、これをBourneシェルでやるにはど うすればよいかというものです。Bourneシェルの達人に聞くと(あるいは Bourneシェルの解説書を読むと)、次のようにすればよいと教えてくれるでしょ う。


#!/bin/sh
i=1
while [ $i -le 123 ]; do
  hoe datafile$i >result$i
  i=`expr $i + 1`
done

どうですか。わかりにくいですね。

Bourne シェルは、算術演算の機能を自分では持っていません。ここでは、 それを実現するために、testexprという外部コ マンドを呼び出しています。testの方は見つけにくいかもしれ ませんね。これは3行目の `[' です。`[' は testコマンドの省略形です。ループ変数が123以下かどうかの判 定はtestコマンドに、ループ変数のインクリメントは exprコマンドに肩代わりしてもらっているというわけです。

このプログラムは読みにくいだけでなく、プログラミング効率を落とす様々 な罠が散りばめられています。例えば・・・

このような問題は、何もBourneシェルだけのものではありません。上に挙 げたCシェルスクリプトは、見やすさの上では若干有利ですが、 @ i = 1などのスペースはやはり省略できません。

もう、このようなシェルスクリプトを書くのはやめましょう。ちょっと過 激ですが、ループプログラムを書くには次の構文だけ覚えれば十分です。


#!/bin/sh
for i in 1 2 3 4 5; do
  echo 今 $i 回目
done

Bourneシェルのfor文はCシェルのforeach文に相当するもので、 この例では変数i に1から5まで順番に代入しながらechoを繰り返し実行しています。 簡単ですね。

さて、では最初の例のように繰り返し回数がムチャクチャ多い場合にはど うしましょうか。それを考える前に、まずは次のPerlスクリプトを見てください。


#!/usr/local/bin/perl
for($i=1; $i<=5; $i++) {
  print "今 $i 回目\n";
}

Perlのことを全く知らなくても、このコードは簡単に理解できるでしょう。 C との違いは、main()がないこと、変数の宣言がないこと、変数 の頭に$が付いていることくらいでしょうか。 え、printfじゃないのかって? 大丈夫、ちゃんと printfも用意されています。

このスクリプトをloop5という名前で保存してからコマンド ラインでperl loop5と入力すると、結果が見られます。実行属 性を与えてから単にloop5としても同じです。

ここで、もう一つ覚えてください。それは、Perlはスクリプトを直接引数 に書くことができる、ということです。このためには、-eとい うオプションを使います。上述したPerlスクリプトを実行するのと同じことを、 コマンドラインから次のように指示できます。

 perl -e 'for($i=1;$i<=5;$i++){print"今 $i 回目\n"}'  

Perlは自由なフォーマットでプログラムが書けますから、このように1行で書 いたりスペースや改行を省いたりしても問題ありません。

さて、本題に戻りましょう。Bourneシェルのfor文では、制御変数の値を inの後ろに並べて書くのでしたね。1から123までの数字を手で 入力するのはとても大変です。そこで、この部分はPerlにやってもらうことに しましょう。次が最終形です。


#!/bin/sh
for i in `perl -e 'for($j=1;$j<=123;$j++){print "$j "}'`; do
  hoe datafile$i >result$i
done

このシェルスクリプトの最大のミソは、バッククオート(`)です。一対のバッ ククオートで囲まれたコマンド列はシェルによっていったん実行され、その結 果得られる出力がこの部分と置き換わります。つまり、このスクリプトの意味 するところは次のものと全く同じになります。


#!/bin/sh
for i in 1 2 3 4 (...途中省略) 120 121 122 123 ; do
  hoe datafile$i >result$i
done

ぜひ、この書き方を覚えましょう。ちょっと長ったらしくなってしまいま すが、すぐに何も見なくても書けるようになります。何も見なくてもどんどん コードが書けることがどんなに素晴らしいことか、皆さんよくご存じでしょう?

(補足: 最終形に出てきたPerlの1行スクリプトでは、制御変数に $iではなく$jを使っています。これは、シェル変 数iと見間違えないように変えておいただけで、 $iにしても問題なく動作します。)


Copyright 1998 Hiroki Mori. All Rights Reserved.