2015年12月1日

『UNIXプログラミング環境』演習問題の解答(第1章)まとめ

※本記事は、"『UNIXプログラミング環境』 Advent Calendar 2015" (http://qiita.com/advent-calendar/2015/unix_prog_env) の1日目の記事になります。


しかし、1989年出版(原版は1983年)と20年以上も前の書籍故か、この本について記載されているWebサイトは、あまり見当たらなかった。
ことさら、演習問題の模範解答に関しては、(私が探した限りでは)一切見つからない状況であった。
そのため、私自身のUnixに関する勉強も兼ねて、本書籍の演習問題の解答を作成してみることにした。


実行環境

  • Arch Linux(x86_64, x86_64, 4.2.5-1-ARCH)

  • dash(0.5.8-1)

  • stty(GNU coreutils, 8.24)

第1章の内容

第1章 初心者のためのUNIX
1.1 はじめてみよう
1.2 日々の利用―ファイルおよびよく使われるコマンド
1.3 ファイルについて―ディレクトリ
1.4 シェル
1.5 UNIXシステムのその他の機能

演習上の注意点

『UNIXプログラミング環境』は、UNIX System V、もしくは4.1BSDでの実習を想定している。
これらの環境では、プロンプト上の1文字削除・1行削除のキー入力は、それぞれ'#'と'@'とされていた(現代では'^H’と'^U')。
また、これらのキー入力は、入力確定時に反映される仕様となっているらしい。
しかし、今回の演習環境では、1文字削除・1行削除のキー入力は、入力した時点でプロンプト上に反映されてしまうため、一部の演習問題は実行できなかった。

ただし、キー設定に関しては、sttyコマンドで設定をおこなえば、再現可能である。
具体的には、プロンプト上で

$ stty erase '#' kill '@'

を実行すれば良い。(zshのみ、sttyで設定しても再現できなかった。)

だが、プロンプトへ反映されるタイミングに関しては、入力確定時ではなく、即時おこなわれるままである。
そのため、演習で想定されている環境を完全再現することは出来なかった。

本記事では、想定される環境を再現できない場合、予想される実行結果を記述することにする。

問題1-1

次のようにタイプするとどうなるか説明せよ。

$ date\@

"date@: not found"の文字列が出力される。

バックスラッシュエスケープによって、'@'が行削除文字ではなく、単なる文字'@'として解釈される。
そのため、入力は"date@"と判別され、コマンド"date@"が実行される。
しかし、"date@"というコマンドは、$PATH内には存在しないため、実際には実行不可能である。
したがって、コマンドが見つからなかった旨のメッセージ、"date@: not found"が出力される。

問題1-2

大部分のシェルは(第7版のシェルでは異なるが)#をコメントの始まりだと解釈する。 そして#からその行の終わりまでのテキストをすべて無視する。 この場合、1文字削除のための文字が同じ#と仮定したときに、次のようなやりとりについて説明せよ。

$ date
Mon Sep 26 12:39:56 EDT 1983
$ #date
Mon Sep 26 12:40:21 EDT 1983
$\#date
$\\#date
#date: not found
$

1. $ date
2. $ #date
3. $\#date
4. $\\#date

まず、1の説明。 これは単に"date"コマンドを実行し、その実行結果が出力されただけである。

次に、2の説明。 この入力に付加されている'#'は、1文字削除の'#'として解釈される。 しかし'#'は、入力の先頭に付加されているため、実際は何も削除されない。 そのため、残った"date"の文字列がコマンドとして実行され、その実行結果が出力されている。

続いて、3の説明。 この入力に付加されている'#'は、直前に入力されたバックスラッシュによって、1文字削除としての特別な意味を失っている。 そのため、'#'は、コメントの始まりの'#'として解釈される。 そして'#'は、コマンド"date"の頭に入力されており、"date"という入力全てが、コメントとして解釈される。 以上の理由により、入力全体がコメントとして解釈されるため、何も実行されていない(何も出力されない)。

最後に、4の説明。 この入力に付加されている'#'は、直前に入力されたバックスラッシュによって、1文字削除としての特別な意味を失っている。 更に、バックスラッシュは2回入力されているため、コメントの始まりとしての'#'という意味も失っており、単なる文字'#'として解釈される。 そのため、この入力は"#date"というコマンドとして実行される。 しかし、"#date"というコマンドは$PATH内には存在しないため、コマンドが見つからなかった旨のメッセージ、"#date: not found"が出力された。

問題1-3

次のコマンドを実行して、表示された指示に従って進め。仕事以外のときにやるとさらに面白いかもしれない。

$ ls /usr/games

私の環境では、/usr/games/は存在しないため、指示されたコマンドの内容は確認できなかった。 ただし、*BSDでは、/usr/games/は存在する。 このディレクトリの中身を見る限りだと、quiz(6)の出題内容が格納されていると思われる。

問題1-4

次のコマンドの違いは何か?

$ ls junk       $ echo junk
$ ls /          $ echo /
$ ls            $ echo
$ ls *          $ echo *
$ ls '*'        $ echo '*'

1. $ ls junk    $ echo junk
2. $ ls /       $ echo /
3. $ ls         $ echo
4. $ ls *       $ echo *
5. $ ls '*'     $ echo '*'

次に、各入力行を実行した際の出力結果と、2つのコマンドの違いを示す。

1. $ ls junk $ echo junk

$ ls junk
erogazou.jpg
homovideo.mpg
oppai.txt
$ echo junk
junk

"ls junk"では、カレントディレクトリ内に存在するディレクトリ、junkの中にあるファイルリストを出力している。
"echo junk"では、単に"junk"という文字列を出力している。

2. $ ls / $ echo /

$ ls /
bin
boot
dev
etc
home
... (省略)
$ echo /
/

"ls /"では、ルートディレクトリ'/'のファイルリストを出力している。
"echo /"では、単に'/'という文字を出力している。

3. $ ls $ echo

$ ls
4.4BSD_document
Dropbox
VirtualBox VMs
bin
books
documents
dotfiles
downloads
game
... (省略)
$ echo

"ls"では、カレントディレクトリのファイルリストを出力している。
"echo"では、空の文字列(正確にはechoコマンドによって付加される改行コード)を出力している。

4. $ ls * $ echo *

$ ls *
4.4BSD_document:
articles
books

Dropbox:
Camera Uploads
Public
Share
animal_photo
hostname_list.txt
pdf_docs
prog
tmp
todo
wallpaper

VirtualBox VMs:
Debian
... (省略)
$ echo *
4.4BSD_document Dropbox VirtualBox VMs bin books dotfiles documents downloads game iso music pdf_docs src tmp work

まず、コマンドが実行される前に、引数として与えられている'*'は、シェルによってワイルドカード文字として処理される。
具体的には、カレントディレクトリに存在する全ファイル・ディレクトリに展開される。

そのため、lsコマンドでは、カレントディレクトリ内の全ファイル・ディレクトリが引数として渡され、実行される。
よって、カレントディレクトリにある全ファイルのリストや、全ディレクトリの中にあるファイルリストが出力された。
同様に、echoコマンドでも、カレントディレクトリ内の全ファイル・ディレクトリが引数として渡され、実行される。
ゆえに、カレントディレクトリの全ファイル・ディレクトリをスペースで区切った文字列、結果的にファイルリストが出力された。

5. $ ls '' $ echo ''

$ ls '*'
ls: cannot access *: No such file or directory
$ echo '*'
*

引数として与えられている文字は、4.と同様に'*'である。
しかし、上記のコマンドの場合、'*'はシングルクォートで囲まれているため、シェルによって展開処理がおこなわれず、単なる文字'*'としてコマンドに渡されている。

そのため、lsコマンドでは、'*'という名前のファイル・ディレクトリとして、lsコマンドに処理されている。 ここで、'*'というファイル・ディレクトリはカレントディレクトリ内には存在しないため、エラーメッセージ"cannnot access *: No such file or directory"が出力された。
一方、echoコマンドでは、単なる文字'*'が引数として渡されたため、文字'*'が出力された。

lsとechoコマンドの違いは、引数として与えられたディレクトリ内のファイルリストを出力するか、文字列を出力するかだけである。

問題1-5

次のようにすると、名前のリストのなかにls.outも含まれる理由を説明せよ。

$ ls >ls.out

リダイレクトによって、コマンド実行前にls.outが生成されるためである。
リダイレクトは、コマンドを実行する前に処理される。 そのため、上記コマンドを処理するにあたり、まずls.outが生成される。 ls.outが生成された後、lsコマンドが実行されるため、lsコマンドの実行結果の出力リダイレクト先、ls.outの中にls.out自身も含まれている。

問題1-6

次のようにしたときの結果について説明せよ。

$ wc temp >temp

もし以下のようにコマンド名のスペルを間違ったら何が起こるか?

$ woh >temp

1. $ wc temp >temp
2. $ woh >temp

次に、tempファイルの中身を確認しながら、上記コマンドを実行した結果を示す。

$ cat temp
hogehoge
$ wc temp >temp
$ cat temp
0 0 0 temp
$ woh >temp
dash: 1: woh: not found
$ cat temp
$

1.では、tempファイルに対してwcコマンドを実行した結果をtempファイルへ上書きする、といった処理結果を期待したものと推測される。
しかし、問題1-5でも述べたように、コマンドの実行はリダイレクトによるファイルアクセスの後に処理される。
そのため、wcコマンドが実行される前に、リダイレクト処理によってtempファイルの中身が空にされている。
よって、空(0バイト)のtempファイルに対してwcコマンドを実行した結果が、tempファイルに書き込まれている。

2.では、存在しないコマンドを実行しているため、シェルによるエラーメッセージ"dash: 1: woh: not found"が出力された。
しかし、エラーメッセージは、通常は標準エラー出力へと出力される。
そして、ファイルディスクリプタの指定をしない限り、リダイレクトは標準出力のみをリダイレクションする。
そのため、存在しないコマンドを実行した標準出力の結果(何も出力されていない)が、tempファイルに書き込まれている。

問題1-7

次の2つの違いを説明せよ。

$ who | sort
$ who >sort

1. $ who | sort
2. $ who >sort

まず、各入力行を実行した際の、出力結果を示す。

$ who | sort
gin      pts/2        2015-12-01 17:23 (tmux(6259).%9)
gin      pts/3        2015-12-01 17:23 (tmux(6259).%10)
gin      pts/4        2015-12-01 18:44 (tmux(6259).%27)
gin      pts/5        2015-12-01 17:23 (tmux(6259).%12)
gin      pts/6        2015-12-01 17:25 (tmux(6259).%16)
gin      pts/9        2015-12-01 18:44 (tmux(6259).%28)
root     tty1         2015-12-01 18:45
$ who >sort
$ cat ./sort
root     tty1         2015-12-01 18:45
gin      pts/2        2015-12-01 17:23 (tmux(6259).%9)
gin      pts/3        2015-12-01 17:23 (tmux(6259).%10)
gin      pts/4        2015-12-01 18:44 (tmux(6259).%27)
gin      pts/5        2015-12-01 17:23 (tmux(6259).%12)
gin      pts/9        2015-12-01 18:44 (tmux(6259).%28)
gin      pts/6        2015-12-01 17:25 (tmux(6259).%16)

これら2つの違いは、パイプ処理をおこなっているか、リダイレクト処理をおこなっているかである。

1.では、whoコマンドによって出力されたログインユーザリストを、パイプによってsortコマンドに渡しており、最終的にsortコマンドによって整列されたログインユーザリストが出力されている。
2.では、whoコマンドによって出力されたログインユーザリストを、リダイレクトによってsortというファイルに出力している。


参考書籍

  • 『UNIXプログラミング環境』、Brian W. Kernighan, Rob Pike著、石田 晴久 監訳、アスキー出版局

雑記

  • 演習問題の解答がどこにも見つからなければ、僕が作ればいいじゃないという発想。
    → ただ、僕が作成した解答が、本当に正しいのかどうかの自信が無い…​

  • パイプやリダイレクトを含んだコマンドを実行した場合、パイプ・リダイレクト・プロセス生成・コマンド実行がどの順番で処理されるのか、明確な根拠が分からない…​
    → このあたりを探して、ソースを読んでみるのが確実かもしれない。 http://minnie.tuhs.org/cgi-bin/utree.pl

  • 書籍に記載されている演習問題を記事にする際、問題文って完全に引用しても良いのかな?
    → 問題文があった方が、解答を検証してもらいやすくなるし、便利な気がするけれど…​ マズかったら削除しておこう。

  • このあたりを参考にすれば、もしかしたら4.1 BSDの演習環境を作れるかもしれない。この書籍の本質じゃないけど…​
    → "corbieのブログ:SIMH - 4.1BSDを動かす (1)" http://blog.livedoor.jp/corbie/archives/4068872.html

  • なんとかAdvent Calendar 1日目に間に合った…​w
    → 先週末、Killing Floor 2ばかりヤッているんじゃなかった(^q^)

  • 第2章、次のAdvent Calendarの記事は、今週中には投稿する…​予定。

Tags: Shell *nix
このエントリーをはてなブックマークに追加