tar コマンドについて教えてください [1997.8]

名前は tape archive からきている.もともとファイルのバックアップや計算機間のデータ移動のためにディスクファイルを磁気テープに書き込むためのコマンドであった.ディレクトリを指定すれば,そのディレクトリからたどっていけるサブディレクトリ内のファイルを書き込んでくれる.いまはもっと一般的に,複数のファイルをひとつのファイルにまとめたアーカイブ (tarfile) を作ったり,tarfile へのファイルの追加,取出しといった操作に使う.src.tar.gz とか src.tar.Z といった名前のファイルは,それが tar-file でかつ gzip や compress コマンドで圧縮されていることを示すためによく使われる.tar.gz を短くした src.tgz もよく使われる.アーカイブをディスク上につくるには単に ar というコマンドもあるが,こちらはディレクトリをたどらない.今は関数ライブラリ作成専用になった感がある.

tar は昔からあるコマンドなので,ほとんどの UNIX で動くと思っていい.機能は機種間で異なるので,データ移動のためにテープを作るようなときには,少し注意がいる.

  1. ディレクトリの含めたファイル名の長さに制限がある.昔のままの tar は100文字以下でなくてはならない.ディレクトリが何段にもなっているとこの制限にはすぐひっかかる.この制限は避けようがない.サブディレクトリごとに tarfile を作るといったことしかできない.
  2. 作成するときのファイル (ディレクトリ) の指定には / ではじまる絶対パスを避けよう.例えば /etc と指定してバックアップをとったとする.この tarfile からファイルを取り出すと,元の /etc に作られる.一時的に自分のホームの下とか,/tmp/etc に展開するのにはオプションの指定が必要だったり,昔の tar の場合不可能だったりする.tarfile の作成には

    cd /; tar cf ~/etc.tar etc

    といったようにディレクトリを移ってそこから相対的な指定をしよう

  3. 展開する前に t オプションで中身を確かめよう. 作成時にディレクトリではなくファイルを指定してあると,展開したときそのディレクトリに大量のファイルが作られてしまう.tar tvf tarfile として中身を確かめよう.

GNU tar では,デフォルトで先頭の / を削除します.[2003.6.7]


ファイルの数が多いと処理が遅いのですが… [1997.10]

UNIX はファイルの作成や削除が確かに遅い.手元のマシンでは,128バイトのファイルを 1000個作るのに 20秒,消すのに 40秒ほど待たされる.どちらも CPU 時間は1秒かからないのに.もちろん,CPU やディスクの性能によってこの値は異なるが,ひとつの大きなファイルを書いたり読んだりするのに比べるといかにも遅い.これはファイルの内容を読む時間ではないのだ.例えば,ファイルの作成時刻だけを変更する touch コマンドをさっきのマシンで 1000個のファイルに対して実行すると約8秒かかる.ファイル本体はまったくアクセスがないのにである.

原因は,ファイルの作成,消去で i ノード情報を更新するが,数多くのファイルを集中的に作成,消去するときには,i ノード更新に伴ってディスクの入出力が発生するためである.大きなファイルを連続して読むような場合は,次のブロックの読み込みを先に指令しておく,本体を置くディスク上の場所をできるだけ連続させたり,シークが少なくなるように配置する,といった工夫をして,ディスクを待つ時間を減らしている.また,実メモリの未使用部分をファイルのブロックのキャッシュとして割り当てて,ファイルの読み書きのほとんどをメモリの読み書きにしてしまう工夫もよくされている.変更があったファイルをディスクに書き出すのは update デーモンといったプロセスが後で順々に行なう.だが i ノードについてはかなりまじめに書き出している.計算機停止の手続きを踏まない電源断などによって,i ノードの情報がおかしくなれば,ファイルが完全に破壊されたのと同じだからだ.

UNIX においてファイルはもっとも重要な構成要素だろう.ファイル維持の安全のために遅くなっているのだ.よって使う側で工夫をして,あまり集中的にファイルの作成 / 消去を行なわないようにする必要がある.

とは言っても,遅さを最も感じるのは,バックアップのテープを全部読み出すような時である.ディスクディスクのコピーを tar で行なうようなときもとても遅い.そこで,i ノードの書き出しもその時でなく後で順次行なうようにできるファイルシステムオプションを備えた UNIX もある.mount 時に async といったオプションをつける.さきのマシンでは,これによって 1000個のファイルの作成が1秒以下になる.速度か安全性かは決断だが,バックアップテープのリストアのときだけ async でマウントして,終ったら通常モードにするといった運用がいいだろう.


