Korn――高機能なシェル

 Linuxのシェルについてはおそらくご存じのことだろう――LinuxでKonsoleやxtermなどの端末ウィンドウを開いてコマンドを入力するとき、文字を受け付けているのが他でもないLinuxのシェルだ。あるいはファイルにコマンドを書き込んで、実行可能にして、実行するということをしているのなら、あなたはもう立派なシェルプログラマだと言えるだろう。しかし利用可能なシェルには様々なものがあって、それぞれに少しずつ違った特徴があるということはご存じだろうか。そのようなシェルの中で個人的に私が気に入っているのがKornシェルだ――この記事を読み終わる頃には、あなたのお気に入りにもなっているかもしれない。

 一般的なLinuxでは、デフォルトでbash(Bourne again shell)が使用されている。そのbashからKornシェルに移行する前に、Kornシェルのメリットを整理しておこう。まず、Kornシェルのバイナリはbashのバイナリよりも小さいうえ、多くの関数(echogetoptsなど)が個別の実行ファイルではなくシェルの中に組み込まれている。言い換えれば、Kornシェルでは実行の際に使用するメモリ容量が少なくて済むのに加えて、実行が高速になることもあるということだ。さらに言えば、Kornシェルはbashとの完全な後方互換性を保っている。つまりKornシェルに移行した場合、Kornシェル独自の機能をまったく使わなかったとしても、すでに知っているコマンドをすべて利用し続けることが可能だということだ。またKornシェルには組み込みの算術式、複合変数、ディシプリン関数、コプロセスなどといった便利な機能がある。本稿中盤以降では、これらの各機能を紹介する。

 ところで、Kornシェルにそれほどの利点があるのであれば、なぜLinuxではデフォルトでKornシェルではなくbashが使用されているのか不思議に思うかもしれない。その理由は極めて単純で、単にそれまでそうなっていたからという理由だけだ。Unixのデフォルトのシェルはsh(Bourne shell)だったので、Linuxのデフォルトのシェルがbash(Bourne again shell)になったのは自然なことだった。当然ながらここでデフォルトと呼んでいるのは、実際には/etc/passwdの各ユーザの当該項目のことを指しているに過ぎない。つまり例えば/etc/passwdの中の項目が以下のようになっている場合には、このユーザのデフォルトのシェルはbashだ。

bainm:x:1000:100:Mark Alexander Bain:/home/bainm:/bin/bash

 また、以下のようになっている場合には、このユーザのデフォルトのシェルはksh(Korn shell)だ。

bainm:x:1000:100:Mark Alexander Bain:/home/bainm:/bin/ksh

 なお現在使用しているシェルは、システムの環境を問い合わせることによっても確認することができる。

> env | grep SHELL
SHELL=/bin/bash

 シェルをKornシェルに変更する前にKornシェルがシステムに実際にインストールされていることを確認しておいた方が良いかもしれない。そのためのもっとも簡単な方法は、次のようにして実行ファイルを確認することだ。

> ls -l /bin/bash /bin/ksh
ls: /bin/ksh: No such file or directory
-rwxr-xr-x 1 root root 677184 2006-12-11 21:20 /bin/bash

 Kornシェルの実行ファイルがない場合には、手元のディストリビューションのパッケージ管理ツールを利用して入手することができる。例えばSUSEの場合にはYastを利用し、Debianの場合には「apt-get install ksh」を実行すれば良いだろう。インストール後は、コマンドラインで「/bin/ksh」を実行すればKornシェルに移ることができる。また「exit」を実行すればKornシェルを終了してbashに戻ることができる。/etc/passwdの中の情報を変更すればKornシェルをデフォルトにすることができるが、その際には手元のディストリビューションのユーザ管理用ソフトウェアを利用するのがおそらく安全だろう。とは言えKornシェルを使用するにはシェルとして使用するという手以外にもスクリプトに使用するという手もある。そのためにはファイルの先頭に次のような一行を追加すれば良い。

