用語集

Command not found コマンドが見付からないというエラー

コマンドラインからコマンド foo を実行しようとしたときに、
% foo
foo: Command not found
とエラーになった場合、以下の原因が考えられる。

1. タイプミス
まずはタイプミスを疑うこと。人間はコンピュータよりミスをするものである。

2. パスが通っていない
例えば、FreeBSD のデフォルトでは /sbin・/usr/sbin などにパスが通っていない。そのため、mount コマンドや ping コマンドはフルパスで指定するか、/sbin・/usr/sbin/ にパスを通しておかなくてはならない。
% echo $PATH
として、現在のパス設定を確認。
>> 環境変数 PATH *

3. ハッシュテーブルを再構築していない
csh か tcsh を使っている場合、コマンドをインストールした直後や、スクリプトを作成した直後に、そのコマンドを実行しようとすると実行できないことがある。これは、シェルのハッシュテーブルの内容が古いままになっており、今インストールした or 作成したばかりのコマンドを見付けられないからである。rehash コマンドを実行してから、再度コマンドを入力すべし。
>> コマンド rehash *

4. 実行権限がない
自分でスクリプトを作ったときは、chmod +x を忘れないこと。
>> コマンド chmod *

5. スクリプトのパスが違っている
これは知らないと結構はまるだろう。スクリプトの1行目には
#!/dir/foo
などと書いてあるが、この /dir/foo が存在しなかったら Command not found となるのである。
/dir/foo: Command not found.
と表示されるならわかりやすいのだが、実際は
スクリプト名: Command not found.
となるので、なかなか気づきにくい。

また、改行コードが CR LF、つまり
#!/dir/foo(CR)(LF)
となっていると、
/dir/foo(CR)
というコマンドを実行しようとして Command not found となる。DOS や Windows で作成したスクリプトを実行するときは注意。

改行コードの確認をするには、hd コマンドや od コマンドでスクリプトをダンプしてみるとよい。
>> コマンド hd *   od *
改行コードの変更は、tr・perl・qkc などを使うとよい、
>> コマンド tr *   perl *   qkc *
>> 用語集 シェバング *

6. コマンドが存在しない
以上の 1〜5 のいずれでもないなら、そもそもそういうコマンドはないということ。どこかから探してきてインストールしましょう。ただし、その前に locate コマンドや find コマンドで、どこかにインストールされていないか調べてみるとよい。
>> コマンド locate *   find *

NIS 複数のマシンでユーザ情報を共有する

複数のマシンを LAN に繋いでいる場合、/etc/passwd・/etc/group などのユーザ情報がそれぞれのマシンに分散するのは好ましくない。一つのユーザの情報を書き換えるだけでも、複数のマシンにログインしてファイルを書き換えることになり、めんどくさいからである。

そこで、Sun Microsystems が YP (Yellow Page) という機能を開発した。ネットワーク越しにユーザ情報をやりとりするもので、一度情報を更新すると、他のマシンでも同じ情報が利用できる。その後、YP は NIS という名称に変わったが、依然として yp で始まるコマンド名が使われている。

NIS は、/etc/passwd・/etc/group のユーザ情報や、/etc/protocols・/etc/services などのネットワークに関するファイルを一元管理することができる。具体的には、NIS サーバという中心となる役目のマシンを一つ決めておき、その他のマシン (NIS クライアント) は、ユーザ情報・ネットワーク情報が必要になるたびに NIS サーバに問い合わせることになる。

これにより、
  • NIS サーバでユーザアカウントを作成しておけば、どの NIS クライアントでもログインすることができる。
  • ログインシェル・GECOS フィールドなど、各ユーザ固有の情報を変更すれば、NIS を利用しているマシン全体に反映される。
というように、一元管理ができるわけである。

NIS クライアントでは、/etc/passwd・/etc/group・/etc/protocols・/etc/services などの各ファイルは本来の意味を持たない。ypcat コマンドで見ることができる。NIS を利用して得られる情報の一覧は
% ypcat -x
Use "passwd" for "passwd.byname"
Use "master.passwd" for "master.passwd.byname"
Use "group" for "group.byname"
Use "networks" for "networks.byaddr"
Use "hosts" for "hosts.byaddr"
Use "protocols" for "protocols.bynumber"
Use "services" for "services.byname"
Use "aliases" for "mail.aliases"
Use "ethers" for "ethers.byname"
でわかる。例えば
% ypcat passwd
とすると、「NIS を利用して /etc/passwd に相当する情報を表示する」ことができる。同様に、
  • ypcat group ... /etc/group
  • ypcat networks ... /etc/networks
  • ypcat hosts ... /etc/hosts
  • ypcat protocols ... /etc/protocols
  • ypcat services ... /etc/services
  • ypcat aliases ... /etc/aliases
  • ypcat ethers ... /etc/ethers
というふうに対応している。
>> コマンド passwd *   chsh *   chpass *

setuid 特定の権限でコマンドを実行する仕組み (suid・s-bit・sbit)

passwd・chpass などのコマンドを使うと、ユーザのログインシェルやパスワードなどの個人情報を変更できる。これらの情報は /etc/passwd や /etc/master.passwd などに保存されるわけである。

ここでよ〜く考えてみよう。つまり passwd や chpass は /etc/passwd や /etc/master.passwd などのファイルを更新するということだ。もちろんこれらのファイルは、一般ユーザが書き換えできないようにパーミッションが設定されている。
% ls -l /etc/passwd /etc/master.passwd
-rw------- 1 root wheel 994 Sep 26 20:00 /etc/master.passwd
-rw-r--r-- 1 root wheel 793 Sep 26 20:00 /etc/passwd
なぜコマンドを実行しているのは一般ユーザなのに、root しか書き換えられないはずのファイルを書き換えることができるのだろうか?

その秘密が setuid (suid・sbit) である。ls で passwd や chpass のバイナリのパーミッションを見てみよう。
% ls -l /usr/bin/{passwd,chpass}
-r-sr-xr-x 6 root bin 36864 Mar 14 1999 /usr/bin/chpass
-r-sr-xr-x 2 root bin 32768 Jul 22 1998 /usr/bin/passwd
パーミッションの最初の部分が「r-s」となっていることに注意してほしい。「s」という文字は
「そのコマンドが所有者の権限で実行される」
という意味である。/usr/bin/chpass・/usr/bin/passwd というファイルの所有者は root なので、一般ユーザが /usr/bin/chpass を実行すると、root の権限を得ることになる。そのコマンドが終了すると root 権限は失われる。

UNIX において、setuid という仕組みは、かなり広い範囲で使われている。
% ls -l /{,usr/,usr/local/}{,s}bin/ | grep 'r[-w]s'
としてみるとわかる通り、passwd・chpass に始まって、login・man・procmail・rcp・ping・traceroute・shutdown・crontab なども setuid されている。

例えば man には catman という仕組みがあり、一度参照されたマニュアルは /usr/share/man/cat? にプレインテキストの形で保存しておく必要がある。しかし /usr/share/man/cat? は
% ls -ld /usr/share/man/cat1/
drwxr-xr-x 2 man bin 1024 Oct 10 13:57 /usr/share/man/cat1/
と、一般ユーザが書き込めるようにはなっていない。そのため man コマンドは
% ls -l `which man`
-r-sr-xr-x 1 man bin 28672 Jul 22 1998 /usr/bin/man
と、ユーザ man の権限で動くようになっている。
>> コマンド catman *   man *

また、ping・traceroute コマンドは raw socket という低レベルのネットワーク機能を使っているのだが、raw socket は root 権限がないと使うことができない。しかし一般ユーザにも ping・traceroute コマンドを使うことができるように、このように root に setuid されているわけだ。
% ls -l `which ping traceroute`
-r-sr-xr-x 1 root bin 139264 Jul 22 1998 /sbin/ping
-r-sr-xr-x 1 root bin 16384 Jul 22 1998 /usr/sbin/traceroute

setgid (sgid) も同様に、
「そのコマンドが所有グループの権限で実行される」
ことを意味する。例えば procmail は
% ls -l `which procmail`
-rwsr-sr-x 1 root mail 65536 Jun 30 1998 /usr/local/bin/procmail
と、「rwsr-s」となっている。これは、procmail コマンド実行時に、
「実効ユーザは root、実効グループは mail」
として動作する (つまり procmail は suid+sgid されているということ)。

では、実際に setuid なプログラムを作成してみよう。シャドウパスワードである /etc/master.passwd は一般ユーザでは見ることができないが、これを一般ユーザでも見られるようなコマンドを作る(シャドウパスワードは /etc/shadow となっている OS もある)。
#include <stdio.h>
#define shadow_passwd "/etc/master.passwd"
main(){
FILE *fp;
char buf[256];

fp = fopen(shadow_passwd,"r");
if ( fp == NULL ){
perror(shadow_passwd);
exit(1);
}
while (1){
if ( fgets(buf,sizeof(buf),fp) == NULL ) break;
printf("%s",buf);
}
}
という内容の cat-shadow.c というソースを作り、
% cc -o cat-shadow cat-shadow.c
でバイナリを作成する。では実行してみよう。
% ./cat-shadow
/etc/master.passwd: Permission denied
一般ユーザは /etc/master.passwd を見ることはできないのだから当然エラーになる。では、このプログラムを suid してみよう。現在は
% ls -l cat-shadow
-rwxr-xr-x 1 user group 8808 Oct 10 16:05 cat-shadow
というパーミッションになっている。root になって
% su (root になる)
# chown root cat-shadow (ファイルのオーナーを root に)
# chmod 4755 cat-shadow (sbit を立てる)
これで cat-shadow が root に suid された。
% ls -l cat-shadow
-rwsr-xr-x 1 root group 8808 Oct 10 16:05 cat-shadow
ちゃんと「rws」になっていることを確認してほしい。では再度実行。
% ./cat-shadow
root:$1$7.KA4$3NMQsk3vjh33yNm.GROmA0:0:0::0:0:Charlie &:/root:/bin/csh
toor:*:0:0::0:0:Bourne-again Superuser:/root:
daemon:*:1:1::0:0:Owner of many system processes:/root:/sbin/nologin
(略)
これで一般ユーザが、一時的に (コマンドを実行中の間だけ) 特別な権限を取得できたわけである。

