用語集

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

コマンドラインからコマンド foo を実行しようとしたときに、
% foo
foo: Command not found
あるいは
% foo
-bash: foo: コマンドが見つかりません
というエラーになった場合の対処方法をまとめる。


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

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

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

sh・bash でもハッシュテーブルの仕組みはあるものの、csh・tcsh とは考え方が異なるので、Command not found の原因とはならない。興味がある方は hash コマンドを参照のこと。

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

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 コマンドでスクリプトをダンプしてみるとよい。
改行コードの変更は、tr・perl・qkc などを使うとよい、

6. 外部パッケージからインストールする必要がある
外部パッケージとして提供されている可能性がある。例えば telnet コマンドや perl コマンドなど、 以前は標準コマンドであったものが外部パッケージとして切り出されたものは数多くある。下記を参照に、外部パッケージとして提供されていないか探してみてほしい。

7. コマンドが存在しない
以上の 1〜7 のいずれでもないなら、そもそもそういうコマンドはないということかもしれない。その前に 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
というふうに対応している。

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

setuid とは、UNIX/Linux において、root など特定の権限でプログラムやコマンドを実行する仕組みである。


setuid の基礎
UNIX/Linux において passwd コマンドでパスワードを変更したり、chsh コマンドでログイン時のシェルを変更したりすることができる。これらの情報は /etc/passwd や /etc/shadow (または /etc/master.passwd) などに保存されている。

ここでよ〜く考えてみよう。つまり passwd コマンドや chsh コマンドは、/etc/passwd・/etc/shadow などのファイルを更新するということだ。しかしながら、んこれらのファイルは、一般ユーザが書き換えできないようにパーミッションが設定されている。
% 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 や chsh のバイナリのパーミッションを見てみよう。
% ls -l /usr/bin/{passwd,chsh}
-r-sr-xr-x 6 root bin 36864 Mar 14 1999 /usr/bin/chsh
-r-sr-xr-x 2 root bin 32768 Jul 22 1998 /usr/bin/passwd
パーミッションの最初の部分が「r-s」となっていることに注意してほしい。「s」という文字は
「そのコマンドが所有者の権限で実行される」
という意味である。/usr/bin/chsh・/usr/bin/passwd というファイルの所有者は root なので、一般ユーザが /usr/bin/chsh を実行すると、root の権限を得ることになる。そのコマンドが終了すると root 権限は失われる。

setuid の使われ方
UNIX/Linux において、setuid という仕組みは、かなり広い範囲で使われている。
% ls -l /{,usr/,usr/local/}{,s}bin/ | grep 'r[-w]s'
としてみるとわかる通り、passwd・chsh に始まって、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 の権限で動くようになっている。

また、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 プログラムを作成
実際に 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
(略)
これで一般ユーザが、一時的に (コマンドを実行中の間だけ) 特別な権限を取得できたわけである。

スクリプトの setuid
バイナリは上記のとおり setuid することができた。ではスクリプトを 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 されているので、誰のユーザ権限にでも移行できる。

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

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

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

UNIX/Linux における「穴あきファイル」とは、内部に NULL データを含むファイルで、なおかつディスク上には一部データの実体が存在しないファイル、である。英語では "Sparse file" (スパース・ファイル) と呼ぶ。


穴あきファイルを作ってみる
穴あきファイルを実際に作ってみよう。以下のようにしてファイルサイズが 1,000,000 バイトな 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 などである。

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

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

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

…と書いたのは 2002年頃だった気がするが、2000年代前半、VMware などの仮想マシンの VM イメージで使われ、2000年代後半はクラウドでの VM イメージなどで使われ、2010年代は Docker イメージでも使われ、結果としては穴あきファイルは必要なものである、ということを歴史が証明したと言えよう。

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

UNIX/Linux における終了ステータスや戻り値など、正常・異常などの結果を表現するコード値について説明する。


コマンドの終了ステータス
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) などのライブラリルーチンの戻り値は、千差万別である。とにかくマニュアルを見てくださいとしか言いようがない。

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