#!/bin/ksh

 それではKornシェルを使い始めることで新たに得られる機能を紹介しよう。まずは、Kornシェルの組み込み算術式について述べる。

組み込み算術式

 bashで何らかの算術演算をしようとすれば、「answer=$( echo 1.5 + 1.7 | bc )」や「answer=$( echo 1.5 1.7 | awk '{print $1 + $2 }' )」などのような形で間接的に実行するしかない。しかしKornシェルの場合には直接的に「answer=$(( 1.5 + 1.7 ))」や「((answer = 1.5 + 1.7 ))」というように記述することができる。

 この機能はabs、acos、asin、atan、cos、cosh、exp、int、log、sin、sinh、sqrt、tan、tanhなどの数学関数と一緒に利用することができる。したがって例えば「(( pi = 4.0 * ( 4.0 * atan(1.0/5.0) - atan(1.0/239.0) ) ))」という式で、非常に精度の高いπの値を持つ変数(pi)を作成することができる。なおこの場合、piの値は3.14159265358979324になる。

複合変数

 上記で、πの値を持つ変数を作成する方法を紹介した。ここではKornシェルとこの変数piを使って、円を表わすのに使用する変数を考えてみよう。

> radius=30
> (( circumference = 2 * pi * radius ))
> (( area = pi * radius * radius ))

 見て分かる通り、変数を使用する際に「$」記号を使用する必要はない(使いたいなら使っても良いが、使う必要はない)。この式では、変数circumferenceの値は188.495559215387594、変数areaの値は2827.43338823081392になる。

 次に、各変数名の前に例えば「circle_」という文字を付け足して、これらの変数をグループ化したいという場合について考えてみよう。Kornシェルでは、「複合変数」という単一の変数を用いてそれを行うことができる。

> circle=
> circle.radius=30
> (( circle.circumference = 2 * pi * circle.radius ))
> (( circle.area = pi * circle.radius * circle.radius ))

 このテクニックは変数を整理整頓するのに役立つだけではなくて、多くのプログラミング言語にあるような構造体やレコードを作成するために使用することもできる。

> driverx=(
name=""
float wage=145.50
integer travel_radius=50
)

 上記では、構造体として複合変数を定義して、各フィールドのデータ型を定義して、各々にデフォルトの値を代入している。 以下では、この構造体を使って複数のレコードを作成している。

> eval "driver01=$driverx"
> driver01.name="Bill"
> eval "driver02=$driverx"
> driver02.name="Fred"
> (( driver02.wage = ${driverx.wage} * 1.10 )) # Fredの給料を上げる
> print "${driver01.name} \$${driver01.wage} ${driver01.travel_radius} miles"
Bill $145.50 50 miles
>  print "${driver02.name} \$${driver02.wage} ${driver02.travel_radius} miles"
Fred $160.05 50 miles

ディシプリン関数

 Kornシェルでは、単一の変数に複数の値を割り当てることができるだけでなく、単一の変数に複数の関数(Kornシェルではディシプリン関数と呼ばれる)を関連付けることもできる

 Kornシェルの変数には、3つのディシプリン関数を関連付けることができる。すなわちget、set、unsetの3つで、それぞれ変数が読み取られるとき、書き込まれるとき、削除されるときに実行される。これらのディシプリン関数は、直接的には何も出力しないという、その他のシェル関数とは大きく異なる点がある。そこで「.sh.value」というKornシェルの特殊変数を利用する必要がある。ディシプリン関数の中で.sh.valueの値が変更されると、それによって変数の値が変わる。したがって例えば、ディシプリン関数を使って円周を計算するメソッドは次のようになる。

> function circle.circumference.get {
(( .sh.value = 2 * pi * circle.radius ))
}

 この関数は、現在使用中のシェル上で利用するためにコマンドラインで定義することもできるし、スクリプトファイルの中に書いておくこともできる。そのどちらかをしておけば、後は変数circle.radiusに半径の値を代入するだけで変数circle.circumferenceに円周の値を求めることができる。

