Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

コマンドの出力をtop風に表示させるtopless

UNIX実用シェルスクリプトシリーズ:パート1

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/04/22 00:00

この記事ではUNIXサーバ管理という側面からのシェルスクリプトプログラミングを取り上げます。コマンドの結果がtopのように出力されるツールを作ります。

はじめに

 この記事ではUNIXサーバ管理という側面からのシェルスクリプトプログラミングを取り上げます。またシェルスクリプトを自作するときのポイントも解説します。

 UNIXサーバを管理するときは、道具としてのシェルスクリプトが重宝します。常時ログを監視するにしても、入力するコマンドをまとめるにしても、UNIXに用意されているコマンドを有効活用するにはシェルスクリプトが効果的です。

 シェルスクリプトは、コマンドを入力する作業の延長線でプログラミングができるという容易な側面を持っていますが、正しく理解しようとすると多くのルールやコマンドを覚えなければならないというやっかいな側面も持っています。

 シェルスクリプトを習得する方法はいくつかあります。じっくり時間がとれるならどの学習方法でもよいのですが、入社後にUNIXサーバを管理することになり、緊急に使えるようにならなければならないという逼迫した状況であれば、他人のシェルスクリプトを読んで用途に応じて編集していくという方法が効果的です。

 シェルスクリプトは現在主流になっているプログラミング言語とちがって、空白改行の扱いがコマンドプロンプト的であるなど、それまでのプログラミングの経験からでは理解しずらい面があります。よって、言語仕様を把握して一から覚えていくのも手ですが、自分が目的としているシェルスクリプトを探してきて編集して使う方が効果的に覚えられますし、実用的でもあります。これからじっくりシェルスクリプトを学ぼうとしている方にも、この学習方法はお薦めできます。

対象読者

 本稿では、ある程度シェルスクリプトを使うことができる、中級者から上級者を読者として想定します。

必要な環境

 紹介するシェルスクリプトを実行するために必要となる環境は、最近のUNIX互換OSです。こちらではFreeBSD 5.3、NetBSD 2.0、OpenBSD 3.6、Novel SuSE 9.1、Solaris8、Solaris10で動作を確認してあります。

シェルスクリプト

 シェルスクリプトでどういったことが実現できるのでしょうか。もちろん、やろうと思えばなんでもできますが、シェルスクリプトにはそれに適した用途範囲があります。サーバの起動停止処理であったり、既存のコマンドを組み合わせる場合に効果を発揮します。

 以降では、シェルスクリプトで実現できることを示すという目的で、toplessというシェルスクリプトを取り上げます。toplessはUNIXサーバ管理業務で使用できる実用的なスクリプトです。シェルスクリプトとしては難しい部類に入り、シェルスクリプトの限界を把握しておく意味で効果的な教材といえます。

 toplessシェルスクリプトは、2005年春におこなわれた関東甲信越BUG合同合宿において開発されました。