サブシェルとは、シェル (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。シバン。

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


シェバングの基礎
コンピュータが直接実行できるのはマシン語で記述されたバイナリファイルのみである。しかし UNIX/Linux では、ファイルの先頭 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 シェバングにおける env コマンド
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 だったはず。

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" "$@"

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 のシェバング解析
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」となるが、エラーメッセージがわかりづらいのではまらないように。

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 用コメント
emacs (mule) では、ファイルの 1行目に特定の書き方をすることで、メジャーモードなど各種設定を行うことができる。

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

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

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

Tips.10 シェバング行についてのさらに詳細な情報
シェバングに関する、より詳細な情報は以下の URL を参照のこと。

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

シェルスクリプトは
#!/bin/sh
echo "Hello World"
などと、シェルが解釈できるコマンドを羅列したファイルのことを指す。


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

シェルスクリプトの作り方
vi や emacs 等のエディタで
#!/bin/sh
echo "Hello World"
というファイルを sample.sh などのファイル名で保存する。その後、
% chmod +x sample.sh
として実行権限を付与することで、
% ./sample.sh
Hello World
と実行できる。

sh・bash でのシェル変数セット
単純に文字列を代入し、表示する。
hoge=FUGA
echo $hoge
"=" の前後に空白を入れて下記のようにするとうまく動かない。引数 "=" と "FUGA" を渡して "hoge" コマンドを実行する、と解釈されるためである。
hoge = FUGA
改行コード入りの文字列を代入し、表示する。変数を利用する際、"" で囲むことで改行が維持される ("" で囲まないと IFS により 1行になってしまう)。
hoge="abc
def"
echo "$hoge"

sh・bash での変数置換
${var} 変数の値をそのまま返す。
${var:-word} 変数が未定義か空文字の場合、word を返す ($var に保存しない)
${var:=word} 変数が未定義か空文字の場合、word を返す ($bar にも保存する)
${var:?word} 変数が未定義か空文字の場合、標準エラー出力に word を出力し、スクリプトを終了する。
${var:+word} 変数がセットされている場合、word を返す。

sh・bash での環境変数セット
HOGE=FUGA
export HOGE
export HOGE=FUGA # 代入と export を同時に行ってもよい

sh・bash での一定回数のループ
i=0
while [ $i -lt 5 ]; do
echo $i
i=`expr $i + 1`
done
seq コマンドを使ってもよい。
for i in `seq 1 10`; do
echo "No: $i"
done
これは bash のみだと思うが、下記のようにも書ける。
for i in {1..10}; do
echo "No: $i"
done
これも bash のみだと思うが、for (( expr1 ; expr2 ; expr3 )) という書き方もできる。
for (( i=0 ; i<10 ; i++ )); do
echo "No: $i"
done

sh・bash でのリスト処理
vars="x y z"
for var in $vars; do
echo $var
done

sh・bash での行単位の処理
コマンド実行結果から:
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

bash での配列初期化 【2018-12-28追加】
myarray=()
→ 空配列
myarray=(a0 a1 a2)
→ インデックス0番目に a0、1番目に a1、2番目に a2 を設定
myarray=([0]=a0 [1]=a1 [3]=a3)
→ インデックスを明示的に指定。

bash での配列要素参照 【2018-12-28追加】
myarray=(a0 a1 a2)
echo ${myarray[0]}
→ 結果は a0
echo ${myarray[1]}
→ 結果は a1
echo $myarray
→ 結果は a0

bash での配列要素数を参照 【2018-12-28追加】
myarray=(a0 a1 a2)
echo ${#myarray[@]}
→ 結果は 3

bash での要素の値を更新・追加・削除 【2018-12-28追加】
更新は普通に代入すればよい。
myarray[1]=a1xx
unset で要素を削除できる。
unset myarray[1]
末尾に追加:
myarray=(xxx yyy "${myarray[@]}")
myarray+=(xxx yyy)
先頭に追加:
myarray=("${myarray[@]}" xxx yyy)

bash での配列をすべて表示 【2018-12-28追加】
要素のみでよいなら下記のようにする。
myarray=(a0 a1 a2)
echo ${myarray[@]}
→ 結果は a0 a1 a2
ループで全要素を取得する例。
for i in "${myarray[@]}"; do
echo "$i"
done

インデックスから全要素を取得する例。
myarray=([0]=a0 [1]=a1 [3]=a3)
echo ${!myarray[@]}
ループでまわす場合は下記。
for i in "${!myarray[@]}"; do
echo "$i": "${myarray[$i]}"
done

sh・bash でのファイル名の取得
ファイルグロブを使って:
for file in *.txt; do
echo $file
done
コマンドの実行結果を使って:
for file in `ls | grep -v abc`; do
echo $file
done
for file in `find . -name \*.txt`; do
echo $file
done

sh・bash での if 文
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
「=」以外にも、「!=」「>」「<」や、「-f」「-d」などのファイルテスト演算子を使うことができるが、test コマンドを参照してほしい。

sh・bash での continue・break
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

sh・bash での switch 文
case $var in
hoge)
echo "var is hoge"
;;
foo|bar)
echo "var is foo or bar"
;;
*)
echo "var is unknown"
;;
esac

sh・bash での関数
sh・bash では、下記のようにして関数を作成することができる。
myfunc () {
echo "myfunc の処理"
}
bash ならば関数定義の先頭に function をつけてもよい (function をつけてもつけなくても挙動は同じ)。
function myfunc () {
...
}
定義した関数を実行するには、下記のように単に関数名を書けばよい (呼び出し時にカッコなどは不要)。
myfunc

引数を処理する場合、コマンドパラメータ取得時のように $#・$1・$2 などの変数を使うことができる。
myfunc_arg () {
echo "引数の個数: $#"
echo "引数1 $1"
echo "引数2 $2"
}
myfunc_arg abc def
上記の実行結果は下記のようになる。
% ./sample.sh
引数の個数: 2
引数1 abc
引数2 def

sh・bash の関数内のローカル変数
bash と一部の sh では、local コマンドを使うことで、ローカル変数の定義・宣言ができる。
myfunc () {
local var="1"
}
詳細は local コマンドのページを参照のこと。

sh・bash で、コマンドを実行し、終了ステータスを取得する 【2018-03-06 追加】
コマンドを実行したい場合、コマンド名を記述すればよい。その後 $? が 0 であれば正常終了、
0 以外であれば異常終了である。
command
ret=$?
if [ $ret != 0 ]; then
command がエラー
else
command が正常終了
fi

$? は、直前のコマンドの結果で上書きされるので、
command
echo $?
if [ $? != 0 ]; then 〜
はうまく動かない。echo の結果 (ほとんどの場合 0) で上書きされるからである。ちなみに変数代入も $? を上書きするので、下記もうまくうごかない (if の判定は変数代入の結果をチェックしていることになる)。
command
ret=$?
if [ $? != 0 ]; then 〜

sh・bash で、Yes か No を答えさせる
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

bash であれば bash の内部コマンド select コマンドで入力を受け取ることも検討してみるとよい。

sh・bash での計算
$(( 〜 )) で囲むことで、計算を行う。内部では変数を $x ではなく x と書ける ($x と書いても動くようだが)。
x=10
y=20
z=$(( x + y * 3 ))
echo $z
→ 70 となる
bash の場合、代入する変数を declare -i にて数値型であることをあらかじめ宣言しておけば、カッコなどが不要になる。
declare -i z
x=10
y=20
z=x+y*3
echo $z

csh・tcsh におけるスクリプト
csh・tcsh での書き方を以下にまとめておく。

なお、世の中一般においては csh や tcsh でシェルスクリプトを書くことはよくないとされている。当ページ管理人としてはコマンドラインから使う分には tcsh が好きなので聞こえないふりをしてきた (そして Perl や Python でスクリプトを書いてきた)。

