UNIX コマンドの実行履歴を残すには? [1999.2]

バグを報告したり,プログラムの動作の不調を相談するとき,何より大事なのは,問題が起きたときの状況をありのままに伝えることである.設定ファイルの全体,入力したキー列,結果の全体,出力されたメッセージ,実行したマシンの仕様,OS のバージョン,プログラムのバージョンなどなど.これらを報告者の解釈を一切加えることなく,そのまま提示するのが最良の報告書である.「本を見て正しく設定したのですが動きません.助けて」なんてのは最悪である.

とは言え,良い報告をするには,その問題が再現してくれて,かつ,メッセージ等を正確に記録できなければならない.この記録のほうには役立つツールがある.ひとつは script コマンドだ.script と打ってから後の画面出力がすべてファイル (指定なしだと名前は typescript) に残る.エコーされたキー入力も記録される.ただし,普段は見えない Control-L などの画面制御コードも残る.もう一つのツールは,Emacs (NEmacs や mule も) である.M-x shell と打って,シェル対話モードを起動すると,キー入力,画面出力がすべて Emacs バッファに残される.ファイルに保存したり,そのままメールで送るのにも便利だ.ただし,パスワードなど,エコーされない文字も残るので,他人に見せるときには気をつけよう.シェル対話モードから telnet を起動すると ^M が行末につく.すべてを残すためには必要だが,次のような shell-mode のフックを .emacs に追加しておけば取り除ける (Emacs19.34版).

(add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m t t)

バグ報告以外でも,メッセージが重要で,かつそれが多いコマンド,例えば,make, patch, configure は script やシェル対話モードで走らせる習慣をつけよう.

これらで記録が取れないのは,例えば Emacs 自身である.大量の画面制御出力でなんだかわからないものが残る.このため Emacs は M-x open-termscript で,画面出力をファイルに残せるし,M-x view-lossage でキー入力記録が見えるようになっている.

本当に困るのは GUI で操作するコマンドだ.マウス操作のスライダの履歴なんて取りようがないし,報告もありのままというわけにはいかない.GUI なんですが,動きません.助けて…


† UNIX ではエラーメッセージは stderr に書き出すのが決まりだ.make のログを残そうとして
 make all > makelog
としても肝心のエラーメッセージはファイル makelog には残らない.csh (tcsh) なら
 make all >& makelog
sh や bash なら
 make all > makelog 2>&1
とすると,stdout か stderr に出力されたメッセージがどちらもファイルに書き出せる.ただ,画面には何も出ないので,最初からこうするのには抵抗があるかもしれないが,エラーが発生してそれが再現するとき,記録を残すのに気軽に使える.
sh (bash) で動かしているなら,stdout と stderr の両方を記録に残して,さらに画面に表示するのに tee コマンドが使える.
 make all 2>&1 | tee makelog
のようにする.2>&1 はファイル記述子 2 (stderr) をファイル記述子 1 (stdout) と同じ出力先に接続しろ,との指定である.csh ではこの手のファイル記述子操作は不得意†3 なので,素直に script を使おう.[2003.9.17]
‡ Emacs21 でも使えるようだ.[2003.6.27]
†3 例えば Csh Programming Considered Harmful を参照のこと.[2003.9.17]


コマンドの動きを好みに変えたいのですが… [1999.3]

自分の分身たる道具を鍛えるのは,その道を志す者の努めだ.自分の手になじむように設定をするカスタマイズは,うまくやれば環境が実に快適なものになる.コマンドの動きはシェルの設定で制御できるものが多い.コマンドの特定のオプションをいつも有効にしたいときには alias 機能がよい.漢字コードや常用エディタの指定は環境変数を設定する (setenv 等) で行なう.シェル自体もひとつのプログラム言語だから,シェルスクリプトを書いて実行パス上のディレクトリにおけば自分用のコマンドを自由に作ることができる.エディタのカスタマイズもよくある.DEL と Backspace の入れ換えやカーソル移動コマンドを変更する例も多い. 設定のためのファイルはだいたい先頭がピリオドではじまる名前のファイルにある.ls -a を打つと普段隠れたファイルが見えるが,案外数が多いものだ.

