連載
» 2012年01月27日 00時00分 公開

OSに付属するシェルスクリプトを読んで技術を盗むスマートな紳士のためのシェルスクリプト(3)(2/2 ページ)

[後藤大地オングス]
前のページへ 1|2       

「命名」という悩ましい問題

 中身を反映した、分かりやすい名前を付ける。あえて説明することではないかもしれないが、大切な考え方だ。そして、誰にでも分かる名前を付けるということは実は難しい。

 シェルスクリプトを作っていると、繰り返し使う部分を関数としてまとめ、再利用しやすくすることがある。特に、その部分が長くなってきたら、関数としてまとめた方がよい。これは、どのような言語でも、プログラムを作っている人なら当たり前にやっていることだろう。

 ここで、ARP(Address Resolution Protocol)キャッシュをすべて消し去るスクリプトを書くと想定しよう。

 ARPコマンドを順に実行して、キャッシュを一掃するには、いくつかの手順を踏む必要がある。まず、「arp -an -i インターフェイス名」でキャッシュの内容を一覧する。

% arp -an -i em0
? (192.168.1.10) at 00:xx:xx:xx:xx:xx on em0 expires in 1150 seconds [ethernet]
? (192.168.1.1) at 00:xx:xx:xx:xx:xx on em0 expires in 1153 seconds [ethernet]
? (192.168.1.101) at e0:xx:xx:xx:xx:xx on em0 permanent [ethernet]
? (192.168.1.214) at 08:xx:xx:xx:xx:xx on em0 expires in 208 seconds [ethernet]

 続いて、キャッシュの内容を取得し、それをsedで加工してIPアドレスを得る。そして、IPアドレスを引数にしてarpコマンドに-dオプションを付けて実行し、キャッシュを削除する。

% arp -an -i em0 | sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p'
arp -d 192.168.1.10
arp -d 192.168.1.1
arp -d 192.168.1.101
arp -d 192.168.1.214
%

 この一連の処理をシェルスクリプトにまとめると以下のようになる。

arp_flush() {
        arp -an -i $interface | \
                sed -n -e 's/^.*(\(.*\)) at .*$/arp -d \1/p' | \
                sh >/dev/null 2>&1
}

 ここに挙げた関数「arp_flush」の実際の処理内容は、「arp -an -i インターフェイス名」の出力と、それをsedで加工した結果が分からなければ想像できない。しかし、「arp_flush」という名前を付けておけば、名前が示す通りの機能を持っているのだなと分かりやすくなる。

 どのような処理を関数としてまとめ上げて、どう命名するかというところには、開発者のセンスが表れる。大量のスクリプトを読んで、より良い命名法を追求するようにしたいものだ。

出力を抑制する> /dev/null 2>&1

 コマンドの実行結果の出力を止めるために、「> /dev/null 2>&1」を使うのはよくあることだ。コマンドを実行したいだけ、あるいは、コマンドの実行が成功したのか失敗したのかを知りたいだけで特に出力は必要ないというときに使う。

 この記述は「> /dev/null」と「2>&1」の2つに分けると理解しやすい。前半は標準出力を/dev/nullに書き出すように指示している。基本的なリダイレクトだ。これでコマンドの標準出力は/dev/nullへ消えていく。

 「2>&1」は、標準エラー出力(ディスクリプタ2)の指し示す先を標準出力(ディスクリプタ1)に変えることを意味している。標準エラー出力も標準出力に出力させるということだ。こうすると、例えば、ファイルディスクリプタの複製を作るdup2(2)システムコールを呼ぶときなどに、dup2(標準出力, 標準エラー出力)と引数を並べることができる。

 では、「> /dev/null」と「2>&1」を組み合わせるとどうなるか? 標準出力も標準エラー出力も/dev/nullへのリダイレクトとなり、結局どちらも消えていく。「2>&1」の指定が「dup2(2)システムコール」に対応するものであることが分かると、「2>&1」を使ったほかの記述の意味も分かる。基本的な使い方だが重要なテクニックだ。

 以下に示すのは、IPアドレスのエイリアス(別名)に付いているルーティング情報を消し去るシェルスクリプトだ。よく読んで、以上で説明したテクニックを駆使していることを確認してほしい。

delete_old_alias() {
        if [ -n "$alias_ip_address" ]; then
                $IFCONFIG $interface inet -alias $alias_ip_address > /dev/null 2>&1
                #route delete $alias_ip_address $LOCALHOST > /dev/null 2>&1
        fi
}

数値の比較演算

 ちょっと長いが、次のサンプルを見てほしい。