関東甲信越BUG合同合宿について
 BUGは「BSD Users Group」の略で、関東甲信越の各BUGからお互いの知識の交換や交流、勉強会を目的として、定期的に合宿を行っています。

 合宿の途中、ある開発者の方がプロンプト2.1のようなコマンドを教えてくださいました。^[と表示されている文字列はESCコード(1BH)です。

プロンプト2.1 netstatコマンドの結果がtopのように出力される1行コマンド (sh, bashなどsh系シェルで実行する場合)
# clear; while :; do echo -n "^[[1;1H"; netstat -nr; sleep 1; done

 このコマンドを実行すると、netstat -nrというネットワークの接続状況を表示するコマンドが、1秒おきに端末に再描画されます。プロセスの状況を画面に表示するtopというコマンドがありますが、その出力内容がnetstat -nrの出力結果になったような感じです。

 この処理の要は、clearで端末をクリアしたあとで、echo "^[[1;1H"として画面左上隅にカーソルを移動させるエスケープシーケンスを端末に送って、コマンドの出力がつねに同じ場所に行われるようにしている点にあります。

 その場でこの1行コマンドが便利だということになり、いくつか機能を追加し常用できるシェルスクリプトまでおこすことになりました。それがtoplessです。toplessという名前は、topのような出力をおこない、lessのようにコマンドのページャとして扱うというところから命名されています。FreeBSDで開発されましたが、のちに使用者からのフィードバックをもとにNetBSD, OpenBSD, Linux, Solarisでも動作するように変更されました。

topless

 UNIX環境がある方は、toplessをダウンロードして、プロンプト3.1のようにコマンドを実行してみてください。netstat -nrの出力結果が、1秒おきに更新される様子がわかります。

プロンプト3.1 topless実行例
# topless netstat -nr

 次に、-cを指定してプロンプト3.2のように実行してみてください。出力結果において、前回の出力と違うところが赤色で表示されるようになります。この前回と出力が違っている行は色をつけるという機能が、toplessで実現されているもっとも注目される機能です。

プロンプト3.2 topless実行例 前回との差分に色をつける
# topless -c netstat -nr
図3.3 topless -c netstat -nrの実行例
図3.3 topless -c netstat -nrの実行例

 さらに-n 5という指定を行うと、過去5回分まで記録し、変更があった行に色が残るようになります。ネットワークを監視する場合など、しばらくその結果が残っている方が好ましいことが多いため、この機能がつけられました。シェルスクリプトで実現するのは、この機能あたりまでが限界でしょう。これ以上は複雑すぎて、シェルスクリプトで実現するにはあまり利益がなくなってきます。

プロンプト3.4 topless実行例 過去数回を比較し色をつける
# topless -c -n 5 netstat -nr

 ほかにも、-hでヘルプの表示、-vでバージョンの表示、-sで更新時間の指定をおこなうことができ、コマンドを終了するためのキーが[Ctrl]+[c]だけではなく、topのように[q]キーでも行えるようにしてあります。シェルスクリプトとしてはかなり技巧が施されている方でしょう。このあたりまでくると、通常はC言語やほかのスクリプト言語で書き直したりするものです。シェルスクリプトで実現する機能としては、だいぶ難しいものとして参考にしてください。

本体の処理

 まず処理の本体を探します。わからないコマンドが使われていたとしても、適当にあたりをつけて読み飛ばします。シェルスクリプトを読む場合、枝葉末節にはこだわらずどこが処理の本体かを探します。そうしないと、処理の枝葉末節が気になって全体が把握できなくなってしまうからです。

 toplessは、前回の出力との違う部分に色をつける処理を実現するために、だいぶ込み入ったものになっていますが、オプションが指定されていない場合に実行されない部分を追っていて不要な部分を落し整理していくと、結局つぎのようなコードが処理の本体ということがわかります。

リスト4.1 シェルスクリプトの本体を見抜く:toplessの場合
while :
do
    buffer=$(eval ${@+"$@"})
    echo -n "$es_cl$es_ho$buffer"
    sleep $waitsec
done

 これを突き止めることができれば、結局処理としてはプロンプト2.1と同じことが実行されていることがわかります。本体がわかれば、それをコアにして自分でスクリプトを拡張していくこともできます。

 以降では処理の基本的なアイディアと流れを紹介します。toplessで使われているコマンドの意味やその使い方については、マニュアルをご覧ください。

表示の制御

 toplessでは、エスケープシーケンスを使って、文字列を色をつけたり、端末をクリアしたり、カーソルの位置を移動させたりしています。端末によってはエスケープシーケンスでかなり多くの操作を行うことができます。端末を駆使したい場合は一度エスケープシーケンスを調べておきましょう。代表的なエスケープシーケンスを表4.2に示します。

表4.2 代表的なエスケープシーケンス
シーケンス 内容
ESC[x;yH 座標x,yにカーソルを移動
ESC[2J クリア
ESC[0J その行をカーソルから行末までクリア
ESC[0m 通常の文字
ESC[1m 太字文字
ESC[4m 下線つき文字
ESC[5m 点滅文字
ESC[7m 反転文字
ESC[30m 前景色:黒
ESC[31m 前景色:赤
ESC[32m 前景色:緑
ESC[33m 前景色:黄
ESC[34m 前景色:青
ESC[35m 前景色:紫
ESC[36m 前景色:水色
ESC[37m 前景色:白
ESC[40m 背景色:黒
ESC[41m 背景色:赤
ESC[42m 背景色:緑
ESC[43m 背景色:黄
ESC[44m 背景色:青
ESC[45m 背景色:紫
ESC[46m 背景色:水色
ESC[47m 背景色:白

 エスケープシーケンス自身の入力は、エディタごとに異なります。Emacsであれば[Ctrl]+[q]→[ESC]で、端末でエディタを使っている場合は[Ctrl]+[v]→[ESC]で、エスケープシーケンス自身を入力することができます。エスケープシーケンス自身を入力する方法がわからなかったり、シェルスクリプト内に直接エスケープシーケンスを書き込むことを避けたい場合は、リスト4.3のようにechoprintfコマンドを使います。代表的なエスケープシーケンスはtputコマンドでも出力させることができますので、それを使ってもいいでしょう。

リスト4.3 エスケープシーケンスをコマンドで用意する例
color_red=$(echo -e "\e[31m")
color_blu=$(echo -e "\e[34m")
color_org=$(echo -e "\e[0m")
es_cl=$(tput clear) # clear screen and home cursor
es_ho=$(tput home) # home cursor

 前回の出力と違うところに色をつける処理は、diffコマンドを使うことでおこなっています。前回の出力と、今回の出力をファイルに保存して、diffコマンドで違いを調べます。違いがある行とそうでない行は変数に状態を記録しておき、その変数をもとに色付けを行うエスケープシーケンスを付与したりしなかったりを選択しています。/bin/shのシェルスクリプトは配列を使うことができませんので、すべて変数として設定します。

 複数回以前までの状態を記録する方法は、先ほどの変数に数字を設定することでおこなっています。変数にたとえば「5」という値を設定し、一回処理が終わるごとに数値を減算していきます。変数が「0」より大きい場合は色付けを実行し、そうでない場合は色付けを行わないということです。こうすれば、数回分を記録しておくことができます。

 あと注目するべきは、空白タブがそのまま解釈されるようにwhile構文で使用されているIFS変数の指定です。while readの処理では、空白タブが短縮されてしまうため、そのまま文字列として処理するためにIFS=を指定しておきます。

リスト4.4 while IFS= read lineという使い方
echo "$buffer" |
while IFS= read line
do
    ....

 あとはコマンドとして使用するために引数の処理を加え、割り込み時の処理を設定し、細かく調整を加えて仕上げています。

シェルスクリプトを編集する

 シェルスクリプトの内容が把握できたら、練習する意味も込めて編集を行ってみましょう。たとえば、オプションのデフォルト設定を変更するといった簡単なところからはじめます。

 toplessであれば、表示される色が赤では見にくいから青に変更するとか、標準の更新時間をもっと長くしたいとか、そういったところから始めます。そこから徐々に変更する場所を増やしていって、変更した場合の動作の違いを逐次調べながら作業をおこないます。実際に実行しながら編集すると、思いの外速くシェルスクリプトを理解することができるようになります。

 編集し変更することになれてきたら、リスト4.1のように本体だけから自分でコマンドを組み立てて作成してみます。最初は元のシェルスクリプトを参考にしながら理解できる範囲内で作業をおこないます。わからない部分が出てきたら逐次マニュアルを調べながら作業を行います。

 シェルスクリプトになれてきたら、最初から自分で作成してみます。いくつかのシェルスクリプトでこれを行えば、シェルスクリプトがだいぶ実用的に使えるようになるでしょう。

互換性について

 UNIXサーバの管理をおこなうときは、一種類のOSで済むとは限りません。対象とするOSが複数ある場合は、作成したシェルスクリプトも複数のOSで動いてほしいことになります。

 まず、シェルスクリプトには基本的に/bin/shを選択するようにします。/bin/shはほとんどのUNIXに用意されています。いったん作成したら、各種OSで動作を調べ、使用するシェルスクリプトの機能やコマンドをどのOSにもあるものに置き換えていきます。

 たとえば、リスト6.1の処理はOS間の互換性をとるための処理です。OSごとの状況に応じて、実行するコマンドが違っていたり、自前で同じ名前のコマンドを用意しています。

リスト6.1 互換性のための処理
tput_cll=$(tput ce 2> /dev/null)
tput_cls=$(tput cd 2> /dev/null)
es_ce=${tput_cll:-$(tput el)}
es_cd=${tput_cls:-$(tput ed)}

stty size > /dev/null 2>&1 &&
    readonly sttysize='stty size' ||
    readonly sttysize='/usr/ucb/stty size'
type mktemp > /dev/null 2>&1 ||
mktemp()
{
    > ${@+"$@".$$}
    chmod 600 ${@+"$@".$$}
    echo ${@+"$@".$$}
}

 /bin/shの機能はどのUNIXでも似ていますが、Solarisなど特定のOSはポリシーの違いから、ほかのOSで持っている機能が入っていないことがあります。そのような場合、Solaris版shに実装を合わせるか、Solarisでは/bin/shの代わりにbashを使ったりします。Solarisではbashを使うようにするといた処理を実現するには、たとえばリスト6.2のように記述します。

リスト6.2 Solarisではbashを使うようにする処理
#!/bin/sh
[ -z ${BOOTED-""} -a SunOS = `uname -s` ] && exec env BOOTED=yes
 bash "$0" "$@"

 どこまで互換性を考慮するのかは、ケースバイケースで選択します。意味もなく互換性を考慮しても、使いにくくなるだけです。自分の使用する範囲内で適度におこなえば十分です。

まとめ

 実用シェルスクリプトとしてtoplessを取り上げました。

 シェルスクリプトはUNIXを活用するための効果的なツールです。適切な用途範囲であれば、短時間で効果的なコマンドを作成することができます。

 シェルスクリプトは便利である反面、雑多でもあります。ひとつひとつを詳しくみていくと、枝葉末節にとらわれて作成できなくなったり、理解できなくなります。まず確実に動作するものを編集したり、確実に動作するところから広げていきます。

 シェルスクリプトは便利な機能です。既存のものを利用するなどして、積極的に活用していきましょう。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

バックナンバー

連載:UNIX実用シェルスクリプトシリーズ
All contents copyright © 2006-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5