> circle.radius=1
> echo ${circle.circumference}
6.28318530717958648
> circle.radius=20
> echo ${circle.circumference}
125.66370614359173

 これができれば、circle.area、circle.circumference、circle.radiusの3つの変数だけを使って、半径を入力すれば円の面積と円周を求めることができる関数、円周を入力すれば面積と半径を求めることができる関数、面積を入力すれば円周と半径を求めることができる関数などを作成することも簡単にできるだろう。

 それでは次に、おそらく他では見たことがない、しかしきっと便利に感じるであろう機能――コプロセス――を紹介する。

コプロセス

 bashを使いなれているのなら、「|」記号で表わす「パイプ」についてはおそらくすでに知っていることだろう。パイプを使えば、あるプロセスの出力を別のプロセスの入力として与えることができる。Kornシェルのコプロセスは、言ってみれば単に双方向のパイプだ。コプロセスを使えば、バックグラウンドプロセスとフォアグラウンドプロセスとがお互いに通信することができる。使い方は簡単で、「|&」記号を使ってプロセスをバックグラウンドで実行すれば、「print -p」でそのプロセスに情報を送ることができて、「read -p」でそのプロセスの出力を受け取ることができる。

 それではまず最初に、バックグラウンドで実行するためのスクリプトを作成しよう。次に示すスクリプトは、入力として数字を受け取って、これまでに入力された値の合計を出力する。

#!/bin/ksh
total=0
while [ "" == ""  ]
do
 read input
 (( total = total + input ))
 echo $total
done

 このスクリプトをkeep_count.kshとして保存しておけば、コマンドラインでもスクリプトの中でも、以下のような形で使用することができるようになる。

./keep_count.ksh |&
print -p 2
read -p x # xの値は2
print -p 10
read -p x # xの値は12

 バックグラウンドで実行するプロセスの数はいくつでも良い。しかしいくつもプロセスを起動すると通信したいプロセスをどうやって指定したら良いのかが問題になる――とは言ってもKornシェルではそれも大した問題ではない。というのも、各パイプのファイルディスクリプタを再定義できるからだ。

./ keep_count.ksh |&
exec 4>&p # 入力を再定義する
exec 5<&p # 出力を再定義する
print -u4 1
read -u5 x # xの値は1
./ keep_count.ksh |&
exec 6>&p
exec 7<&p
print -u4 1
read -u5 x # xの値は2
print -u6 1
read -u7 y # yの値は1

最後に2点ほど

 冒頭でKornシェルにはbashとの完全な後方互換性があると述べたが、実際にはbashとは異なる点もある。例としては履歴機能がある。bashで「history 100」を実行すると、直近の100個分のコマンド履歴のエントリが表示されるが、同じことをKornシェルで実行すると、履歴ファイルの100行めからの全履歴が表示される。なおKornシェルの場合、再び実行したい行を見つけたら「r」と行番号を入力すれば良い。

>history
1069 ls -l /lib/ast/ksh
1070 ls -l /bin/ksh93
1071 html2text -style pretty /home/bainm/Articles/Bain_ksh.html | wc -w
1072 grep bainm /etc/passwd
1073 ps -ef | grep firefox
1074 kill -9 3639
>r 1071

 もう一つKornシェルを使い始めたときに気付くかもしれない点として、上矢印キーを入力した際に、最後に入力したコマンドではなく制御文字が表示されるということがある。これは左/右/下矢印キーを入力した場合についても同じだ。しかしこの点は簡単に改善することができて、「set -o emacs」という一行を/etc/.kshsrcファイルに追加しておけば、次回シェルを起動したときには、矢印キーは慣れ親しんだ通りの動作をするだろう。

Linux.com 原文