fill_classless_routes() {
        set $1
        while [ $# -ge 5 ]; do
                if [ $1 -eq 0 ]; then
                        route="default"
                elif [ $1 -le 8 ]; then
                        route="$2.0.0.0/$1"
                        shift
                elif [ $1 -le 16 ]; then
                        route="$2.$3.0.0/$1"
                        shift; shift
                elif [ $1 -le 24 ]; then
                        route="$2.$3.$4.0/$1"
                        shift; shift; shift
                else
                        route="$2.$3.$4.$5/$1"
                        shift; shift; shift; shift
                fi
                shift
                router="$1.$2.$3.$4"
                classless_routes="$classless_routes $route $router"
                shift; shift; shift; shift
        done
}

 まず、test(1)を使って(つまり[ ]で)整数を比較している点に注目したい。test(1)の比較演算は汎用的なプログラミング言語と比較すると分かりにくい。忘れてしまい、必要になるとその都度マニュアルを引くことになるという人も多いのではないだろうか。しかし、シェルスクリプトでは、整数の比較にはこの方法がよく使われる。

 [ ]内には比較演算子がある。「-ge」が「≧」、「-eq」が「=」、「-le」が「≦」に対応している。ここではその時の$1に代入されている整数の範囲によって処理を切り替えていることが分かる。

変数の中身をずらす

 直前に挙げたサンプルをもう一回見てほしい。引数の個数が5以上である間、whileで処理を繰り返している。これもシェルスクリプトではよく使われるテクニックだ。

 シェルスクリプトでは引数を参照するのに$1、$2、$3といった変数を使う。そして、shiftを1回実行すると、$2の中身を$1に代入し、$3の中身を$2に代入し、といったように、変数の中身を1つずつずらすことができる。

 この例では引数を処理するためにshiftを使っているが、一般にはコマンドオプションを処理するためにshiftを使うことが多い。

 シェルスクリプトの引数とshiftの動きは、CやJavaといったほかの汎用プログラミング言語を身に付けている方には分かりにくいということがある。whileとshiftはほかのスクリプトにも登場するので、そのときにまた取り上げたいと思う。

局所変数local

 関数を作るときは、次のように変数宣言に「local」を指定することがある。これには変数のスコープを対象の関数内に限定するという意味があり、関数よりも外で扱っている変数と名前がぶつかることを避けるために使う。

add_new_resolv_conf() {
        # XXX Old code did not create/update resolv.conf unless both
        # $new_domain_name and $new_domain_name_servers were provided.  PR
        # #3135 reported some ISP's only provide $new_domain_name_servers and
        # thus broke the script. This code creates the resolv.conf if either
        # are provided.
 
        local tmpres=/var/run/resolv.conf.${interface}
        rm -f $tmpres

 localで局所変数を設定すると、どのような動きを見せるか、次のようなスクリプトを書いて実行してみよう。

#!/bin/sh
 
drink=beer
 
doit()
{
    local drink=sake
    echo $drink
}
 
doit
echo $drink

 関数「doit()」の外では、変数「drink」の中身は「beer」になっているが、関数内では「sake」になっている。このシェルスクリプトを実行すると次のような結果になる。

sake
beer

 関数doit()内の変数drinkにはlocalが指定があるので、関数外の変数drinkには影響を与えない。このスクリプトからlocal指定を外してみると違いが分かる。変数drinkがsakeになってしまうため、出力はどちらもsakeになる。

sake
sake

 localは、よくある変数名を関数内で使いたいときや、環境変数との衝突を避けたいときなどに使う。シェルスクリプトでもちょくちょくお目にかかる指定だ。シェルスクリプトで使用する関数を、ライブラリのように別ファイルにまとめて再利用するときなどは、localの指定を徹底しないと予期せぬ問題が発生する。

スクリプトを読む

 今回紹介したサンプルは、dhclient-scriptの前半から持ってきたものだ。「自分もこの書き方はよく使っている」という方もいるだろう。しかし、実はその意味をよく分かっていなかったという人もいるのではないだろうか。

 他人が書いたソースコードを読むのは苦痛だ。よほど品質の高いものでなければ、とても読んでいられたものではない。特にまだスキルが低い方は、書いてあることの意味を理解することすらできず、相当な精神的ストレスを感じるだろう。

 しかし、こうして他人のソースコードを読むことで、新しい知識を得たり、自分の知らなかったことを経験することもできる。手っ取り早く生きた知識を得ようと考えると、ほかには考えられないほど効率の良い方法だ。今後しばらくは、シェルスクリプトを読み進めていきたい。

著者プロフィール

後藤 大地

オングス代表取締役。@ITへの寄稿、MYCOMジャーナルにおけるニュース執筆のほか、アプリケーション開発やシステム構築、『改訂第二版 FreeBSDビギナーズバイブル』『D言語パーフェクトガイド』『UNIX本格マスター 基礎編〜Linux&FreeBSDを使いこなすための第一歩〜』など著書多数。



前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。