では、スクリプトを suid できるのだろうか?
#!/bin/sh
/bin/cat /etc/master.passwd
というスクリプトを作って、
% su
# chown root cat-shadow.sh (ファイルのオーナーを root に)
# chmod 4755 cat-shadow.sh (sbit を立てる)
としても、
% ./cat-shadow.sh
cat: /etc/master.passwd: Permission denied
となる。つまりスクリプトの suid bit は無視されるわけである。これには深い理由があるのだが割愛。

しかし perl スクリプトだけは別である。perl は スクリプトが suid されているかどうかを調べ、suid されていたら suidperl を起動する。suidperl 自体が
% ls -l `which suidperl`
---s--x--x 2 root wheel 458752 Jun 30 1998 /usr/local/bin/suidperl
と root に suid されているので、誰のユーザ権限にでも移行できる。

最後に注意。suid はとても危険である。root に suid されたコマンドにセキュリティホールがあると、そのホストのセキュリティは無いも同然。例えば chpass コマンドは、環境変数 EDITOR で指定されているプログラムを実行する。しかし単純に EDITOR で指定されたプログラムを実行すると、悪意を持ったユーザが
% env EDITOR="rm -rf /" chpass
とすることで、root 権限で rm -rf / が実行されることになる。これを避けるために、実際の chpass では、/etc/master.passwd や /etc/passwd などを書き換えるときだけ root 権限になり、それ以外の部分では一般ユーザ権限で動作するようにプログラムされている。また、環境変数 PATH などにも気をつけなければならない。

sudo コマンドを使うと、指定したユーザが指定したコマンドを実行できるようにすることができる。
>> コマンド sudo *   chmod *   ls *

穴あきファイル 内部に NULL を含むファイル (穴空きファイル・Sparse file・疎なファイル)

穴あきファイルとは、内部に NULL データを含むファイルで、なおかつディスク上には一部データの実体が存在しないファイル、である。

穴あきファイルを作ってみる
穴あきファイルを実際に作ってみよう。以下のようにしてファイルサイズが同じな 3つのファイルを作成する。
% perl -e 'open(OUT,">normal.dat");print OUT "a" x 1000000'
⇒ normal.dat は 'a' で埋まったファイル
% perl -e 'open(OUT,">zero.dat");for(1..1000000){printf OUT "%c", 0}'
⇒ zero.dat は NULL (0x00) で埋まったファイル
% perl -e 'open(OUT,">sparse.dat");seek(OUT,999999,0);printf OUT "%c", 0'
⇒ sparse.dat は、1MB 弱シークで移動し、NULL (0x00) を出力したファイル

作成されたファイルを ls で見てみよう。ただし -s オプションを付けて、消費ブロックサイズを表示する。
% ls -ls
  1008 -rw-r--r--   1 user  group   1000000 Jun 25 14:57 normal.dat
  1008 -rw-r--r--   1 user  group   1000000 Jun 25 14:56 zero.dat
    32 -rw-r--r--   1 user  group   1000000 Jun 25 14:56 sparse.dat

normal.dat と zero.dat 使用ブロック数が 1008 なのに対し、sparse.dat は 32 しか使用していない。この環境のブロックサイズは 1024 バイトなので、normal.dat と zero.dat は約 1MB の領域を占めているのに対し、sparse.dat は 32KB しか使用していないことになる。この sparse.dat のようなファイルを「穴あきファイル」と呼ぶ。

穴あきファイルを作るには、ファイルをオープンし、ファイル終端よりも後ろにシークして、何かデータを出力すればよい。そうすれば、中間のデータは NULL (0x00) となり、そのデータはディスク容量を消費しなくなる (間接ブロックの分は必要だが)。

穴あきファイルのメリット
穴あきファイルのメリットは何か。当ページ管理人が思うに、以下ようなものであろう。
  • ディスクを節約したい
  • アクセス速度を高速にしたい
  • 実際のデータ量は少ない
例えば、全国民の氏名・住所などを一元管理するシステムを作るとする。カードを発行して、役所で便利なサービスを提供したい。カードの使用履歴をファイルに保持したい。人数を 1億人として、ひとりあたり 10KB の容量があれば十分な履歴情報を保持できる。データ量は 1TB になるが、1TB のハードディスクを買う予算がない。

悩んでいるとそこに「このシステムは不人気で誰もカードを持とうとしない」という情報が。どうやら国民の1%しかカードを持っていないらしい。ということは実際に必要なデータは 100GB。

ここで穴あきファイルを使って、先頭 10KB は 住民コード 0番の人、次の 10KB は 住民コード 1番の人…、と割り当てれば、
open(OUT, "<+ data.dat");
seek(OUT, 10240 * 住民コード番号, 0);
というアクセス方法で高速に履歴情報を参照・更新でき、なおかつカード発行率は 1% なので実データを 100GB 程度でおさめることができる。

…と苦しい例を出してみたが、ディスク容量が飛躍的に増え、マシン性能が上がった今となっては、穴あきファイルを使うのはやめた方がよいと当ページ管理人は思う。以下に、穴あきファイルの管理の難しさを示す。

穴あきファイルの注意点
穴あきファイルに対して不用意な操作をするとファイルサイズが突然増えることがある。たとえば多くの UNIX では穴あきファイルを cp すると、コピー先ファイルのブロックサイズが増え、1008 ブロック消費するようになる。
% cp sparse.dat sparse2.dat
% ls -l
32 -rw-r--r-- 1 user group 1000000 Jun 25 14:56 sparse.dat
1008 -rw-r--r-- 1 user group 1000000 Jun 25 14:56 sparse2.dat
tar も同様で、tar cf でアーカイブを作成し、tar xf で展開すると、穴あきファイルは普通の NULL 埋めファイルになってしまう。

この例では 1MB 程度のファイルなので問題ないが、数十GB の穴あきファイルをコピーしたりバックアップしたりすると、容易にディスクがあふれてしまうだろう。

これは cp や tar が悪いのではない。穴あき部分を read(2) しても、NULL (0x00) が連続しているデータとして読み込まれるので、各コマンド側から穴あきファイルかどうかを判断することはできないからである。どうしても穴あきファイルをみわけたければ、ブロックサイズから計算した実サイズと、みかけのサイズを比較するか、dump コマンドのようにデバイスを直接参照するしかない。

GNU tar ではアーカイブ作成時に -S オプションをつけると穴あきファイルを「穴あきファイルとして」アーカイブするこができる。ただし、この方法で作成したアーカイブは POSIX 標準 tar フォーマットではないため、GNU tar 以外では展開できないだろう。また、GNU の cp はデフォルトで穴あきファイルを正しくコピーできる。この挙動は --sparce オプションで変更できる。GNU tar と GNU cp は、前述の実サイズとみかけのサイズを比較する方法で穴あきファイルかどうかを判断している。

UNIX において穴あきファイルを正しく扱えるのは pax・dump・dd・cpio・ufsdump などである。
>> コマンド dd *

その他
ファイルシステムによっては穴あきファイルをサポートしていないものもある。そのようなファイルシステム上で穴あきファイルを作ろうとしても、実際に NULL (0x00) が詰まったファイルとして生成されるだろう。例えば、Solaris は (デフォルトでは) /tmp がメモリ上に確保されるが、この /tmp 上では穴あきファイルは生成できない。

Linux では、カーネルだか newfs だか mkfs だかのオプションで、ファイルシステムごとに穴あきファイルを生成するかどうかを制御できたような気がしないでもない。

当ページ管理人は、穴あきファイルなどディスクを 1バイトでも節約したかった時代の過去の遺物であり、はやくなくなってしまえと思っていたのだが、Windows でも穴あきファイルが導入されてしまった (Windows 2000 の NTFS 2000 以降で sparse file が使用可能)。需要あるんですかねぇ。
>> コマンド dd *

戻り値 ステータスコード・終了ステータス・返り値

UNIX における戻り値について説明しよう。

コマンドの終了ステータス
ls(1)、cat(1) などのコマンドが返す値は、終了ステータス・ステータスコード・exit status などと呼ばれる。ほとんどのコマンドは以下のルールに従って実装されている。
  • 正常終了した場合は 0 を返す。
  • 異常終了時は 1 以上を返す。

個々のコマンドの終了ステータスを調べたい場合は、必ずマニュアルを参照しよう。たとえば FreeBSD の ls コマンドのマニュアルを見ると、上記のルール通り
ユーティリティ ls は、成功すると 0 で、エラーがあった場合は >0 で終了します。
となっている。しかし diff コマンドはルールから外れており、
比較結果として、違いが無かった場合は 0 を、違いが発見された場合は 1 を、何かエラーがおきた場合は 2 を返します。
とある。つまり、最終的にはマニュアルで確認するしかないということだ。

エラー値はコマンドごとにばらばらかと言うと そうではなく、ゆるいながらも一応の指針はある。エラーの種類は sysexits(3) で定義されている。以下、いくつか例をあげる。
  • 64 (EX_USAGE)
コマンド使用法エラー。オプション異常など。
  • 65 (EX_DATAERR)
入力データ異常。
  • 75 (EX_TEMPFAIL)
一時的な動作エラー。もう一度同じことをやれば成功するかもしれないエラー。~/.forward から procmail を呼び出す際に
"|IFS=' ' && exec /usr/local/bin/procmail -f- || exit 75 #username"
と書くが、この exit 75 というのは EX_TEMPFAIL を返しているわけである。

自作のシェルスクリプトやコマンドでは、最低限
  • 正常終了時は 0 を返す
  • 異常終了時は 1 (あるいはそれ以上の値) を返す
とすべきである。もし余力があれば sysexits(3) に従うとよい。

なお、終了ステータスは 0〜255 で、負の数は返せない。