vi や emacs 等のエディタで
#!/bin/csh -f
echo "Hello World"
というファイルを sample.csh などのファイル名で保存する。その後、
% chmod +x sample.csh
として実行権限を付与することで、
% ./sample.csh
Hello World
と実行できる。


[[csh・tcsh での環境変数セット #csh-env-variable-set
setenv HOGE FUGA


csh・tcsh での一定回数のループ
set i=0
while ( $i < 5 )
echo $i
@ i = $i + 1
end

csh・tcsh でのリスト処理
set vars=(x y z)
foreach var ($vars)
echo $var
end


csh・tcsh での if 文
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

csh・tcsh での continue・break
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

csh・tcsh での switch 文
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

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

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

UNIX/Linux の sh・bash、csh・tcsh など、シェルやシェルスクリプト関連の記号類をまとめてみる。


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

$$: プロセスID
プロセス 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. グループコマンド
以下に詳細を説明する。

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

{} その2. ブレース展開
ブレースにて文字列展開が行える。例えば以下のように使用する。
% mkdir /foo/bar/{dir1,dir2,dir3}
⇒ mkdir /foo/bar/dir1 /foo/bar/dir2 /foo/bar/dir3
複数個の文字列のうち、一部分のみ異なる場合に有用。なお、ブレース展開はファイル・ディレクトリが存在するかどうかをチェックしているわけではなく、単に文字列として展開しているだけである。

{} その3. グループコマンド
グループコマンドは
% { command1; command2; }
というふうに複数コマンドをグループ化する際に使用する。グループ化するだけだと意味はないが、複数コマンドの出力をまとめてリダイレクトしたり、ひとつにまとめることでジョブ制御しやすくするなどの利点がある。

なお "()" はサブエシェルで実行されるが、"{}" は現在のシェルで実行されるという違いがある。
% { command1; command2; }
→ 現在のシェルで実行
% (command1; command2)
→ サブシェルで実行

なお、"{" の後、"}" の前には空白が必要であることと、最後のコマンドの後に ";" セミコロンが必要であることに注意。
% {command1; command2;}
→ NG。{ の後に空白がない。} の前に空白がない。
% { command1; command2 }
→ NG。command2 の後に ; がない。

sh・bash は ()・{} が使える。csh・tcsh は () のみである。

#: コメント
シャープ以降はコメント扱いとなり動作には影響しない。
# ここはコメント
ls -l # ここもコメント

sh・bash では、コメント末尾に \ (エスケープ) を置いても継続行とみなされないため、途中をコメントアウトすることができない。
#!/bin/sh
echo a \
  b \
  c \
というスクリプトがあり、
#!/bin/sh
echo a \
#  b \
  c \
と途中をコメントアウトしてしまうと、
echo a を実行
c を実行
と意図しない挙動になってしまう。

なお、csh・tcsh だと該当部分のみコメント化され、
a c
と表示される。

はまりがちなこととして、下記のように "\" の後に空白を入れてしまうと、改行コードのエスケープではなく空白のエスケープになってしまい、結果的に
単なる空白と扱われてしまう。
echo a \(空白)
b
そうすると、意味としては下記のようになり、echo a ' ' を実行した後に b コマンドを実行、となってしまう。
echo a ' '
b

ブラウザからスクリプトをコピペした場合などに、行末に空白が入ることはよくあるので注意しよう。

:
":" は「何もせず、必ず正常終了する」というコマンドである。sh・bash・csh いずれも内部コマンドとして用意されている。

bash では、下記の then〜else にように空のブロックを書くと "syntax error near unexpected token `else'" とエラーになってしまう。
#!/bin/bash
if [ $var = 0 ]; then
# 問題ないので何もしない
else
echo "ERROR"
exit 1
fi
こういうときに : を入れておくと、空ブロックにならずエラーを回避できる。
#!/bin/bash
if [ $var = 0 ]; then
# 問題ないので何もしない
:
else
echo "ERROR"
exit 1
fi

無限ループを書きたい場合にも重宝する。
#!/bin/sh
while :
do
if [ XXXXX ]; then
break
fi
done

空ファイルを作る、または存在するファイルを 0 バイトに truncate したい場合。
% : > foo.txt
別案として
% cp /dev/null foo.txt
などがあるが、外部コマンドを実行しない分だけ処理は軽い。

複数行コメントアウトに使うこともできる。
:<<'EOT'
コメント
コメント
コメント
EOT

なお、: コマンドの代わりに true コマンドを使っても同じ結果になる。

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

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

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

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

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

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

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

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


タイムスタンプの表示
ls -l で mtime、ls -lc で ctime、ls -lu で 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
% ls -lu abc.txt
-rw-r--r-- 1 user group 2052 Feb 12 08:31 abc.txt
⇒ atime
また、stat コマンドでもそれぞれのタイムスタンプを表示することができる。
% stat hoge.txt
File: `hoge.txt'
(略)
Access: 2018-05-10 21:14:12.372266973 +0900
Modify: 2018-05-10 21:14:11.165267108 +0900
Change: 2018-05-10 21:14:11.165267108 +0900

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

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

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 は更新されないかと予想していたが、試してみると更新されてしまった)。

シンボリックリンクのタイムスタンプ
いまどきの 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)。これぞまさしくファイル生成時刻と言えるだろう。NetBSD や macOS でも birthtime は使用可能である。

Linux 界隈では、ext4 よりファイル生成時刻 crtime が追加されているが、カーネルや glibc が未対応であるため、値を取り出すことができない状況が続いていた。2017年5月リリースの Linux-4.11 カーネルにて statx(2) システムコールが追加され、struct statx_timestamp stx_btime にて取得が可能になったので、早晩 glibc も対応が進むと思われる。

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

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

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

パッケージの探し方 UNIX/Linux におけるパッケージ名の確認方法や探し方について

UNIX/Linux においては、外部パッケージが提供されており、簡単にインストールすることができる。しかしながらパッケージの名前やパッケージの分け方にいろいろクセがあるので、パッケージ名を確認する方法やパッケージの探し方のコツを述べる。


パッケージ検索
パッケージを検索したい場合、下記から検索するとよい。パッケージ名・説明・ファイル名などで検索が可能である。
FreeBSD の場合:
RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) の場合:
Ubuntu の場合:
Ubuntu パッケージ検索 https://packages.ubuntu.com/ja/

また、RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) の場合は、yum コマンドなどを使ってコマンドラインから検索することもできる。

以下、パッケージ名称の典型的なパターンを示す。

コマンド単体系
パッケージ名とコマンド名が同じ、という一番わかりやすいパターンが多い。例えば Linux の RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) の telnet パッケージをインストールすると、telnet コマンドと telnet のマニュアルのみがインストールされる。
/usr/bin/telnet
/usr/share/man/man1/telnet.1.gz

クライアント・サーバ
Linux の RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) では、MySQL は以下のように分割されている。
  • mysql … mysql コマンドなどのクライアントと、それが利用するライブラリ
  • mysql-server … mysqld などのサーバ
  • mysql-libs … mysql・mysql-server が共通で利用する設定ファイル (/etc/my.cnf 等) やライブラリ等

一方で、FreeBSD や Ubuntu では
  • mysql-client … mysql コマンドなどのクライアント
  • mysql-server … mysqld などのサーバ
と分かれている。

ライブラリ
ライブラリとは
/usr/lib/libc.a
/usr/lib/libc.so
/lib64/libc-2.12.so
などに置かれる、プログラムの共通部品である。パッケージ内にコマンドとライブラリ両方が含まれている場合もあるし、ライブラリが単体でパッケージとして提供されているものもある。

そのライブラリを開発するためのプロジェクトが存在する場合、libXXX や XXXlib というパッケージ名で提供されることが多い。
  • libpng … 画像フォーマット PNG に関するライブラリ。libpng プロジェクトが開発している。
  • libiconv … 文字エンコーディング変換のライブラリ。GNU プロジェクトのひとつ
  • zlib … 圧縮・伸長ライブラリ。zlib プロジェクトにて開発している。

一方、コマンドなどと一体となって開発されているライブラリであるが、OS またはディストリビューションの判断でライブラリが別に提供されているものもある。例えば bzip2 は、オフィシャルのソースをビルドすると、bzip2 コマンドと libbz2.so などのライブラリが生成されるが、配布物としては "bzip2" である。

しかしながら、bzip2 のライブラリを利用している (bzip2 プロジェクトとは直接関係のない) コマンドが存在するため、ディストリビューターはライブラリを切り出したいと考えた。このような場合、lib または libs という名前が付くことが多い。

その結果、Linux の RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) では、
  • bzip2 パッケージ … bzip2 などのコマンドやマニュアル
  • bzip2-libs パッケージ … /lib64/libbz2.so.1.0.4 などのライブラリ
という分け方をしている。一方、Ubuntu では
  • bzip2 パッケージ … コマンド
  • libbz2 パッケージ … ライブラリ
という名前で分けている。オフィシャルの配布物で分かれていないものを、各ディストリビューションの都合で分割したので、名前が統一されていないわけだ (ちなみに lbzip2 というマルチコア対応の gzip2 もあるが、これはライブラリではない)。

一方、FreeBSD では bzip2 パッケージがコマンドとライブラリを提供し、bzip2 のライブラリを使うコマンドは bzip2 に依存関係がある、と定義することになっている。

RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) では、スタティックリンク用のライブラリ (libc.a など) はパッケージ名に static が付けられているようだ。
glibc-static
libxml2-static

GNU 系
Linux ではおおむね GNU 系のコマンドが入っているが、FreeBSD などの BSD 標準コマンドではなんらかの問題があり、あえて GNU 系のコマンドを使いたい場合がある。その場合、gnu- または g で始まるパッケージが用意されていることがある (しかしながら patch のように gnu も g もつかないものをあるようだ)

以下は FreeBSD の ports をざっと見て、FreeBSD 標準コマンドで存在するが、別途 GNU のパッケージも用意されているものをリストアップしたものである (2017/05 調査)。
  • gawk パッケージ … GNU の awk コマンド
  • gcpio パッケージ … GNU の cpio コマンド
  • getopt パッケージ … GNU の getopt コマンド
  • gindent パッケージ … GNU の indent コマンド
  • gmake パッケージ … GNU の make コマンド
  • gnu-watch パッケージ … GNU の watch コマンド
  • gnubc パッケージ … GNU の bc・dc コマンド
  • gnugrep パッケージ … GNU の grep コマンド
  • gnuls パッケージ … GNU の ls コマンド
  • groff パッケージ … GNU の roff コマンド
  • gtar パッケージ … GNU の tar コマンド
  • binutils パッケージ … GNU の ld・as・gprof・strings・strip コマンドなど
  • findutils パッケージ … GNU の find・locate・updatedb・xargs コマンド
  • diffutils パッケージ … GNU の diff・diff3・cmp・sdiff コマンド
  • shtool パッケージ … 各 OS で非互換性が発生してしまったコマンドの作り直し版
  • patch パッケージ … GNU の patch コマンド

上記でインストールされるコマンドは、おおむね先頭に g が付くようになっているが、gnubc だと g なしで /usr/local/bin/bc、/usr/local/bin/dc としてインストールされるような気がする (未確認)。

devel
devel とついているパッケージは、開発者向けのヘッダファイルやコマンドを含むことが多い。そのパッケージ自体のビルドを行いたいときや、そのパッケージを利用する別パッケージのビルドを行いたい場合にインストールするとよい。

例えば zlib-devel であれば
/usr/include/zconf.h
/usr/include/zlib.h
/usr/share/doc/zlib-devel-1.2.3/example.c
/usr/share/man/man3/zlib.3.gz
のように、ヘッダファイル・サンプルソース・ライブラリのマニュアルなどが含まれている。

名前がわかりづらいもの
Apache HTTP Server は、Linux の RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) でのパッケージ名は httpd であるが、FreeBSD や Ubuntu では apache である。

前者は、Apache HTTP Server のソースアーカイブは httpd-2.xx.tar.gz などであることから httpd としたのであろう。後者は Apache と言えば昔は Apache HTTP Server の開発グループであったため apache としたのであろう。現在の Apache は多種多様なソフトウェアを作成する財団であるため、apache-httpd とすべきではと思うが、今更変えるメリットもないし、パッケージ名を他プロジェクトとあわせるメリットも特にないので放置されているのであろう。

Apache ソフトウェア財団が作成しているソフトウェアについて、apache が付いているかはバラバラである。例えばビルドツールの Apache Ant は下記のとおり。
  • FreeBSD では apache-ant パッケージ
  • RedHat 系 OS では ant パッケージ
  • Ubuntu では ant パッケージ
例えば Apache Tomcat は、FreeBSD・RedHat 系 OS・Ubuntu いずれも tomcat パッケージである。

開発言語系
Perl・Python・PHP などのモジュール名の規則はいろいろである。

Perl の場合:
  • FreeBSD では Perl5 のモジュールは p5- から始まる。例えば HTTP::Cookies モジュールは、p5-HTTP-Cookies パッケージに含まれる (Perl6 はどうするんだろう)
  • Linux の RedHat 系 OS (RedHat Enterprise Linux・CentOS・Amazon Linux 等) では、Perl モジュールは perl から始まる。例えば URI::Encode モジュールは perl-URI-Encode パッケージにに含まれる。
  • Ubuntu では lib*-perl となる。例えば Exception::Handler モジュールは libexception-handler-perl パッケージに含まれる。

Python の場合:
  • FreeBSD では Python2系は py27-、Python3系は py36- が先頭に付く (そのうち py37 になるのだろうか)。
  • RedHat 系 OS では 先頭の python-、python3-、python34-、末尾の -python など、いろいろあってよくわからない。
  • Ubuntu では Python2系は python-、Python3系は python3- が先頭に付く。

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

UNIX/Linux のシェル sh・bash・csh・tcsh で使うことができるファイルグロブ・ワイルドカードの使い方を説明する。ファイルグロブ・ワイルドカードとは、シェルが * ? {} [] ~ などの文字列を解釈し、ファイル名として展開することである。なお、正規表現と似てはいるが、別物である。


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

ファイルグロブ・ワイルドカードの基本
まずは下記を覚えよう。ファイルグロブ・ワイルドカードの処理はシェルの仕事であるため、使っているシェル (sh・bash・csh・tcsh など) によって細かな違いはあるが、下記にあげたものはおおむねどのシェルでも使えると思われる。
  • ? 任意の 1文字。ただし / は除く
  • * 任意の文字列。空文字も含む。ただし / は除く。単体で使われた場合は、. から始まるファイルにはマッチしない。
  • [...] 角括弧内の 1文字。[abc] は a か b か c にマッチする。[a-z] のように範囲指定もできる。[^a-z] で a-z 以外、となる。
  • ~ 先頭でチルダが使われた場合、ユーザのホームディレクトリに置換される。~user という書き方もできる。

以下に基本的な例をあげる。
% ls *
カレントディレクトリにあるファイル・ディレクトリすべて。ただし . から始まるファイルは除く
% ls .*
カレントディレクトリにある . から始まるファイル・ディレクトリすべて。. や .. も含む。
% 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

"?" や "*" が「/ にマッチしない」とはどういうことかというと、もし "/" にマッチするとした場合、
% ls *
は、サブディレクトリも含めたあらゆるファイル・ディレクトリにマッチする、となってしまう。それでは使いづらいため、"?" や "*" は 1つのディレクトリ内でのファイル名・ディレクトリ名にマッチする、とされている。

グロブ展開はシェルの仕事
ファイル名の展開は 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 を作成するには jot コマンドや seq コマンドを使えばよい。
% touch `jot -w file%03d.txt 999`
→ FreeBSD や macOS なら jot コマンドを使う。
% touch `seq -f'file%03.0f.txt' 1 999`
→ Linux なら seq コマンドを使う。

応用
以上が基本であるが、使いこなすのはなかなか難しい。わかっているつもりの初心者が実践できないのは「組み合わせる」こと。試行錯誤してがんばってほしい。「わからなくなったら 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 ・SSD・フロッピーディスク・CD-ROM などのどこにどのようなデータが格納されているかを管理する仕組みである。UNIX/Linux では ufs・ext3・ext4・zfs などのファイルシステムがある。


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

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

以下、UNIX/Linu 界隈で使われる主なファイルシステム一覧を紹介する。

UNIX 界隈で使用されるファイルシステム
UFS
UNIX File System。UNIX 初期のファイルシステム。
FFS
4.2BSD において開発された Fast File System。UFS のブロックサイズを倍にしたもの。
FFFS
4.4BSD において機能拡張された Fat Fast File System。FFS の後継。この FFFS が現在でも各種 UNIX のベースとなっている。まぎらわしいことに、この FFFS を指して UFS・FFS などの呼称が使われることが非常に多い。
UFS2
FreeBSD 5.0 において FFFS を機能拡張したファイルシステム。64bit 拡張を行なったため、1TB を越えるファイルシステムを構築可能。FreeBSD・NetBSD 界隈で使用されている。ファイルサイズ以外は UFS とほぼ同じ。これを UFS と呼ぶことも多い。
ZFS
Solaris10 で採用となったファイルシステム。ウリは 128bit のためエクサバイトレベルまでのサイズを扱えること、チェックサムによる訂正機能、RAID 機能を持つ、超高速なスナップショット機能と、ロールバック機能、などなど。当初は Sun Microsystems による開発であったが、現在は OpenZFS としてオープン化されている。

Linux 界隈で使用されるファイルシステム
ext2
Linux 界隈で使用されている。
ext3
Linux 界隈で使用されている。ext2 の後継。
  • ジャーナルファイルの採用により fsck に要する時間が短縮されている
  • ext2 より速い
などの利点はあるが、基本的には ext2 と同じである。2038年問題を持つ。
ext4
Linux 界隈で使用されている。ext3 の後継。
後方互換性を確保しつつ、ファイルサイズの拡張、最大ボリュームの拡張、高速化、2038年問題の解消などを行なった (が、ext4 は 2514年までらしい)。
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 でマウント。
という挙動をするようになっている。よって、この辺の拡張方式についてはあまり気にする必要はないだろう。

関連コマンド等
ファイル名・ディレクトリ名などの最大サイズ・ファイル数上限などの細かな情報は「ファイル制限まとめ」の項を参照。

マウントする際、ファイルシステムの種類を指定する。

df コマンドで現在マウントされているファイルシステム一覧を表示できるが、-T オプションをつけるとファイルシステムの種類を表示する。

Linux での例:
% df -T
Filesystem     Type  1K-blocks     Used Available Use% Mounted on
/dev/vda3      ext4  100762004 48285348  47351488  51% /
tmpfs          tmpfs    509976        0    509976   0% /dev/shm
/dev/vda1      ext4     243823    77269    153754  34% /boot

FreeBSD での例:
% df -T
Filesystem          Type  1K-blocks     Used    Avail Capacity  Mounted on
zroot/ROOT/default  zfs    84263060  1271260 82991800     2%    /
devfs               devfs         1        1        0   100%    /dev
zroot/tmp           zfs    82991896       96 82991800     0%    /tmp
zroot/usr/home      zfs    93029100 10037300 82991800    11%    /usr/home
(略)

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

UNIX/Linux におけるファイル・ディレクトリ名に使用可能な文字、ファイル名の最大長、最大ファイルサイズ、ファイル数の最大長などについてまとめる。



ファイル名で使用可能な文字
UNIX/Linux において、ファイル名として使用できない禁止文字は以下の 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 コマンドもコンパイル支援を行うのが主目的であるためサフィックスルールを実装している。このように、いくつかのコマンドは拡張子と密接に関係している。

ファイルサイズの上限(ファイル最大サイズ)
1つのファイルのファイルサイズの上限は、使用しているファイルシステムの制限と、そのファイルシステムのブロックサイズによるので説明しづらいのだが、おおむね以下のような
  • 2GB までのファイルサイズなら間違いなくOK
  • それ以上になると、ファイルシステムとしてはテラバイト程度は対応しているが、ブロックサイズが小さい場合、16GB が上限となる場合がある。例えば Linux で使われている ext3 で、ブロックサイズ 1024 バイトの場合は上限 16GB である。
  • いまどきの標準はブロックサイズ 4096 が多いのではないかと思うが、その場合は例えば FreeBSD で使われる UFS2 であれば 4TB、Linux で使われる ext2/ext3 であれば 2TB である。
  • 32bit 環境だと OS の制限がある場合がある。32bit な Linux 環境では上限は 2TB である。

ファイル名の長さ制限(ファイル名最大長)
多くの 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

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

ファイル・ディレクトリ数の上限
ひとつのディレクトリ直下に格納できる、ファイル・ディレクトリ数については、
  • Linux で使用されている ext2・ext3 では、ひとつのディレクトリ直下のサブディレクトリ数上限は 31998個。ファイル数制限はなし
  • Linux で使用されている ext4・XFS ではファイル・ディレクトリとも無制限
  • FreeBSD や Solaris 等で使用されている UFS (FFS)・UFS (FFFS)・UFS2・ZFS はファイル・ディレクトリとも無制限
  • ただし、Solaris の UFS ではひとつのディレクトリ直下のサブディレクトリ数上限は 32767個 (ZFS なら無制限)
となっている。

上記のサブディレクトリの制限を超えてディレクトリを作成しようとすると、
Too many links (リンクが多すぎます)
などのエラーが出るであろう。

しかし、少なくとも 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 コマンドを参照。

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

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

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

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

UNIX/Linux において、「プロセス」とは現在実行中のプログラムのことを指す。全てのプロセスには「プロセスID (PID)」という番号が付与される。

目次:


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

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

    現在実行中のプロセスを終了させるには、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 にセットして、プロセスは終了する。しかし、子プロセスの環境変数は親プロセスに影響しないため、親プロセスであるシェルの環境変数は全く変化しない。

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

    ランダム・乱数まとめ ランダムな、数値・数字・16進数文字列・バイナリデータ・パスワードなどの生成・出力方法

    UNIX・Linux で、シェルスクリプトから使うことができるランダムな数値・乱数・文字列などの作成方法を紹介する。openssl・jot・$RANDOM・/dev/urandom・awk など、いろいろなやりかたがある。


    openssl rand を使う
    100 バイトのランダムなバイナリデータを生成し、file に出力する。
    % openssl rand 100 > file
    10 バイトのランダムデータを 16進数として出力する
    % openssl rand -hex 10
    d5e6809fa57b7db12610
    → 1バイト2文字なので、20文字となる
    % openssl rand -hex 10 | tr a-z A-Z
    C287E323911E5357A6FF
    → 大文字の16進数にする場合は tr で
    % openssl rand -hex 10 | tr a-z A-Z | sed 's/\(..\)/\1 /g'
    D0 3E 27 F4 86 2F 7C EE AD E4
    → 1バイトずつスペースを入れてみた
    16 バイトのランダムデータを BASE64 形式で出力する
    % openssl rand -base64 16
    9zW8RaU/y+EkJ+G/4UcgtQ==

    jot -r を使う
    *BSD 系なら jot -r が使える。
    % jot -r 5
    19
    26
    78
    68
    75
    ⇒ 1〜100 のランダムな数字を 5 個表示する。
    % jot -r 5 0 255
    82
    219
    248
    44
    185
    ⇒ 0〜255 のランダムな数字を 5 個表示する。

    bash・zsh なら $RANDOM
    bash・zsh には、参照するたびに 0〜32767 の範囲の異なる値を返す $RANDOM というシェル変数が用意されている。
    % echo $RANDOM
    3999
    % echo $RANDOM
    9460
    % echo $RANDOM
    21428

    0〜15 の数を 10回取得したい場合
    % for i in `seq 1 10`; do echo $(( $RANDOM % 15 )); done;
    → bash では成功したが zsh では同じ値が返ってきてしまう
    % for i in `seq 1 10`; do echo `expr $RANDOM % 15`; done;
    → こちらは bash・zsh いずれも OK のようだ

    /dev/random・/dev/urandom から
    dd でバイナリデータを 10バイト取得
    % dd if=/dev/urandom bs=1 count=10 > x
    head で 10バイト
    % head -c 10 < /dev/urandom > x
    od で値を取得
    % od -vAn -N1 -tu4 < /dev/urandom
    → 1バイト取得 (0〜255)
    % od -vAn -N2 -tu4 < /dev/urandom
    → 2バイト取得 (0〜65535)
    % od -vAn -N4 -tu4 < /dev/urandom
    → 4バイト取得 (0〜4294967296)
    % echo $((`od -vAn -N2 -tu4 < /dev/urandom` % 100))
    → 100の余りをとって、0〜99 を取得
    10バイト取得し、base64 化
    % cat /dev/urandom | head -c 10 | base64
    ftnLS5s5txujrA==
    20個の数字を取得
    % env LC_ALL=C tr -dc 0-9 < /dev/urandom | fold -w 20 | head -1
    57288818183277636892
    20個の英数字 (小文字) を取得
    % env LC_ALL=C tr -dc a-z0-9 < /dev/urandom | fold -w 20 | head -1
    5brs7zog87raykb4wjuq
    20個の英数字 (大文字小文字混在) を取得
    % env LC_ALL=C tr -dc a-zA-Z0-9 < /dev/urandom | fold -w 20 | head -1
    rLDAIXmzArDfDZuQ3r8e
    20個の英数字 (大文字小文字記号混在) を取得。パスワード生成に使える。
    % env LC_ALL=C tr -dc 'a-zA-Z0-9~!@#$%^&*_-' < /dev/urandom | fold -w 20 |head -1
    E7!SZjhqT$wQd@r_k6C0

    sort や shuf で行の並べ替え
    新しめの GNU sort であればランダムにソートする -R オプションがあるかもしれない。
    % sort -R < file.txt
    → ファイルから
    % seq 1 10 | sort -R
    → 1〜10 の連番をランダムに並べ替え
    % jot 10 | sort -R
    → seq コマンドがなければ jot で
    sort -R の場合、ハッシュでソートするため、同じ行があると固まって出力されてしまう。重複値が隣り合うと困る場合は、shuf コマンドを使うとよい。

    % for i in 1 2 2 2 3 3 3 3 3 4 5 6 7 8 ; do echo $i ; done | sort -R
    → sort -R だと 2 や 3 が必ず隣り合って出力される
    % for i in 1 2 2 2 3 3 3 3 3 4 5 6 7 8 ; do echo $i ; done | shuf
    → shuf コマンドだと大丈夫

    shuf コマンドであれば、以下のようなこともできる。
    % cat file.txt | shuf -n 1
    → ファイルの中からランダムに 1行出力
    % cat file.txt | shuf -n 3
    → ファイルの中からランダムに 3行出力

    N% の確率で〜する
    bash・zsh なら $RANDOM で
    if [ $(($RANDOM % 100)) -lt 5 ]; then
    echo "5%の確率"
    fi
    $RANDOM が使えないなら
    num=`env LC_ALL=C tr -dc 0-9 < /dev/urandom | fold -w 2 | head -1`
    if [ $num -lt 5 ]; then
    echo "5%の確率"
    fi

    awk で
    awk で 0〜99 を 10回出力
    % awk 'BEGIN {srand(); for (i=1;i<=10;i++) print int(rand()*100)}'
    → srand は秒で初期化しているので、1秒以内に複数回実行すると同じ値が返ってくることに注意

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

    UNIX/Linux のシェル sh・bash・csh・tcsh のリダイレクトを説明する。ファイルへの出力、コマンド出力を別のコマンドの入力とする、標準入力・標準出力・標準エラー出力、パイプなどもあわせて説明する。


    リダイレクトの基礎
    コマンドを実行したとき、その出力をファイルに書き込みたい場合は
    % 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 コマンドと同様に、
    • 普通の出力は標準出力に
    • ユーザに気づいてほしいエラー表示などは、標準エラー出力に
    という方針でプログラムが作成されている。

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

    sh・bash のリダイレクト一覧
    sh・bash で使用できるリダイレクトの一覧を下記に示す。たくさんありすぎるので初心者は「いろんなのリダイレクトがある」と思っておくだけでよい。
    >[ファイル] ファイルに出力する
    n>[ファイル] ファイルに出力するディスクリプタn を新規作成する。
    >>[ファイル] ファイルに追記する
    n>>[ファイル] ファイルに追記するディスクリプタn を新規作成する。
    <[ファイル] ファイル内容を標準入力から読み込む
    n<[ファイル] ファイル内容を読むディスクリプタn を新規作成する。
    &>[ファイル] 標準出力と標準エラー出力をまとめてファイルにリダイレクト。bash 拡張
    >&[ファイル] 標準出力と標準エラー出力をまとめてファイルにリダイレクト。bash 拡張
    &>>[ファイル] 標準出力と標準エラー出力をまとめてファイルに追記。bash 拡張
    >(コマンド) 別のコマンドに対して標準出力を渡す。プロセス置換と呼ばれる。bash 拡張
    <(コマンド) 別のコマンドの結果を標準入力から受け取る。プロセス置換と呼ばれる。bash 拡張
    n<&m 入力ディスクリプタm を複製してディスクリプタ n を生成する。
    <&m 入力ディスクリプタm を複製してディスクリプタ 0 を生成する。
    n>&m 出力ディスクリプタm を複製してディスクリプタ n を生成する。
    >&m 出力ディスクリプタm を複製してディスクリプタ 1 を生成する。
    >n<&m- 入力ディスクリプタm を複製してディスクリプタ n を生成し、m をクローズする
    >n>&m- 出力ディスクリプタm を複製してディスクリプタ n を生成し、m をクローズする

    あまり使わないであろうものは下記にまとめた。
    <>[ファイル] 読み書き両用でオープン
    >|[ファイル] ファイルに出力する。ファイル上書き不可モードでも出力する。

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

    標準出力を file1 に、標準エラー出力を file2 に、などと振り分けるには下記のように書く。
    % command 1>file1 2>file2
    いずれかを /dev/null に捨てることで、どちらかだけを表示することも簡単に指定できる。
    % 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

    sh・bash におけるリダイレクトの順序
    さて、先にも述べたように、標準出力と標準エラー出力をファイルに出力する場合は下記のようにする。
    % command 1>file 2>&1
    % command >file 2>&1 (1 を省略してもよい)
    しかし順番を逆にするのは誤りである。
    % command 2>&1 1>file
    → 誤り!
    % command 2>&1 >file
    → 誤り!

    なぜ上の書き方が正しいのか、なぜ下の書き方ではダメなのか説明できるだろうか。なお、「リダイレクトは右に書いたものから順に評価されるから」は間違いである。

    そもそも「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・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
    とするとよい。

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

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

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

    関連コマンド
    コマンド結果を画面とファイル両方に出力したい場合、tee コマンドを使うとよい。

    リダイレクト時にバッファリングのために意図せぬ挙動になることがある。stdbuf を参照してほしい。

    ロケール 言語などを切り替える仕組み (ロカール・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
    を使うとよい。

    圧縮・伸長まとめ compress (.Z)・gzip (.gz)・bzip2 (.bz2)・xz (.xz) とマルチスレッド版 pigz・pbzip2・pxz の圧縮・伸長 (解凍) のまとめ

    現在の UNIX・Linux でよく使われる圧縮形式や、圧縮・伸長 (解凍・展開) コマンドについて説明する。


    圧縮率と速度
    圧縮率は、一般的には下記のとおりで、xz が一番圧縮率が高い (小さいサイズに圧縮できる)。
    gzip < bzip2 < xz
    圧縮・伸長 (解凍・展開) の速度は圧縮率に比例して、
    gzip < bzip2 < xz
    となり、xz が一番遅い。

    近年 CPU 速度が頭打ちで、その代わりにマルチコア対応が進んできた。gzip・bzip2・xz などはいずれもシングルプロセスで圧縮・伸長を行うため、1コアを集中的に使うものの、他のコアが遊んでしまう。最近はマルチコアを有効活用できるよう、マルチスレッド版の pigz・pbzip2・pxv コマンドなどが出てきた。

    gzip コマンド
    UNIX における最も標準的な圧縮・伸長コマンド。
    拡張子 .gz (tar + gzip の拡張子は .tar.gz または .tgz)
    基本コマンド gzip
    伸長 gunzip
    表示 gzcat, zcat
    圧縮したままgrep zgrep (Solaris では gzgrep のようだ), zegrep, zfgrep
    マルチスレッド版 pigz (unpigz)
    tarオプション tar zcf (圧縮)、tar zxf (展開)、tar ztf (一覧表示)
    その他 zforce, znew, zcmp, zdiff, gzexe, gzrecover

    bzip2 コマンド
    gzip より圧縮率が高いが、動作が遅い。Linux 界隈では標準圧縮フォーマットとして認知されている感がある。
    拡張子 .bz2 (tar + bzip2 の拡張子は .tar.gz または .tbz2)
    基本コマンド bzip2
    伸長 bunzip2
    表示 bzcat
    圧縮したままgrep bzgrep, bzegrep, bzfgrep
    マルチスレッド版 pbzip2
    tarオプション tar jcf (圧縮)、tar jxf (展開)、tar jtf (一覧表示)

    xz コマンド
    bzip2 より圧縮率が高いが、動作が遅い。特に圧縮がとても遅い。
    拡張子 .xz (tar + xz の拡張子は .tar.xz または .txz)
    基本コマンド xz
    伸長 unxz
    表示 xzcat
    圧縮したままgrep xzgrep, xzegrep, xzfgrep
    マルチスレッド版 pxz (または xz の -T オプション)
    tarオプション tar Jcf (圧縮)、tar Jxf (展開)、tar Jtf (一覧表示)

    昔の UNIX で使われていたコマンドは下記。

    compress コマンド
    古い形式であり、今となっては使わなくてよいでしょう。Linux では ncompress パッケージとして提供されているようだ。
    拡張子 .Z
    基本コマンド compress
    伸長 uncompress
    表示 zcat
    圧縮したままgrep たぶんない
    tarオプション tar Zcf (圧縮)、tar Zxf (展開)、tar Ztf (一覧表示)

    pack コマンド
    とても古い形式であり、今となっては使わなくてよいでしょう。
    拡張子 .z
    基本コマンド pack コマンド
    伸長 unpack コマンド


    以下は Windows 系でよく使われる形式。

    unzip コマンド
    概要 Windows 界隈で使われている zip 形式を扱える。
    コマンド unzip
    拡張子 .zip

    7z コマンド
    概要 Windows 界隈で使われている 7z 形式を扱えるコマンド。圧縮率は xz コマンドとほぼ同じ。
    拡張子 .7z
    コマンド 7z・7za・7zr・p7zip

    lha コマンド
    概要 Windows 界隈で使われていた lhz・lha 形式を扱えるコマンド。
    コマンド lha
    拡張子 .lhz .lha

    さらにその他 (未調査)
    rzip・lzip・lzop・rar・lz4

    ENDMARK xx