CentOS8で初めてls
のクォート機能を見たとき、真っ先に思い出したのが「UNIX原典」という本に載っていた「UNIX環境におけるプログラム設計」という論文だった。これは1984年に出た本で、学生の頃にプログラミングの師匠に貰ったものだったが、特にお気に入りが前述の論文だ。で、これをもう一度読もうと思ったら、オフィスのどこかに紛失してしまって見つからない。仕方がないのでもう一冊買ってしまった。
ところで該当の論文は、原著がPDFで公開されているので、もしその気があるならこちらで読める。
今回はこの論文の内容を紹介したい。
UNIX哲学 – 1つのことを上手にやる
cat
は、UNIXらしいコマンドとしてよく取り上げられる。まず名前が実にUNIXらしい。cat
はconcatenate(連結)から取られていることから分かる通り、本来は「連結し、プリントする」コマンドだった。典型的な利用例は複数のファイルを指定することで、それらを連続して標準出力へプリントする。cp
とは異なり、ファイルに保存したければリダイレクト >
でファイル名を指定しなければならないし、パイプ |
で他のコマンドへ接続することもよくある。
cat
はまさにこのためのツールで、他の動作は何もしない。UNIXの第1版のマニュアルではオプションが1つも説明されていないほどシンプルなコマンドだった。
現在のUNIX、たとえばLinuxのcatのマニュアルを見ると、--help
と--version
を除いても10個のオプションが説明されている。たとえば-n
(--number
)というオプションは、行番号を付与するし、-v
(--show-nonprinting
)は非表示文字をコントロール記法で表示する。他にも、タブを可視化したり余分なスペースを圧縮したりする「便利な」オプションがある。だが、これらのオプションは果たしてcat
に必要なのだろうか。
もしcat -n
がそんなに便利で利用機会が多いなら、linemumber
のようなone-linerを書けばよい。awk
ならこう書ける。
awk '{ print NR "\t" $0 }' $*
あるいはpr -n
を使って行番号を付けさせればよい。
cat -v
についても同様に、ファイル中の非表示文字を可視化するコマンドを作るべきだと主張する。
A UNIX program should do one thing well, and leave unrelated tasks to other programs. Cat’s job is to collect the data in files. Programs that collect data shouldn’t change the data; cat therefore shouldn’t transform its input.
UNIXプログラムは一つのことを上手にやるべきであり、それ以外の事は他のプログラムに任せるべきだ。catの仕事はファイルのデータを集めることだ。プログラムは集めたデータを変更するべきではない、したがってcatはその入力を加工するべきではない。
http://a.papnet.eu/UNIX/bltj/06771909.pdf
lsの問題
同じ論文では、ls
の段組みについても論じられている。
AT&T版では、ls
は段組みをせず、ファイルのリストは1列のリストで表示されていた。ターミナルの幅をいっぱいに使って(とはいえ昔は幅80文字と仮定していたが)段組みをするようになったのはBSD版のls
だった。しかも、BSD版ls
は「出力先がターミナルのときだけ段組みをする」ように動作した。出力がファイルやパイプならば、この段組み機能は無効になるのだ。
論文は、この機能についても批判する。ls
の段組み機能はファイルリストを生成するためだけに存在し、他のプログラムからアクセスできない。もし段組みが必要なら、pr
がそれを上手にやってくれる。5段組みにするならば、
ls | pr -5 -t -l1
とすればよい。いちいちpr
を呼ぶのが面倒なら、これをバッチ化してしまえばよいというのだ。
自分は、この論点はちょっと行き過ぎではないかと感じている。ls
が段組みをしなくなったら、いちいち大変面倒なことになるからだ。なにしろ、第1版のUnix(1970年とか、そんな昔の話だ)が、
The first versions of the UNIX system were written in the days when 150 baud was “fast” and all terminals used paper. Today, 9600 baud is typical, and hard-copy terminals are rare.
UNIX第1版は紙を使う150ボーのターミナルが「速い」と言われていた時代に作られた。今日、9600ボーは並みの速度であり、印字型ターミナルは希少となっている。
http://a.papnet.eu/UNIX/bltj/06771909.pdf
なんて言っちゃっている時代の論文の議論だから、リストを表示した後、じっくり読めるか否か、表示をし直すのにかかる時間や手間暇なども大きく変わっているということを勘案しないといけない。たとえば現代では、ディスプレイは途方もなく大きく高精細で、文字はリアルタイムで再描画できるというような事情がある。
lsはメタ文字をクォートするべきか?
さてここからは、新しいls
(とはいえ最近CentOS8の導入で知ったというだけの話だが)の動作の話、メタ文字を含むとシングルクォートで括るという話だ。そもそも、なぜクォート機能が必要になったのだろうか。
まず最初に、ファイル名に使う文字の話から始めなければならない。昔は(30年ぐらい前は)、ファイル名にスペースを使うと「後で苦労することになるだろうな」と無意識に避けるのが普通だった。スペースは多くの場合区切り文字として認識されるので、ファイル名に使うと不味いことになること請け合いだからだ。
ところが最近はカジュアルにスペース文字をファイル名に使うようになってしまった。たぶんウインドウ表示とアイコンのせいだと思う。まあ理由はどうでもいいが、割と多くの場面で、普通にスペースや、そのほか多くのメタ文字がファイル名に使われるようになってしまった。
こういうとき、たとえば次のようなバッチがうまく動かなくなる。
for i in *.c; do
cp $i ${i/c/bak}
done
もしファイル名にスペースが入っていると、cpはスペースを区切りと見なしてバラバラにしてしまい、コピーがうまくいかなくなる。こういうバッチはいちいちクォートしなければならない。
for i in *.c; do
cp "$i" "${i/c/bak}"
done
ええ、面倒ですね。
さて、ls
だ。CentOS8で導入された新しいls
は、メタ文字を含むファイルがあるとシングルクォートで括るようになった。こんな感じだ。
さて、これはいったい何のための整形なのだろうか。
もし、先ほど示したバッチに組み込むときに不都合を生じないための工夫だと思うなら、実は違う。ls
は出力先がパイプやリダイレクトと見ると、クォート機能をoffにしてしまうのだ。したがってバッチで使うときは、結局自分でクォートするか、明示的にクォートオプション -Q
を指定するハメになる。
もしかしたら、マウスでドラッグして選択する場合には、役に立つかもしれない。ひょっとするとそのためのクォート機能なのだろうか。しかしls
がそんなことを気にして動作するとは、いくらなんでもあり得ないように思う。ではいったい、何のためのクォート機能なのだろうか。
まあとにかく、ls
にはこんなことはしてほしくなかった。これからいちいちCentOS8をインストールするたびに ls -N
とaliasを仕込むのは面倒で仕方がない。だが、そうせざるを得ない。Linuxというのは本当に厄介な文化だ。