システムコールの戻り値
open(2)、close(2)、read(2)、write(2)、connect(2) などのシステムコールは、正常終了なら 0、エラーなら -1 を返す。エラーの場合は識別子 errno にエラーの原因を表す値が入る。

例えば FreeBSD 4.7-RELEASE の stat(2) のマニュアルには、以下のように errno のエラーの種類が書かれている。stat(2) がこれ以外の errno を返すことは絶対にないと考えてよい (もちろん OS が違ったり、同じ FreeBSD でも異なるバージョンであればその限りではない)。

EACCES 指定されたパスには、検索が許可されていないディレクトリが含まれている
EFAULT sb か name は、プロセスに割り当てられたアドレス空間の範囲外を指している
EIO ファイルシステムでの読み書き中に入出力エラーが発生した
ELOOP パス名を変換するときに検出されたシンボリックリンクが多すぎます。
ENAMETOOLONG パス名の構成要素が 255 文字を越えているか、またはパス名全体が 1023 文字を越えている
ENOENT 指定されたファイルが存在しない
ENOTDIR パスの構成要素中にディレクトリ以外のものが含まれている

errno に意味のある値が入っているのは「システムコールを実行し、エラーが起こったとき」である。正常終了時に errno を参照しても意味のある値が入っていると期待してはいけない。

また、別のシステムコールを実行してエラーが発生した場合は、errno は上書きされる。つまり errno は「最後にエラーとなったシステムコールの原因を表す値」である。

なお、システムコールの戻り値には以下のような例外はある。
  • fork(2) は親プロセスに、生成した子プロセスのプロセス ID を返す。
  • execve(2) は成功したら返ってこないので、戻り値がない。

なお、昔は errno はグローバル変数であったが、スレッドの普及に伴い、同じプロセスでもスレッドごとに異なる errno を扱う必要が出てきた。例えば FreeBSD では errno は「スレッドごとのエラー値を管理している構造体のフィールドへのポインタ」に変わっている。

ライブラリルーチンの戻り値
fopen(3)、printf(3) などのライブラリルーチンの戻り値は、千差万別である。とにかくマニュアルを見てくださいとしか言いようがない。
>> 読み方 errno *

サブシェル シェルから起動された子プロセスのシェル

サブシェルとは、シェル (sh や csh など) から起動された子プロセスのシェルのことである。特に、シェルスクリプト上から起動したシェルのことをサブシェルと呼ぶことが多い。

シェルのプロンプトが表示されているときに
% (コマンド)

% (コマンド1 ; コマンド2)
とすると、シェルはサブシェル (子プロセス) を起動し、サブシェルは括弧内のコマンドを実行する。

サブシェルを使うことで、プロセスをグループ化することができる。例えば
% (コマンド1 ; コマンド2) | コマンド3
% (コマンド1 ; コマンド2) > 出力ファイル
% (コマンド1 ; コマンド2 ; コマンド3) > 出力ファイル
% コマンド1 | (コマンド2 ; コマンド3)
% (コマンド1 | コマンド2) | (コマンド2 ; コマンド3)
などと複数のコマンドの出力を 1つのパイプに流したり、1つのファイルに渡すことが可能になる。

まず、シェルとサブシェルは別プロセスであることに注意しよう。
% cd src ; make
とするとシェルのカレントディレクトリが変更されてしまうため、元のディレクトリに戻るには cd .. などとしなくてはならない。しかしサブシェルを使って
% (cd src ; make)
とすれば、元々起動していたシェルのカレントディレクトリは変更されない。

これはカレントディレクトリはプロセスごとに管理されているためで、サブシェルのカレントディレクトリが変更されても、親プロセスであるシェルのカレントディレクトリには影響を与えないからである。

まとめると以下のようになる。

使い道 1
別のディレクトリで作業をしたいが、カレントディレクトリは変更したくない場合。
% (cd src ; make)
使い道 2
プロセスごとに異なるカレントディレクトリを設定したい場合。
% cd /dir1 ; tar cf - . | (cd /dir2 ; tar xf -)
特定プロセスのみに環境変数を指定したい場合。
% (env HOGE=fuga; cmd1 ; cmd2)

つまり、カレントディレクトリ・環境変数・umask・リミットなど、プロセス固有の値を一時的に変更したい場合、影響範囲をサブシェル内におさえることができる。
>> 用語集 プロセス *

使い道 3
複数プロセスをまとめてバックグラウンドで動かす場合。100秒後に cmd1 を実行したい場合、
% sleep 100; cmd1
とすると 100秒間端末を占有されてしまう。サブシェルを使うと、両方のプロセスをバックグラウンドで動かすことができる。
% (sleep 100; cmd1) &
>> 用語集 シェルスクリプト *

シェバング シェルなどのインタプリタを起動するための「#!」。shebang。

sh・csh・perl・ruby などのスクリプトの先頭行は
#!/bin/sh
#!/bin/csh -f
#!/usr/bin/perl
などと記述するが、この「#!」のことを「シェバング」(shebang) と呼ぶ。また、この行全体を「シェバング行」と呼ぶこともある。シェバングの語源は「sharp bang」「shell bang」など、いくつかあるようだ。

コンピュータが直接解釈できるのはマシン語だけである。しかし UNIX では、ファイルの先頭 2バイトが「#!」であった場合は、その後に記述されている別のコマンドを実行しようとする。

例えばシェルスクリプト hoge.sh の先頭行が
#!/bin/sh
であるとしよう。
% ./hoge.sh
と実行しようとしたとき、カーネルは hoge.sh の先頭 2バイトが「#!」であるため、その後に続く「/bin/sh」を実行するが、その際スクリプトのファイル名が引数として渡される。

つまり、
% ./hoge.sh

% /bin/sh ./hoge.sh
は (基本的には) 等価である。

% ./hoge.sh arg1 arg2 arg3
とコマンドラインで引数を指定した場合は
% /bin/sh ./hoge.sh arg1 arg2 arg3
と等価となる。

シェバング行に引数があった場合、たとえば fuga.csh の先頭行が
#!/bin/csh -f
の場合は以下のようになる。
% ./fuga.csh
⇒ /bin/csh -f ./fuga.csh と等価
% ./fuga.csh arg1 arg2 arg3
⇒ /bin/csh -f ./fuga.csh arg1 arg2 arg3 と等価

sh・csh・perl・ruby などは、# から始まる行をコメント扱いすることに注意しよう。1行目のシェバング行はただのコメントとしてスルーされるわけである。
% cat fuga.csh
#!/bin/csh -f
(略)
% ./fuga.csh
⇒ /bin/csh -f fuga.csh と等価
% /bin/csh ./fuga.csh
⇒ /bin/csh ./fuga.csh と等価 (シェバング行はただのコメント扱い)

シェバング行にはシェルや perl などの言語だけではなく、どんなコマンドでも書ける (それが役に立つかは別の話)。例えば、
#!/bin/cat -n
hoge
fuga
というファイル selfcat を作り、それを実行すると
% ./selfcat
1 #!/bin/cat -n
2 hoge
3 fuga
と自分自身を行番号付きで表示する。この場合は
% /bin/cat -n ./selfcat
相当のコマンドが実行されたわけである。

Tips.1
sh や csh のバイナリはまず間違いなく /bin 直下に置かれているため、
#!/bin/sh
#!/bin/csh
などと書けばよい。しかし perl や ruby などの新しめのコマンドは、OS によって /usr/bin にあったり /usr/local/bin にあったりと、置き場所がバラバラである。そこで
#!/usr/bin/env ruby
などと env コマンドを使用することが多い (特に ruby 界隈でよくみかける)。どこの ruby コマンドが実行されるかは、実行時に設定されている環境変数 PATH の内容次第であるため、確実性には少し欠ける。

ただし /usr/bin/env がない環境も少ないながら存在するため万能ではない。例えば 10.2 (Jaguar) より前の Mac OS X では /usr/bin/env がない。あと、AIX だか IRIX だか Tru64 も /usr/bin/env ではなく /bin/env だったはず。
>> コマンド env *

Tips.2
シェバング行に
% cat hoge.sh
#!/bin/cmd -a -b -c def #comment
(略)
などと複数のオプションを指定した場合の挙動は OS によって異なる。上記スクリプトを
./hoge.sh
と実行した場合、各 OS では以下のように解釈される。

Linux・IRIX・Tru64・AIX・HP-UX・Mac OS X:
/bin/cmd "-a -b -c def #comment" ./hoge.sh
⇒ 全ての引数がひとつにまとめられる
FreeBSD 2.2.7-RELEASE:
/bin/cmd "-a" "-b" "-c" "def" ./hoge.sh
⇒ 引数がひとつずつ渡されるが # 以降は捨てられる
FreeBSD 4.0-RELEASE:
/bin/cmd "-a" "-b" "-c" "def" "#comment" ./hoge.sh
⇒ # 以降も含め、引数がひとつずつ渡される。
FreeBSD 5.5-RELEASE:
/bin/cmd "-a -b -c def #comment" ./hoge.sh
⇒ Linux などの多数派と同じ挙動になるよう修正
Solaris8:
/bin/cmd -a ./hoge.sh
⇒ 最初の引数しか渡されない。
参考: http://lists.freebsd.org/pipermail/freebsd-arch/2005-February/003525.html

移植性を重視するなら、シェバング行の引数はひとつだけにしておこう。もし複数の引数を記述する必要があるなら、以下のようなシェルスクリプトにすることをお勧めする。
#!/bin/sh
exec /bin/cmd -a -b -c def "$0" "$@"
>> コマンド exec *

Tips.3
シェバング行に記述するコマンドは、必ずバイナリでなくてはならい。
a.sh
#!/home/user/b.sh
b.sh
#!/bin/sh
(略)
という 2ファイルがあるとする。このとき、
% ./a.sh
は動かない (エラーにもならない。終了ステータスは 0)。シェバング行に書かれているコマンドがスクリプトであった場合は、再帰的なシェバングの解析は行われない (FreeBSD 5.2.1-RELEASE・Linux・Solaris8・HP-UX11i で確認)。ただし、a.sh を
#!/usr/bin/env /home/user/b.sh
と env コマンドを使うようにするという逃げ道はある。