カスタマイズで解消できる不便さとはなんだろう.DEL と Backspace の入れ換えや,Control と CapsLock の入れ換えで使いやすくなったなら,それはあなたが使っているキーボードが元凶なのかもしれない.粗悪なキーボードは鍛えがいがないので直ちに破棄しよう.

カスタマイズの問題点は,環境や経験を他人と共有できないことである. Dvorak 配列のキーボードに慣れた人はコンソールで苦労するだろう.ファイル消去をファイル移動コマンドに変更している人は,root になって重要なファイルを本当に消してから慌てるだろう.他人の Emacs は -q をつけて設定ファイルを読まずに起動しないとまったく使えない.サバイバルゲームのようだが,管理者なら,single user の設定も何もない生の UNIX で一通りの作業ができないといけない.カスタマイズを知ったらカスタマイズしないことを学ぼう.

本当に手になじむ道具とは,自分で作った道具である.刀を打ち出すのは無理でもコンパイラならあなたでも作れる.言語の設計もやってみよう.人の作ったものをカスタマイズしたくらいで満足してはいけない.ここまでくれば経験の共有なんて不要だ.分けてあげるほどの経験が身につくだろう.道具ばかりでなく,あなたも相当に鍛えられるはずだ.


ロックとはなんですか? [1999.5]

UNIX は複数のユーザがログインし,多数のプロセスが同時に走行して共有物,つまりファイルやメモリ領域といった計算資源を分けあって使っている.ひとつのファイルに二つのプロセスが同時に書き込み動作することだってできる.ただし,書いた結果が混ざってしまって役に立たないが.共有物へのアクセスは,互いの動作を調整して取り決めにしたがって実行するようにプログラムする.このために使うのがロック機構である.よく使うロックは,ファイルの助言的 (advisory) ロックである.さらにこれは専有 (exclusive) ロックと共有 (shared) ロックに分けられる.

ロックとはまさに鍵で,共有物をアクセスするプログラムの場所 (際どい領域) を実行するときにロックを借り,アクセスが終ったらロックを返す.専有ロックはひとつだけしかないので,ひとつのプロセスがそれを借りたら他のプロセスは返されるまで待つしかない.共有ロックはいくつも用意されている.ただし,専有ロックが貸し出されていると共有ロックを借りることはできない.また,共有ロックがひとつでも貸し出されていると,専有ロックは借りることができない.このロックの使い分けのルールは,

ファイルを読み込むときには共有ロックを借りる
ファイルを変更するときには専有ロックを借りる

である.これを守れば,読み込みは複数のプロセスが同時に実行できるし,書き込むときには,そのファイルをそのとき読んでいるプロセスも他に書き込もうとしているプロセスも,どちらもいないことが保証できる.重要なのは,取り決めを守らないプロセスの行動を禁止してくれることはない点である.ロックの借り出し,返却は flock とか fcntl システムコールを使う.

ロックは UNIX のカーネルが管理しているが,カーネルもプログラムだ.内部でのアクセスの調整にはロックのシステムコールを使うわけにはいかない.このためには割込み禁止やバスロックといった特別の CPU 命令を直接使用する.バスロックは,例えばあるメモリアドレスの内容を読み,変更し,書き込むまでを他の CPU やデバイスにじゃまされないことを保証する.この種類の命令の実行中には CPU の特定の信号ピンの電圧が変更されるのだ.信用保証の最後はやはり物理である.


/dev にあるのはなんですか? [1999.7]

dev は device のことで,名のとおり計算機に備わっている各種の周辺装置にアクセスするための手段が,ファイルの形 (スペシャルファイル) で提供されている.ディスクや端末,メモリファイルなどが当初からあるスペシャルファイルだ.open, close, read, write といった基本的なシステムコールは /dev にあるスペシャルファイルにもそのまま使える.装置とファイルを同一視して扱い方を同じにしたことは,UNIX の特徴のひとつだ.それまでの OS (その後も?) は,コンソール操作をシリアル接続のグラスターミナルにするか,カレントループ接続のテレタイプにするか程度の切替えしかできなかった.装置は装置,ファイルはファイルだったのだ.