FreeBSD で利用できる Softupdates の機能 (Softupdate の論文の pdf ファイルはここからたどれる) は,速度と安全性の両立に成功した技術といえる.[2003.6.7]
Softupdate の日本語の解説はここ [2003.7.17]


文字の読み込みで待たないようにするには? [1997.11]

標準入力とネットワークからといった複数の系統の入力を扱いたいとき,また,ほとんどは CPU が仕事をしているが,ときどき入力があるかを調べたい,といったプログラムでは,read の呼びだしで入力がないときは待たないで欲しい.fcntl (file control) システムコールでこのように設定することができる.しかし,待たないからといって,複数の入力を順に調べていくループを回すプログラムはいいプログラムとは言えない.read できる文字が用意されたら signal SIGIO を発行する設定を同時にして,割り込み駆動プログラムを書こう.

このような制御は OS に強く依存する.オプションの名前や引数などは man を見て確認して欲しい.まず待たないようにするには,fcntl システムコールで O_NDELAY または O_NONBLOCK といった引数を付けて F_SETFL する.O_ASYNC を同時に付ければ入力が用意されたときに SIGIO シグナルが発行される.シグナルを受け取るプロセス番号を fcntl F_SETOWN で指定する必要もある.SIGIO が発行されるのは,端末かソケットのときだけだ.名前付き pipe ストリーム (FIFO) や pty では SIGIO は発行されない. fcntl で指定するのは,open, socket, accept といったシステムコールで得られるファイル記述子であって fopen の戻り値のファイルポインタではないから注意しよう.なお,待たないのは read だけでなく,write, accept も待ちに入ることはなくなる.ネットワーク相手のときは,write も待つ可能性がある.

複数の記述子に ASYNC を設定したとき,SIGIO ではそのうちどれが読み込み可能なのかは区別できない.順に read してもよいし,BSD 系なら select システムコールを待ちなしを指定して呼び出してもいい.read ではなく,accept すべきソケットもあるかもしれないが,これはプログラムで対処しよう.read で読み込むものがなかったときには -1 が戻って errno が EAGAIN といった値に設定される.エラーと区別しよう.なお,エラー番号一覧は man ページ intro(2) にある.割り込み駆動型プログラムで実行すべき仕事がなくなったら pause システム (や sigpause, sisgsuspend) でシグナルが発行されるまで待ちに入ろう.


UNIX の落し穴は [1998.1]

UNIX を使い始めてからずいぶんになるが,いまでも落し穴にはまることがあってくやしい思いをする.コマンド,ファイルフォーマット,システムコールなどの落し穴を順不同で挙げてみる.

タブと空白: 同じに見えるものは同じものであってほしいのだが,Makefile や sendmail.cf などでは,タブと空白がはっきり区別されている.いくら画面やリストを見ても決して発見できない種類のバグとなる.ウィンドウシステムでカット & ペーストしたらおしまいだ.

rehash: インストールしたはずのコマンドが起動できないのがこれだ.ハッシュで高速化するのはいいが,動作が見えない rehash や ldconfig といったおまじないコマンドがたくさんある.

uuencode は2引数だが第1引数がオプションである: 引数をひとつ与えたときは2引数の後ろの引数の意味となる.どうしてこうなったかはわからないが,不思議な仕様である.

システムコールの中断,一部完了: ネットワークプログラミングをすると思い知るのが,read システムコールがアラームで中断してエラーとなることだ.指定した量より少ないデータを受け取ったところで戻ってくる,write システムコールで指定した量より少ないデータを書いただけで戻ってくることもある.2号前の FAQ で待ちをしない read の指定法を答えたが,stdin を NONBLOCK 指定すると,ペアの stdout も NONBLOCK 扱いになる UNIX がある.この場合,stdout についても,write の中断,実書き出しデータが小さいことがあるので注意しよう.

C の演算子の優先順位: いまだに間違えるので自己嫌悪ものだ.筆者は Lisp も書くのだから,かっこを書くのは得意なのだが,それでも & と < の順位はやはり直観に反するように思う.

消したファイルが戻らない: これは永遠の課題のような気もする.人間は必ず間違えるのだから,rm * の悲劇は不滅だ.筆者は,このところ newfs したディスクが戻らないのに困ったのが続いたが,UNIX のせいにするのはかわいそうか.

20年前から残っている穴はもう埋まらないだろう.新技術の OS は穴のないように願いたい.


PDP-11 で動いていた UNIX Version 7 から.1982年頃.[2003.6.7]