Tips.4
perl のシェバング解析は、UNIX 界においてはかなり「変」である。perl は自前でシェバング行を解析してオプションを読み取ろうとする。sh や csh はこういうことはしない。
% cat foo.pl
#!/usr/bin/perl -w
(略)
% ./foo.pl
⇒ /usr/bin/perl -w foo.pl と等価
% /usr/bin/perl foo.pl
⇒ perl がシェバング行を解析して -w オプションを読み取るため、結局は /usr/bin/perl -w foo.pl と等価となる
perl は sh スクリプトさえも実行してくれる (perl がシェバング行を読んで、/bin/sh を実行しているだけ)。
% cat bar.sh
#!/bin/sh
echo This is sh-script

% perl bar.sh
This is sh-script

Tips.5
UNIX の改行コードは LF (0x0A) であるが、スクリプトの改行コードを CR LF (0x0D 0x0A) にしてしまうと、シェバング行の解釈時に「Command not found」となるが、エラーメッセージがわかりづらいのではまらないように。
>> 用語集 Command not found *

Tips.6
スクリプトの名前を -h などとすると、大抵の場合うまく動作しなくなる。
% cat ./-h
#!/usr/bin/perl
(略)

% -h
Usage: perl [switches] [--] [programfile] [arguments]
(略)
⇒ /usr/bin/perl -h と実行されるが、ファイル名である -h がオプションと誤認されたため

よって、シェバング行には
#!/usr/bin/perl --
と、オプションの終わりを表す -- を付けた方がよい。ただし、当ページ管理人はそこまで気を遣ったりはしない。わざわざ -h なんてファイル名に変更する方が悪い。

Tips.7
シェバング行の最大長は OS によって異なるが、かなり小さいものもある。移植性を考えると、128バイト以内におさめた方がよい。ちなみに FreeBSD 4.4-RELEASE 以前の最大長は 64バイト、SunOS4 は 32バイトであった。

Tips.8
emacs (mule) では、ファイルの 1行目に特定の書き方をすることで、メジャーモードなど各種設定を行うことができる。

「-*-」と「-*-」の間に hoge と書くと、hoge-mode になる。
#!/usr/bin/perl # -*- perl -*-
⇒ このファイルを emacs で開くと自動的に perl-mode になる。
これはシェバングとは直接は関係ない。例えば C であっても
/* -*- lisp -*- */
main(){ ... }
と書けば lisp-mode になる。

普通は「拡張子 .c は c-mode」「.pl は perl-mode」などと拡張子でモード設定を行うので、このような書き方は必要ないが、どうしても拡張子がないスクリプトを書かざるをえなくなったときに活用するとよい。
>> コマンド emacs *

Tips.9
今どきの UNIX では、シェバング行を解析するのはカーネルの役割である。ただし はるか昔の UNIX、例えば NEWS-OS はライブラリ exec*(3) 側でシェバング行を解析していた、とNeco氏がどこかで語っていたような気がする。

Tips.10
シェバングに関する、より詳細な情報は以下の URL を参照のこと。
>> 用語集 シェルスクリプト *

シェルスクリプト コマンド実行を自動実行するためのファイル (if/else/foreach/while/case/switch)

シェルスクリプトは
#!/bin/sh
echo "Hello World"

#!/bin/csh -f
echo "Hello World"
などと、シェルが解釈できるコマンドを羅列したファイルのことを指す。

sh が解釈できるシェルスクリプトのことを「sh スクリプト」(エスエイチスクリプト)、csh が解釈できるシェルスクリプトのことを「csh スクリプト」(シーシェルスクリプト) と呼ぶことがある。

なお、シェルスクリプトのことを指して「シェル」と呼んではいけない。本人は略しているだけのつもりかもしれないが、端から見るとシェルとシェルスクリプトの区別が付いていないように見える。よくわかっていない人が
「ファイルが消えた原因はシェルのバグです」
と主張するとき、原因は sh や csh のバグではなく、ほとんどの場合その人が書いたシェルスクリプトのバグにすぎない。当ページ管理人のネット上・実社会での観測結果によると、「シェルスクリプト」を「シェル」と呼ぶ人のスキルは著しく低い傾向がある。

sh スクリプトも csh スクリプトも、コマンドを羅列するという点では共通であるが、シェルの内部コマンド・変数の扱い・パイプ・リダイレクトなどの点で大きな違いがある。以下に簡単な相違点をあげる。

シェル変数セット
sh:
hoge=FUGA
csh:
set hoge=FUGA

環境変数セット
sh:
HOGE=FUGA
export HOGE
export HOGE=FUGA (一部 sh・bash の方言)
csh:
setenv HOGE FUGA

変数参照・変数代入
sh:
echo $i
j=$i
csh:
echo $i
set j=$i

一定回数のループ
sh:
i=0
while [ $i -lt 5 ]; do
echo $i
i=`expr $i + 1`
done
csh:
set i=0
while ( $i < 5 )
echo $i
@ i = $i + 1
end

リスト処理
sh:
vars="x y z"
for var in $vars; do
echo $var
done
csh:
set vars=(x y z)
foreach var ($vars)
echo $var
end

行単位の処理
sh:
コマンド実行結果から:
ls -l | while read line; do
echo $line
done
ファイルから:
while read line; do
echo $line
done < file.txt
ヒアドキュメントで:
while read line; do
echo $line
done <<END
hoge
fuga
END
csh:
どうやるんだっけ?

コマンド実行結果のリスト処理
sh:
for file in `ls`; do
echo $file
done
csh:
foreach file (`ls`)
echo $file
end

if 文
sh:
if [ $var = "abc" ]; then
echo "var is abc"
elif [ $var = "def" ]; then
echo "var is def"
else
echo "var is not abc nor def"
fi
>> コマンド if *
csh:
if ( $var = "abc" ) then
echo "var is abc"
else if ( $var = "def" ) then
echo "var is def"
else
echo "var is not abc nor def"
endif

continue・break
sh:
continue・break は for・while から脱出・次ループへ移動を行う。
cotinue 数字
break 数字
などとすることで、内側から何個目の for・while に対しての指示なのかを指定できる。
continue
continue 1

break
break 1
は、それぞれ等価である。
for file in `ls`; do
if [ $file = "hoge.txt" ]; then continue
if [ $file = "fuga.txt" ]; then break
done
csh:
continue・break は foreach・while から脱出・次ループへ移動を行う。csh の continue・break は、最も内側のループにしか効かない。
foreach file (`ls`)
if ( $file = "hoge.txt" ) then
continue
endif
if ( $file = "fuga.txt" ) then
break
endif
end

switch 文
sh:
case $var in
hoge)
echo "var is hoge"
;;
foo|bar)
echo "var is foo or bar"
;;
*)
echo "var is unknown"
;;
esac
csh:
switch ($var)
case "hoge":
echo "var is hoge"
breaksw
case "foo":
case "bar":
echo "var is foo or bar"
breaksw
default:
echo "var is unknown"
breaksw
endsw

Yes か No を答えさせる
sh:
while [ 1 ]; do
/bin/echo -n "Type Yes/No: "
read line
case $line in
[yY][eE][sS])
echo YES; break
;;
[nN][oO])
echo NO; break
;;
esac
done
csh:
どうやるんだっけ。
>> コマンド sh *   csh *   tcsh *   bash *   if *   test *   for *   foreach *
>> 用語集 シェバング *

シェル記号類まとめ sh・csh・tcsh・bash などのシェル・シェルスクリプトの記号まとめ (> >> 2>&1 < << $$ >& & && ( ) | || <<- <& >| <>)

シェル関係の記号類を中心にとしたまとめ。

> >> 2>&1
リダイレクト。> はファイルへの出力を、>> はファイルへの追記を表す。
>> 用語集 リダイレクト *

$$
プロセス ID を表すシェルの変数。sh 系・csh 系を問わず、あらゆるシェルで利用可能。

<<
ヒアドキュメント。シェルスクリプトなどにおいて、複数行にわたるファイルへの出力内容をスクリプト内に記述する場合などに使用される。
#!/bin/sh
cat <<END
line1
line2
line3
END
このとき、最後の END の前後に空白やタブを付けてはならない。

ヒアドキュメントの中は $foo などの変数展開や、`〜` のコマンド行置換が行われる。これを抑止したい場合は <<'END' などとクォートで囲むとよい。
#!/bin/sh
cat <<END
path is $PATH (no quote)
END
cat <<'END'
path is $PATH (quoted)
END
実行結果は以下の通り。
path is /sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin
⇒ クォートで囲まないと変数が展開される
path is $PATH
⇒ クォートで囲むと変数展開は行われない

sh・bash の場合、<< の部分を <<- とすることで、ヒアドキュメント中の行頭のタブ (TAB) を自動的に削除する。
#!/bin/sh
cat <<-END
a
(TAB)b
(TAB)(TAB)c
END
実行すると以下のように行頭のタブが削除される。
a
b
c

; & && ||
「;」「&」「&&」「||」は同じ仲間で、複数のコマンドの間にこれらの文字を挟むといろいろな動作をさせることができる。

複数のコマンドを ; (セミコロン) でつなぐと、前のコマンドが終わり次第、次のコマンドが実行される。
% command1 ; command2
⇒ command1 を実行し、その実行が完了次第 command2 を実行する。
% command1 ; command2 ; command3
⇒ 3つ以上のコマンドを記述しても OK

|| と && は、; と似ているが、前のコマンドの終了ステータスによって、後続のコマンドを実行するか否かが決まる。
% command1 && command2
⇒ command1 が 0 を返した場合、command2 を実行する。
% command1 || command2
⇒ command1 が 1 以上を返した場合、command2 を実行する。
一般的にコマンドは正常終了時に終了ステータス 0 を返し、異常時に 1 以上を返すため、
% command1 && 正常終了時に実行したいコマンド
% command1 || 異常終了時に実行したいコマンド
という使い方をする。典型的な例として以下のようなものがある。
% configure && make && make install
⇒ configure が成功したときのみ make を実行し、さらに make が成功したときのみ make install を実行する
% command || mail -s "error" foo@example.co.jp
⇒ command が失敗したらエラー通知メールを送信する。
>> 用語集 戻り値 *