普通の利用者が周辺装置を直接操作することはあまりなかったが,フリーの UNIX OS が登場してからは利用者が1人でかつ管理者にならざるを得ないことが多い.ファイルシステムのチェックやディスクのバックアップには直接操作が登場する.

/dev には結び付く装置が存在しないスペシャルファイルがある.null とか zero, stdout, random などだ.これら仮想デバイスはアクセス手段としてスペシャルファイルの形をとっているが特別な動作をする.このうち当初からあるのは /dev/null で,当時のマニュアルには data sink と説明されていた.このファイルに書き出したデータはどこにも残らずに捨てられる.読み出しは常に長さ 0となる.コマンド実行ですべてのメッセージの表示を消したいとき等,大変よく使われるファイルだ.同様に,読み出すと常に指定された長さだけの 0を返す /dev/zero とか,乱数が得られる /dev/random がある.ファイル記述子を表すスペシャルファイルもあって例えばシェルスクリプトからエラー出力にメッセージを出すのに

echo "not found." > /dev/stderr

のように使える.

装置の機能は読み書きだけではない. テープを巻き戻したり,CD をイジェクトする機能など,装置ごとに特徴のある機能が備わっている.これを制御するのが ioctl システムコールだ.逆にオープン,クローズ,読み書き以外のすべての制御は ioctl で行なう.昔は制御する装置は tty だけなので ioctl でよかったが,それが現在も続いている.ioctl の引数も戻り値もエラーも装置固有だ.きれいとは言い難い.


exit と _exit の違いは? [1999.8]

man してみるとわかるが exit はセクション3のライブラリ,_exit はセクション2のシステムコールである.プロセスを終了させる点では機能は同じだが,終了に伴う後始末処理の対象が exit は利用者の記憶域に跡のある処理で,_exit はカーネル空間での処理になる.exit はまず利用者が起こしたアクションの後始末をして,最後に _exit を呼び出すのだ.典型的には,exit は利用者が atexit ライブラリ関数で登録した後始末関数を (登録の逆順に) 実行し,次に標準入出力のバッファをフラッシュする.この後,OS によっては tmpfile ライブラリ関数で作成した作業用ファイルを削除する._exit はすべてのファイル記述子を閉じる,親プロセスが wait や SIGCHLD シグナルを待っているときには,親への終了通知を出す,といったもろもろの後始末処理を行なう. _exit のカーネルでの処理は,OS によって特色が出ているので,興味があれば exit(2), intro(2) の man ページを参照してほしい.

通常のプログラムでは,実行終了に exit() を使えばいい.だが,自分で fork システムコールを使っているときに注意するのは,fork した子プロセスは exit を使わないことだ.fork は典型的には exec 系のシステムコールを使ってプログラムを実行するが,ファイルが存在しなかったり,実行権がなくて実行に失敗したとき,子プロセスを終了するのには _exit を使う.なぜなら fork システムコールは親のスタック内容,メモリ内容,開いているファイル記述子等を継承している.よって子プロセスでライブラリの exit を呼ぶと利用者空間での後始末処理が実行されて標準入出力のフラッシュが (2度) 起きたり,場合によっては一時ファイルが消えてしまったりするからだ.

一般にはシステムコールはライブラリを経由して使用するべきだが,このような例外もある.また,ライブラリを信用せずにすべてを自分で書いているプログラムもある.メール配送システムの qmail がその例だ.文字列の長さを得る strlen まで自分で用意している.その上,ループ展開までやっているという徹底ぶりには感心する.もちろん exit は出てこない.すべて _exit である.


コマンドを扱うコマンドはありますか? [1999.10]

コマンド自体を対象とするメタなコマンドは UNIX にいくつもある.分類してみよう.ここでは BSD 系によく備わっているコマンドをあげるが,すべてのマシンにあるとは限らない.実行のしかたも man で確認してほしい.


   タイトル一覧  ホーム
webmaster