環境変数とはなんでしょう [1998.2]

PATH を設定しろ,とか,TERM をチェックしろ,とメッセージが出たりするときのこの PATH や TERM が環境変数だ.シェルは,PATH から実行ファイルを探すディレクトリを知るし,また,シェルから実行されるプログラムは環境変数のコピーを受け取るので,例えば TERM 変数の内容によって,画面制御の動作を適切なものに合わすことができる.

ただし,シェルが実行するシェルスクリプトで使う変数と環境変数は同一ではない.シェルスクリプトで使った変数はそこから実行したプログラムには渡されない.sh (Bourne shell) 系と csh 系で様子が異なるが,sh 系では,内容の設定は「名前=内容」で行なう.別に「export 名前」を実行するとその変数は環境変数扱いになって,プログラムに渡されるようになる.csh 系では,「set 名前=内容」で設定された名前はシェルの内部変数,「setenv 名前 内容」で設定されたものは環境変数となる.どちらでも env や printenv コマンドで設定されている環境変数とその内容を見ることができる.csh 系では @ でシェルの内部変数を見ることができるし,内部変数の path, user, term は環境変数 PATH, USER, TERM と連動している.

PATH や TERM で動作を決めるのはシェルや各々のプログラムであって,UNIX カーネルがそれを見ているわけではない.カーネルは呼び出すプログラムに環境変数を渡すメカニズムを提供しているだけだ.典型はシステムコール execve で,実行ファイルのパス名,引数配列 argv, 環境変数配列 envp を指定する.この envp に "変数名=内容" の形の文字列の配列を渡す.sh の export されていない変数や,csh の内部変数は含めない.C なら,main 関数を

int main(argc, argv, envp)
int argc; char **argv, **envp;

とすれば環境変数を直接受け取ることができる.envp を書かなくても,宣言

extern char **environ;

を書いておくと envp と同じ内容がこの変数 environ に入っている.execl といった環境変数を指定しないでファイルを実行するライブラリ関数は,この environ を使って execve を呼び出しているわけだ.C プログラムでなくとも awk といったプログラム実行系は受け取った環境変数を配列の形で使用できることが多い.


C で end の名前のグローバルの配列を作ったらプログラムが落ちますが…? [1998.4]

SUNOS 4.X や FreeBSD Version 2 でこれが起きる.他のシステムでも起きるかもしれない.原因は,名前 end がすでに別の用途で使われているためだ.man 3 end をすれば説明がある.end は,そのプログラムを実行するためにファイルをメモリ上に読み込んだ時,データ領域の最後の場所の次におかれた変数の形をしている.&end とすれば,そのアドレスが得られる.このアドレス以降のデータの記憶域はプログラムの実行開始時には未使用である.同様に,コード領域の最後の次に etext, 初期化されているデータ領域の最後の次には edata がある.ユーザがプログラムで end を配列として定義したとしても,実行形式ファイルが生成されるときに上書きされ,配列の実体はこの未使用領域に割り当てられてしまうのだ.もちろん宣言しただけのスペースがあるとは限らない.しかも,古めの OS 付属のコマンドでは警告も出してくれない.

それでは,end は何に使うのだろう.新たな記憶域確保を増減量で指定するライブラリ関数 sbrk は,その時のデータ領域の最後の番地を知らなければならないが,その値の初期値として end が使われる.実行が始まってからは,sbrk(0) で得られる番地がデータ領域の最後の番地である.

大域変数として使われている名前には他に4月号で述べた environ やシステムコール実行時のエラー番号がセットされる errno がある.特に errno はユーザプログラムからもたびたび参照することになる重要な名前である.システムコールを実行してカーネルが止まったりユーザプログラムが止まることは (止まれ,というシステムコール以外では) あってはならない.このためシステムコールの実現ルーチンでは与えられた引数や要求を厳重にチェックする.そして,エラーを見つけたときや,資源が不足して実行できないときにはその理由を errno にセットしてユーザプログラムに伝える.必ずしもエラーとはいえない場合も errno 経由で情報を渡すようになっている.手軽に errno の情報を見るには,関数 perror を呼べばよい.errno 番号に対応した文字列を出力してくれる.これ以外は,システムコールごとの errno 番号の解釈を man ページで調べてプログラムを作らなければならない.


さすがに最近の UNIX では落ちなくなっている.過去互換性のためか end 自体は定義されているようだが,ユーザがプログラム中で定義した end は正しく使える.[2003.5.27]


   タイトル一覧  ホーム
webmaster