~
チルダは自分自身のホームディレクトリ・もしくは特定ユーザのホームディレクトリに展開される。
% echo ~
/home/68user
⇒ 自分自身のホームディレクトリに展開される
% echo ~/bin/
/home/68user/bin/
⇒ ~ の後に好きな文字列をつけてもよい。
% echo ~foo
/home/foo
⇒ そのサーバ内に foo というユーザがいるなら、~foo は foo のホームディレクトリに展開される。

{}
{} は主に 3つの使われ方をする。1.変数の区切りを表すもの、2.ブレース展開、3.グループコマンドである。

変数の区切りは
echo "${VAR}"
のように使用する。上記の例では ${VAR} でも $VAR でも結果は変わらないが、
echo "${VAR}_1.dat"
という場合に $VAR_1.dat と書いてしまうと $VAR_1 という変数として扱われてしまう。$VAR_1 ではなく $VAR という変数であることを明示するために ${VAR} とブレースで囲むわけである。

ブレース展開は
% mkdir /foo/bar/{dir1,dir2,dir3}
⇒ mkdir /foo/bar/dir1 /foo/bar/dir2 /foo/bar/dir3
というふうに使用する。複数個の文字列を指定するが、そのうちの一部分のみ異なる場合に有用。

グループコマンドは
% { command1; command2; }
というふうに複数コマンドをグループ化する際に使用する。グループ化するだけだと意味はないが、複数コマンドの出力をまとめてリダイレクトしたり、ひとつにまとめることでジョブ制御しやすくするなどの利点がある。{} でのグループ化は sh・bash のみ。csh・tcsh は () でグループ化する。
>> 用語集 シェルスクリプト *

時刻管理 UNIX における時刻管理 (時刻あわせ/時刻合わせ/時刻同期/時刻設定)

マシンの時刻を手動で設定したい場合は、date コマンドを使う。
>> コマンド date *

もしインターネットに経由で自動的に時刻あわせを行いたい場合は、ntp パッケージに含まれる ntpd デーモンや、ntpdate コマンドを使う。ntpd を使うことで、定期的に時刻合わせを行うことができる。一方、ntpdate コマンドは一度限りの時刻合わせを行う場合に使用する。

他のサーバに対して、時刻同期サービスを提供したい場合も ntpd デーモンを使用する。

公開 NTP サーバ
日本における NTP サーバについては、福岡大学の clock.nc.fukuoka-u.ac.jp が有名であった。しかし 2005年、福岡大学の NTP サーバが過負荷に耐えられず、他 NTP サーバを利用するよう 2ch にて依頼。これを契機として、インターネットマルチフィード株式会社 (mfeed) が、日本標準時を元にした公開 NTP サーバを正式立ち上げ。さらに 2006 年には、日本標準時を管理する NICT (独立行政法人 情報通信研究機構) みずからが公開 NTP サーバを立ち上げ、日本において安定的な時刻供給ができる体制が整った。
>> コマンド ntpd *   xntpd *   ntpdate *

ファイルのタイムスタンプについては、下記を参照。
>> 用語集 タイムスタンプ *

タイムスタンプ UNIX におけるファイルの時刻管理 (atime/ctime/mtime) (更新時刻・更新日時・変更日時)

UNIX のファイルには、一般的に 3つのタイムスタンプがある。
  • atime … 最終アクセス時刻 (access time)
  • mtime … 最終変更時刻 (modify time)
  • ctime … 最終ステータス変更時刻 (change time)

atime・mtime・ctime
atime は、最後にファイルにアクセスした時刻のこと。より正確には、ファイル内容を read(2) した場合に変更される。ファイルをオープンしただけでは変更されないし、ファイル内容を write(2) しても変更されない。当然ながら、read(2) する権限がなかった場合も変更されない。

mtime は、最後にファイルを変更した時刻のこと。より正確には、ファイルに write(2) または truncate(2) した場合に変更される。

ctime は、最後にファイルを変更した時刻のこと。より正確には、ファイルに write(2) または truncate(2) した場合、そして inode データの修正を行った場合に変更される。inode データに含まれるのは、
  • ファイル名
  • ファイルサイズ
  • パーミッション
  • リンク数
  • オーナー・グループ
などである。つまり、write(2)・rename(2)・truncate(2)・chmod(2)・link(2)・chown(2) などのシステムコールを発行すると ctime が更新される。なお、たまに「ctime はファイル生成時刻 であり、Create TIME の略である」と解説している文章を見かけるが、誤り。

ファイル内容を write(2) や truncate(2) で更新した場合、mtime と ctime の両方が更新されることに注意 (ファイルサイズが変わらないように write(2) すれば、ctime は更新されないかと予想していたが、試してみると更新されてしまった)。

タイムスタンプの表示
ls -l で mtime、ls -lc で ctime、ls -lu で atime を表示することができる。
% ls -lu abc.txt
-rw-r--r-- 1 user group 2052 Feb 12 08:31 abc.txt
⇒ atime
% ls -l abc.txt
-rw-r--r-- 1 user group 2052 Jan 8 16:55 abc.txt
⇒ mtime
% ls -lc abc.txt
-rw-r--r-- 1 user group 2052 Jan 8 16:53 abc.txt
⇒ ctime
また、stat コマンドで表示すること

タイムスタンプの変更
atime・mtime は、touch コマンドを用いれば (つまり utimes(2) を用いれば) 任意の時刻を設定することが可能である。

しかし ctime に任意の時刻を設定することはできない。これは、chmod コマンドなどを使って ctime に現在時刻をセットすることはできるが、過去や未来の日時をセットできないということ。ただし fsdb コマンドなどで、ファイルシステムを直接いじれば何でもアリである。
>> コマンド touch *

シンボリックリンクのタイムスタンプ
いまどきの UNIX ではシンボリックリンクそのものの atime, mtime, ctime を持っているが、シンボリックリンクそのもののタイムスタンプを変更できるのは lutimes(2) を持つ OS のみである。*BSD には lutimes(2) が実装されているのでシンボリックリンクそのものの atime・mtime を変更できるが、lutimes(2) を持たない Linux・Solaris・HP-UX では変更ができない。

ファイル生成時刻
伝統的な UNIX では、ファイル生成時刻は保持していない。ただし FreeBSD 5.x などで使用されている UFS2 では、inode が生成された時刻「birthtime」という情報がある (struct stat の st_birthtime)。これぞまさしくファイル生成時刻と言えるだろう。

ただ、ファイルシステムをまたいで rename(2) して inode が変わった場合、birthtime が引き継がれるかどうかは未確認。

mount の noatime オプション
ファイルを read(2) すると atime が更新されてしまうが、更新には少なからず時間がかかる。たとえば web サーバなどの用途では、どのファイルが参照されたかは web サーバのログに記録すればよいので、atime を更新する価値がほとんどない。そのような場合、mount 時に noatime オプションを付加することで atime の更新が行われなくなり、少しだけパフォーマンスが向上する。
# mount -t ufs -o noatime /www
>> コマンド mount *

ファイルシステム
これまで「UNIX のファイルには、一般的に 3つのタイムスタンプがある」などと解説してきたが、実際にはファイルシステムにも依存する。

たとえば、UFS2 ではファイル生成時刻を保持できる。仮に Linux で UFS2 をマウントできたとして、Linux 側にファイル生成時刻を読み出すインタフェースがなかったとしたら、それを参照することはできない (Linux の UFS2 対応状況は知らない)。
>> 用語集 ファイルシステム *

ファイルグロブ ファイル名の置換 (メタキャラクタ・ワイルドカード)

ファイルグロブとは、シェルが * ? {} [] ~ などの文字列を解釈し、ファイル名として展開することである。なお、グロブは正規表現とは別物である。

使いどころは、「複数のファイルに対して一括して処理を行う」場合である。

基本
以下に基本的な例をあげる。
% ls a*
a から始まるファイル・ディレクトリ
% ls ab?de
ab の後に任意の一文字があり、その後に de が続くファイル・ディレクトリ
% ls *.c
拡張子が c のファイル・ディレクトリ
% ls foo[0-9]*
foo の後に数字が 1つ続き、その後に任意の文字列があるファイル・ディレクトリ
% ls foo[^0-9]*
foo の後に数字以外の文字が 1つ続き、その後に任意の文字列があるファイル・ディレクトリ
% ls ~
ホームディレクトリ
% ls ~/*.txt
ホームディレクトリ直下にある拡張子が txt なファイル・ディレクトリ
% ls ~hoge
ユーザ hoge のホームディレクトリ
% ls foo.{c,h,pl}
foo.c と foo.h と foo.pl

グロブ展開はシェルの仕事

ファイル名の展開は csh や bash などの「シェルが」行うことに注意しよう。ls や grep などの各コマンドが展開するのではない。「シェルが展開し、各コマンドの引数として渡す」のである。

つまりはこういうことだ。
  • ls の引数で使えたファイルグロブは、他のコマンドでも使える (コマンドに依存しない)。
  • ls などの各コマンドは、シェルが展開した「結果だけ」を受け取る。

これが理解できると、find の引数でファイル名を条件とする際に「*」などのメタキャラクタを
% find . -name \*.c -print
% find . -name '*.c' -print
などとエスケープしたりクォートで囲まなければならない理由がわかるだろう。この「*.c」はシェルに解釈させるためのものではなく、find コマンドにそのまま渡す必要があるからだ。なぜなら、もしカレントディレクトリに foo.c と bar.c があるときに、「*」をエスケープせず
% find . -name *.c -print
とすると、シェルは
% find . -name foo.c bar.c -print
と展開し、find コマンドに渡してしまう。これはユーザの意図した挙動ではないだろう。

もしコマンドラインで「*」や「?」の挙動に不可解な点があったら、echo コマンドを使うべし。
% find . -name *.c -print
が意図通りに動かず「おかしいな?」と思ったら、先頭に echo を付けて実行してみる。
% echo find . -name *.c -print
find . -name foo.c bar.c -print
その結果を見て、展開後の文字列が自分の意図通りになっているかどうか確かめること。


グロブは「存在するファイル・ディレクトリ」に展開される。ただし {} は例外。

* や ? などは、必ず存在するファイル・ディレクトリに展開される。しかし {} はファイルが存在しなくても展開されてしまう。

以下、カレントディレクトリにファイルが存在していないと考えてほしい。

% ls *.c
ls: No match.
⇒ シェルは *.c を展開しようとするが、存在しないので、シェルがエラーとする。グロブ展開の段階で失敗したので、ls は実行されない。

% ls foo.{c,h}
ls: foo.c: No such file or directory
ls: foo.h: No such file or directory
⇒ シェルは foo.{c,h} を展開する。ファイルが存在するかどうかはチェックしない。その後 ls foo.c foo.h が実行され、ls がエラーメッセージを表示している。

ファイルグロブの限界

ファイルグロブは便利だが、そのうちファイルグロブだけでは解決できないような問題に突き当たるだろう。例えば以下のような感じだ。
file001.txt〜file999.txt の 999 個のファイルから文字列「foo」を含む行を抽出したいが、file627.txt だけはサイズが 3GB と巨大なため除外したい。

こういう場合は、「広めにマッチさせ、grep -v で削る」というやり方が効果的だ。
% ls file[0-9][0-9][0-9].txt
⇒ まずは file001.txt〜file999.txt をマッチさせ
% ls file[0-9][0-9][0-9].txt | grep -v file627
⇒ file627 を除外し
% grep foo `ls file[0-9][0-9][0-9].txt | grep -v file627`
⇒ その結果から grep する
% grep foo `echo file[0-9][0-9][0-9].txt | grep -v file627`
⇒ 実行結果は変わらないが、内部コマンドの echo を使う方が 1 プロセス節約できる。

実はこの例は、以下のようにすればファイルグロブでも実現可能である。しかし、grep -v と比べ、可読性が低くなることがよくわかるであろう。
% grep foo file{[0-57-9][0-9][0-9],6[013-9][0-9],62[0-689]}.txt

ちなみに file001.txt〜file999.txt を作成するには
% touch `jot -w file%03d.txt 999`
とすればよい。
>> コマンド jot *

応用
以上が基本であるが、使いこなすのはなかなか難しい。わかっているつもりの初心者が実践できないのは「組み合わせる」こと。試行錯誤してがんばってほしい。「わからなくなったら echo する」のが基本である。
% grep foo *.c
⇒ これは普通だが…
% grep foo */*.c
⇒ ひとつ下のディレクトリの下の *.c から grep
% grep foo */*/*.c
⇒ さらに下のディレクトリから
% grep foo ../*/*.c
⇒ ひとつ上がって、*/*.c から grep
% grep foo {,*/,*/*/}*.c
⇒ 3 階層まとめて grep。*.c */*.c */*/*.c と同じ
% grep foo {,*/,*/*/}*.[ch]
⇒ さらにヘッダファイルも grep
% grep foo {,*/,*/*/}*.{c,h,cpp}
⇒ cpp も追加
% grep foo *.{c,h,cpp} */*.{c,h,cpp} */*/*.{c,h,cpp}
⇒ 混乱してきたら、展開してみよう
% grep foo `echo {,*/,*/*/}*.{c,h,cpp} | grep -v contrib/`
⇒ contrib/ 以下は grep 対象外にしたりしてみる

ファイルシステム UFS・ext2・ext3・FAT などの解説

ファイルシステムとは、HDD ・フロッピーディスク・CD-ROM などのどこにどのようなデータが格納されているかを管理する仕組みである。

生産されたばかりのディスクは白紙のノートのようなものだと思えばよい。罫線もなければページ番号もない。このままでも利用できなくはないが、不便である。ファイルシステムを作成するということは、白紙のノートに罫線やページ番号を記入するようなものである。

ファイルシステムには多くの種類があり、それぞれ
  • ファイル名の長さ
  • 格納できるファイル数
  • 最大ファイルサイズ
  • パーミッションなどの付属情報を格納できるかどうか
などが異なる。先ほどのノートの例で言えば、各ページの左上に日付記入欄があるノートもあれば、そのような欄がないノートもある、と言ったところか。

以下に主要なファイルシステム一覧をあげる。
UNIX 界隈
UFS
UNIX File System。UNIX 初期のファイルシステム。
FFS
BSD が開発した Fast File System。UFS のブロックサイズを倍にしたもの。
FFFS
BSD が開発した Fat Fast File System。FFS の後継。この FFFS が現在でも各種 UNIX のベースとなっている。まぎらわしいことに、この FFFS を指して UFS・FFS などの呼称が使われることが非常に多い。
UFS2
64bit ファイルシステムのため、1TB を越えるファイルシステムを構築可能。FreeBSD・NetBSD 界隈で使用されている。ファイルサイズ以外は UFS とほぼ同じ。これを UFS と呼ぶことも多い。
ZFS
Solaris10 で採用となったファイルシステム。ウリは 128bit なこと。結構野心的。

Linux 界隈
ext2
Linux 界隈で使用されている。
ext3
Linux 界隈で使用されている。ext2fs の後継。
  • ジャーナルファイルの採用により fsck に要する時間が短縮されている
  • ext2 より速い
などの利点はあるが、基本的には ext2 と同じである。
XFS
SGI が開発したジャーナルファイルシステム。

Windows 界隈
FAT
いわゆる MS-DOS フォーマット。8.3 形式のファイル名。1 クラスタのビット数により FAT12・FAT16・FAT32 という種類が存在する。

FAT12
クラスタのビット数が 12 ビット。よって、扱えるファイル数は 2^12=4096 弱である。そもそもはフロッピーディスク向けに作成されたファイルシステムなので、仕方があるまい。
FAT16
ファイルサイズの上限は 2GB。
FAT32
Windows 95 OSR2 でサポート。ファイルサイズの上限は 4GB。
VFAT
Windows 95 で採用されたファイルシステム。FAT のファイル名の 8.3 制限をなくして 255 文字までのファイル名を使用することができる。これを LFN (Long File Name) 拡張と呼ぶ。なお、VFAT (Virtual FAT) という名前の通り、実際には VFAT は仮想的なレイヤであり、実際にディスクに記録されるときには FAT に変換される。

つまり、
  • Windows95 では OS は VFAT のレイヤを挟んで FAT16 を操作している。
  • Windows95 OSR2 では OS は VFAT のレイヤを挟んで FAT32 を操作している。
ということだ。しかし FAT16 や FAT32 では 8.3 形式のファイル名しか扱えないため、実際には FAT 上に隠しファイルを作成し、その上に LFN 情報を格納するという、かなり無理矢理な方法をとっている。
NTFS
Windows NT で採用されたファイルシステム。ようやく FAT のしがらみを取り去った。UNIX のようなファイル属性を管理できる。
CD-ROM
ISO9660
CD-ROM 用のファイルシステムのための規格。ISO9660 には Level 1・2・3 という 3 つのレベルがある。Level 3 が制限が緩く (高機能)、Level 1 が最も制限が厳しい (低機能)。

Level 3
  • ファイル名の長さは 31 文字まで。
  • ファイル名には必ず「.」を含めなければならない。
  • ファイル名に使用できる文字は英大文字・数字・アンダーバー。
  • ディレクトリ階層の深さは 8 段まで。
Level 2
  • 1つのファイルは、連続したブロックに記録されていなければならない
  • ファイルを複数のセクションに分割しない。
  • それ以外は Level 3 と同じ。
Level 1
  • ファイル名は 8.3 形式。

しかし最も高機能な Level 3 でさえも実用のファイルシステムとしては機能が足りなさすぎる。そこで以下のような拡張方式が存在する。

ISO9660 RockRidge 拡張 (RockRidge Extension)
おもに UNIX 方面で使用される ISO9660 の拡張方式。UNIX のパーミッション・オーナー・グループなどを記録できる。
ISO9660 Joliet 拡張 (Joliet Extension)
おもに Windows 方面で使用される ISO9660 の拡張方式。

FreeBSD や Linux では RockRidge 拡張・Joliet 拡張に対応しており、
  • RockRidge 拡張で記録された CD-ROM は RockRidge 拡張でマウント。
  • そうでないなら、Joliet 拡張で記録された CD-ROM は Joliet 拡張でマウント。
  • そうでないなら、ISO9660 でマウント。
という挙動をするようになっている。よって、この辺の拡張方式についてはあまり気にする必要はないだろう。

ファイル名・ディレクトリ名などの最大サイズ・ファイル数上限などの細かな情報は「ファイル・ディレクトリ」の項を参照。
>> 用語集 ファイル制限まとめ *
>> コマンド mount *   newfs *   disklabel *   df *

ファイル制限まとめ UNIXにおけるファイル・ディレクトリについての規則・上限値のまとめ

ファイル名・ディレクトリ名の制限や、ファイル名の長さなどについて。

ファイル名で使用可能な文字
UNIX において、ファイル名として使用できない文字は以下の 2つのみである。
  • "/" (スラッシュ。ASCII コード 0x2F)
  • \0 (ASCII コードのゼロ)
上記以外の文字、例えば空白・タブ・各種記号 (#$%& など)・コントロールコードなどはすべて使用可能である。

実際に試してみよう。以下の Perl コマンドは、abc(0x01)(0x02)(0x03)..... というファイルを作成する。"....." の部分に \x04〜\xff まで入れてみて試してほしい (Perl における \xXX は 16進数を表す表記法である)。
% perl -MFcntl -e 'sysopen(FH, "abc\x01\x02\x03.....",O_WRONLY|O_CREAT, 0600)||die "$!"'

\x00 とスラッシュを表す \x2f を除く、\x01〜\x2e、\x30〜\xff すべての文字が使用可能であることが確認できるはずだ。
【注意】
ls コマンドはデフォルトではコントロールコードなどの表示できない文字を「?」で置き換えて表示する。実際のファイル名を確認したい場合は
% ls -w
とすることで、「?」への置換が行われなくなる。上記の実験の後にファイルを消したい場合、
% rm abc*
とワイルドカードを活用しよう。コントロールコードが多く含まれるため、コマンドラインからファイル名を正確に指定するのは結構難しい。

もしスラッシュを表す \x2f を入れると
% perl -MFcntl -e 'sysopen(FH, "abc\x01\x02\x03.....",O_WRONLY|O_CREAT, 0600)||die "$!"'
No such file or directory at -e line 1.
とエラーになる。これは、もし「ファイル名にスラッシュを含んでもよい」という仕様にした場合は、
% ls foo/bar.dat

  • 「foo」ディレクトリの下の bar.dat というファイル
と解釈すべきなのか、あるいは
  • 「foo/bar.dat」というファイル
と解釈すべきのか、という部分が曖昧になってしまうための制限である。

また、\x00 を入れるとそこが文字終端として扱われるので、
% perl -MFcntl -e 'sysopen(FH, "abc\x00def",O_WRONLY|O_CREAT, 0600)||die "$!"'
としても abc というファイルが作成される。Perl における sysopen はシステムコール open(2) を呼び出すだけだが、open(2) は「引数で指定されたアドレスから、0x00 が出現するまでの文字列」をファイル名と見なすため、open(2) からすると
"abc"

"abc\0def"
の区別がつかない。よって、より正確に言うと、「ファイル名の終端を表すコードが \0 であるため、ファイル名に \0 を渡しても、そこがファイル名の終端と見なされてしまい、結果的にファイル名に \0 を含めることができない」となる。

. と .. と /
"." はカレントディレクトリを表す。".." は親ディレクトリ (1階層あがったディレクトリ) を表す。これは、表記法ではなく、実際にあらゆるディレクトリに "." と ".." というディレクトリエントリが存在する。"." と ".." はファイル名としては正しいが、どのディレクトリにも既に作成されているため、新たに作成することはできない。

なお、ルートディレクトリ ("/") にも親ディレクトリ ".." は存在するが、「ルートディレクトリの親ディレクトリはルートディレクトリ」("/.." は "/" は同一) と定義されている。

"/" は、パス名の先頭であればルートディレクトリを現し、パス名の途中に現れればディレクトリの区切りを表す。
/foo
⇒ ルートディレクトリ直下の foo というファイル (またはディレクトリ)
/foo/bar
⇒ ルートディレクトリ直下の foo というディレクトリの下の bar というファイル (またはディレクトリ)
foo/bar
⇒ カレントディレクトリ直下の foo というディレクトリの下の bar というファイル (またはディレクトリ)
"//" や "////" のようにスラッシュを連続して記述しても、"/" と同じものとして扱われる (POSIX で定義されている)。

拡張子
MS-DOS や Windows では拡張子は OS と密接に連携しており、例えばファイルのダブルクリック時に拡張子を元に関連付けの定義を参照し、起動すべきアプリケーションを決定する。しかし UNIX における拡張子はより緩いルールのようなものにすぎず、OS レベルでは拡張子を意識することはない。

とはいえ、gcc などのコンパイラにおいては
foo.c → foo.s → foo.o → foo
と拡張子が順に変わっていくし、make コマンドもコンパイル支援を行うのが主目的であるためサフィックスルールを実装している。このように、いくつかのコマンドは拡張子と密接に関係している。
>> コマンド gcc *   make *

ファイル名の長さ制限(ファイル名最大長)
多くの UNIX 系 OS においては、ファイル名の長さの上限は 255バイトである。
% touch 1234567890(以下、あわせて25回繰り返し)12345
⇒ 255文字のファイル作成は成功
% touch 1234567890(以下、あわせて25回繰り返し)123456
touch: File name too long
⇒ 256文字は失敗

ただしこれは、あるディレクトリエントリ内の文字列長の上限が 255バイトである、というだけで、パス全体の合計が 255バイトを超える分には問題ない。つまり、
/too/long/long/long/long/long/...(略).../long/filename
は、"/too" から "/filename" までを 255文字以内にする必要はない (ただし、パス全体の長さ制限はある。後述)。なお、「255文字」ではなく「255バイト」であることに注意。Shift_JIS や EUC-JP などで日本語ファイル名を作成する場合、2〜3バイトで1文字を構成するため、日本語使用時は120文字程度しか使用できないことになる。

ちなみに古い UNIX では 14バイトであったが、おそらく 4.2BSD の FFS 実装において 255バイトに拡張されたと思われる。

パスの長さ制限(パス名最大長)
多くの UNIX 系 OS においては、パス名全体の長さの上限は 1023バイトである。これは
/too/long/long/long/long/long/...(略).../long/filename
の "/too" から "/filename" までの長さである。ただし、これはファイルシステムの制限ではなく、システムコールに与えるパス名の長さの上限であることに注意すること。つまり、
% ls /too/long/long/long/long/...(略).../long/filename
ls: File name too long
は長すぎるのでエラーとなったとしても、以下のように cd コマンドでディレクトリを少しずつ降りていき、一度に 1023バイトを超えないようにすれば問題ない (FreeBSD 5.2.1-RELEASE にて実験済)。
% cd /too/long/long/long/long/long
% cd long/long/long/long/long
% (略)
% cd long/long/long/long/long
% ls filename

とはいえ、当然ながら扱いづらいことに変わりはないため、あまりに長い文字列は使用しないことをお勧めする。

ファイル・ディレクトリ数の上限
ひとつのディレクトリ直下に格納できる、ファイル・ディレクトリ数については、おそらく上限はない。おそらく、数万・数十万のファイルを同一ディレクトリ内に作成することは可能であろう。ただし、少なくとも UFS (FFS/FFFS)・ext2・ext3 においてはファイル名の探索は線形探索であり、非常に遅い。具体的には、特定のファイルをオープンしたり、タイムスタンプやパーミッションなどの情報を得る (stat(2)) 場合などは、そのディレクトリ内のエントリを 1つずつ順に調べ、一致するファイル名を探し出す必要がある。

現実的には、ひとつのディレクトリに数万〜数十万のファイルを置くと、ls -l の結果を見るだけでそれなりに時間がかかるようになる。ひとつのディレクトリに置くファイルは、多くても 1000個程度にとどめておくことをお勧めする (ちなみに RaiserFS・xfs・jfs などのイマドキなファイルシステムでは、B+-Tree や B*-Tree などの二分木を発展させた管理方法を採用しており、ファイル数の増加はそれほど問題にはならない)。

なお、ファイルシステム全体としては i-node (inode) の数がファイル・ディレクトリ数の上限である。i-node の使用数・未使用数は df コマンドに -i オプションを渡すことで確認できる。
% df -i
Filesystem  1K-blocks     Used     Avail Capacity iused    ifree %iused  Mounted on
/dev/ad0s1a    253678    39866    193518    17%    1040    31982    3%   /
/dev/ad0s1e    253678     2152    231232     1%     254    32768    1%   /tmp
/dev/ad0s1f 186444376 39519590 132009236    23% 5528815 18564879   23%   /usr
詳細は df コマンドを参照。
>> コマンド df *

i-node 数の上限は UFS (FFS/FFFS)・ext2・ext3 などの古めのファイルシステムであっても 2^32 個 (42億) であるため、現実的には問題になることは少ないだろう。

余談
当ページ管理人が 2006年ごろ、ひとつのディレクトリに数万個のファイルを作成してみたことがあったのだが、ls しようとすると bash がコアダンプしてしまった (Fedora Core 3 + bash)。また、これは2000年ごろだと思うが、数万階層のディレクトリを作成したら消せなくなったことがある (FreeBSD)。また、gzip のようにファイルサイズ 2GB や 4GB で問題が発生するコマンドもある。
>> コマンド gzip *

ファイルシステムの仕様上は問題ないとしても、OS のバグ・周辺アプリケーションのバグを踏む可能性があるので、クリティカルなシステムにおいてあまり冒険するのは避けた方がよいだろう。

プロセス UNIX におけるプログラムの実行単位

プロセスは現在実行中のプログラムのことを指す。/bin/ls はプログラム・コマンド・実行ファイルなどと呼ぶ。これをシェル上から
% /bin/ls
と実行したときにプロセスが生成される。プロセスは仕事 (ls の場合はファイル・ディレクトリの表示) を行い、それが終わったらプロセスが終了する。

現在実行中のプロセスは ps コマンドや top コマンドで確認することができる。
>> コマンド ps *   top *

現在実行中のプロセスを終了させるには、kill コマンドを使う。
>> コマンド kill *

プロセスには必ず親となるプロセスが存在する (初めに起動される init は例外)。つまり「あらゆるプロセスは子プロセスである」と言える (init は例外)。子プロセスは親プロセスから以下の情報を引き継ぐ。
  • 環境変数 (printenv などで参照・setenv・export などで変更可能)
  • カレントディレクトリ (pwd で参照・cd で変更可能)
  • ルートディレクトリ (chroot で変更可能)
  • ファイル生成マスク (umask で参照・変更可能)
  • リミット情報 (リソース制限。limit・ulimit で参照・変更可能)
  • オープン中のファイルディスクリプタ
  • 端末情報
実際はこの他にプロセスグループ・シグナルハンドラ情報・端末情報・リソース情報 (rusage)などが渡されるが、詳細は省略。

上記の情報を変更するコマンド (setenv・export・cd・umask・limit・ulimit) は全てシェルの内部コマンドであることに注意しよう。なぜなら、子プロセスから親プロセスの上記の情報を変更することはできないからである。仮に setenv コマンドがシェルの内部コマンドではなく、/bin/setenv という外部コマンドであったとしよう。
% /bin/setenv HOGE fuga
とするとシェルは fork して子プロセスを生成し、子プロセスは /bin/setenv を exec する。/bin/setenv は自身の環境変数 HOGE の値を fuga にセットして、プロセスは終了する。しかし、子プロセスの環境変数は親プロセスに影響しないため、親プロセスであるシェルの環境変数は全く変化しない。

これでは全く意味がないため、シェルのプロセス自身の情報を変更するコマンドは、シェルの内部コマンドとして用意されているわけである。
>> コマンド limit *   ulimit *

リダイレクト コマンドの出力をファイルや別のコマンドに振り分ける (リダイレクション)

コマンドを実行したとき、その出力をファイルに書き込みたい場合は
% command > file.txt
とする。

コマンドを実行したとき、その出力を別のコマンドの入力として渡したい場合は
% command | command2
とする。これを「パイプで繋ぐ」と言う。
% command | command2 | command3 | command4
と、パイプで多段に接続することもできる。

UNIX では、2つの出力先がある。標準出力と標準エラー出力である。

ls コマンドの出力をファイルに書き込む場合を考えてみよう。拡張子が .txt のファイル名の一覧をファイル output に書き込むには
% ls *.txt > output
とすればよい。しかし拡張子が .txt であるファイルがひとつも存在しなかったらどうなるだろうか。出力先を output にリダイレクトしているにも関わらず、ls コマンドは
% ls *.txt > output
ls: No match.
というエラーを出力する。なぜなら、エラーが出たということはユーザが気づくべきことなので、リダイレクトの指定があったからといって、ファイルに書き込むのは望ましくないからである。

これを実現しているのが、標準出力と標準エラー出力である。ls コマンドはファイル一覧などの通常の出力は標準出力に書き出す。しかし、No match やオプションの誤りなどでエラーが起こった場合は、標準エラー出力に書き出すのである。他のコマンドも ls コマンドと同様に、
  • 普通の出力は標準出力に
  • ユーザに気づいてほしいエラー表示などは、標準エラー出力に
という方針でプログラムが作成されている。

しかし、標準エラー出力もファイルにリダイレクトしたり、パイプで他のコマンドに渡したい場合がある。ここからは csh・tcsh と sh・bash では書式が大きく違うので、分けて記述する。

csh・tcsh の場合
標準出力と標準エラー出力を両方リダイレクトしたい場合は、
% command >& file.txt
% command |& command2
とする。
% command &> file.txt (誤り!)
% command &| command2 (誤り!)
ではないことに注意。これだと、command をバックグラウンドで実行して、その標準出力をファイルや他のコマンドに渡すことになってしまう。

標準出力を file1 に、標準エラー出力を file2 に、などと振り分けるには、
% ( command > file1 ) >& file2
とする。これを応用して、標準エラー出力を全く表示させず、標準出力だけを見るには
% ( command > /dev/tty ) >& /dev/null
とすればよい。例えば、find コマンドで「他人のディレクトリを見る権限がない」という意味の Permission denied というエラー表示が邪魔なときは
% ( find / > /dev/tty ) >& /dev/null
とするとよい。
>> コマンド find *

また、標準エラー出力だけをファイルに書き出すには
% ( command > /dev/null ) >& file
とする。

標準出力だけをページャなどで見たい場合は、普通に
% command | less
として、less 実行中に Ctrl-l で画面を再描画すると、標準エラー出力が消えて見やすくなる。

csh・tcsh の文法は少々腐っているので、標準エラー出力が絡んでくると、トリッキーな技を使う必要がある。

sh・bash の場合
まず、
  • 標準出力は 1 番
  • 標準エラー出力は 2 番
ということを覚えてほしい (ちなみに標準入力は 0 番)。

標準出力を file1 に、標準エラー出力を file2 に、などと振り分けるには、
% command 1>file1 2>file2
とする。
% command 1>/dev/null
% command 2>/dev/null
と、どちらかだけを表示することも簡単に指定できる。上記の find の例だと、
% find / 2>/dev/null
とすればよい。

標準出力と標準エラー出力を両方まとめて他のコマンドに渡すには
% command 2>&1 | less
とし、標準出力と標準エラー出力をまとめて file に書き出す場合は
% command >file 2>&1
とする。ここで順番を逆にして
% command 2>&1 >file (誤り!)
としてはうまくいかないことに注意。

標準エラー出力のみをページャなどで見たい場合は、
% command 2>&1 1>/dev/null | less
である。これも
% command 1>/dev/null 2>&1 | less (誤り!)
と順番を逆にするとうまくいかない。

より複雑な例を紹介しよう。

標準エラー出力のみをパイプに出力する。
% command 2>&1 >/dev/null | command2
標準エラー出力のみをパイプに (出力を閉じるので、command が出力結果をチェックしているならエラーになる)。
% command 2>&1 >&- | command2
標準出力と標準エラー出力を交換する。
% command 3>&1 1>&2 2>&3
標準出力を捨て、標準エラー出力をページャで参照する。
% command 3>&1 >/dev/null 2>&3 | less

さて、先にも述べたように、標準出力と標準エラー出力をファイルに出力する場合は、
% command 1>file 2>&1
% command >file 2>&1 (1 を省略してもよい)
とする。しかし
% command 2>&1 1>file
% command 2>&1 >file (1 を省略してもよい)
は誤りである。なぜ上の書き方が正しいのか、なぜ下の書き方ではダメなのか説明できるだろうか。なお、「リダイレクトは右に書いたものから順に評価されるから」は間違いである。

そもそも「2>&1」という書き方は「2 の出力先を 1 にマージする」というイメージで捉えている人が多いのではないだろうか。「2>&1」の本当の意味は「2 の出力先を、1 の出力先と同じものに設定する」である。

まず、リダイレクトを指定しない場合は、
% command
⇒ 1 の出力先 … 画面
⇒ 2 の出力先 … 画面
となっている。標準出力のみをファイルにリダイレクトする場合は
% command 1>file
⇒ 1 の出力先 … file
⇒ 2 の出力先 … 画面
となる。では標準出力と標準エラー出力をファイル file に出力する
% command 1>file 2>&1
だが、これはまず「1>file」が処理されて、
⇒ 1 の出力先 … file
⇒ 2 の出力先 … 画面
となり、その後に「2>&1」が処理されて、
⇒ 1 の出力先 … file
⇒ 2 の出力先 … file (なぜなら「2 の出力先を、1 の出力先と同じものに設定した」から)
となって、めでたく標準出力と標準エラー出力が file に出力される。

一方、誤った書き方の
% command 2>&1 1>file
であるが、リダイレクト解析前の初期状態は
⇒ 1 の出力先 … 画面
⇒ 2 の出力先 … 画面
となっている。ここでシェルが「2>&1」を処理しても、
⇒ 1 の出力先 … 画面
⇒ 2 の出力先 … 画面
と何も変化しない。なぜなら、「2 の出力先を、1 の出力先と同じものに設定した」からである。その後に「1>file」が処理され、
⇒ 1 の出力先 … file
⇒ 2 の出力先 … 画面
となり、あなたの望んだものとは異なる結果となってしまう。

なお、「n>&m」は「n の出力先を、m の出力先と同じものに設定する」と説明してきたが、UNIX 的には「dup2(m,n)」しているにすぎない。

さて、最後に宿題。以下のシェルスクリプトは何を意図したものでしょうか?
#!/bin/sh
exec 3>&1
status=$({ { command1 3>&- 4>&-; echo $? 1>&4 3>&- 4>&-;} | command2 1>&3 3>&- 4>&-;} 4>&1)
if [ $status != 0 ]; then
...
fi
ちなみに bash であれば $PIPESTATUS を使えば一発で解決である。
>> コマンド csh *   sh *

ロケール 言語などを切り替える仕組み (ロカール・locale)

国際化のためのフレームワークのことをロケールという。例えば普通に date コマンドを実行すると
% date
Tue Nov 18 05:08:37 JST 2003
となるが、ロケールを日本語に設定しておくと
% env LANG=ja_JP.eucJP date
2003年 11月18日 火曜日 05時09分09秒 JST
と日本語表記になる。

また、シェルが日本語 locale に対応していると、例えば
「Command not found」
でなく、
「コマンドが見付かりません」
などと日本語でエラーを表示するものもある。

これらのメッセージを日本語で表示してほしくなければ、普段は
% unsetenv LANG
としておけばよい。

ただし、LANG を設定しないと日本語を扱えないコマンドもある。例えば FreeBSD では、LANG=ja_JP.eucJP としておかないと、
% jman ls
としても man ls と同じく英語マニュアルを表示してしまう。そういうときは
% alias jman env LANG=ja_JP.EUC jman
などと env コマンドを使って、jman の実行時だけ LANG=ja_JP.eucJP となるようにすればよい。

OS によって 日本語を表す locale 名が違う。EUC-JP を使いたいのであれば、以下のいずれか。
  • ja_JP.eucJP
  • ja_JP.EUC
  • ja_JP.ujis
  • ja_JP.EUC-JP
Shift_JIS を使いたいのであれば、以下のいずれか。
  • ja_JP.SJIS
  • ja_JP.PCK
Unicode であれば
  • ja_JP.UTF8
を使うとよい。
>> 環境変数 LANG *

解凍 伸長のこと。

このページでは、「解凍」という用語は使っていない。「解凍」で検索して引っかからなかった場合は、「伸長」に変えて再度検索してみてほしい。

ちなみに、Windows では「圧縮・解凍」という言葉が通用するが、UNIX 界隈では「圧縮・伸長」という用語を使う場合が多い。世の中広いもので、「圧縮の対義語は解凍ではなく伸長である。解凍という用語を使いたければ圧縮と言わず冷凍と言うべきだ」という理由から、解凍という言葉を嫌う人さえいる。

当ページ管理人は積極的に「解凍」という用語を使うことはないが、「圧縮・解凍」という組み合わせに対して いちいち指摘するほどのこだわりはない。