Mukai Systems

Parenist養成ゼミ:プログラムと入出力

この記事は、Parenist養成ゼミシリーズの記事である。

今回は、受講者のパイプやリダイレクトの理解が怪しかったので、初学者を対象に講習を行った。

プログラムとは何か

プログラムとは、本質的には入力を受け取って出力を行うものである。

入力 -> プログラム -> 出力

入力も出力も行わないプログラムも考えることができるかもしれないが、これは、ただ単により大きな系で考えた際にエントロピーを増大させる程度の意味しかない。

$ cat cool-heater.p
(while true) ; what a cool heater!
$ paren cool-heater # hot.

標準入出力 - standard I/O

プログラムにとって最も馴染み深い入出力は標準入出力である。

標準入力 -- stdin(standard input)
キーボードからの入力
標準出力 -- stdout(standard output)
端末への出力

プログラムと標準入出力

例えば、ls(list directory)は、カレントディレクトリのファイルを列挙するプログラムである。

$ ls
head1.p  stdio.html  stdio.md  write-args.p

これは、入力なしに出力を生み出すことにほかならない。

ls -> 標準出力

標準入力とは別に、プログラムには実行時に値を指定できる。この引数のことをコマンドライン引数という。コマンドライン引数は以下のように指定する。

$ program arg1 arg2 ...

lsにコマンドライン引数-aを与えると、.から始まるファイルも対象となる。このような、-から始まる引数のことはオプションと呼び分けることが多いが、詳細は割愛する。

$ls -a
.  ..  head1.p  stdio.html  stdio.md  write-args.p

cat(concatenate)は標準入力を標準出力に複写するプログラムである。

標準入力 -> cat -> 標準出力
$ cat
a
a
bcd
bcd
^C

catにコマンドライン引数を渡すと、各引数をファイル名と解釈し、それぞれを中身を標準出力に出力する。

$ cat a.txt
a
$ cat b.txt
b
$ cat a.txt b.txt
a
b

リダイレクトとパイプ

リダイレクト

あるプログラムの標準出力をファイルに保存することができる。この機能のことをリダイレクトという。

標準入力 -> プログラム -> ファイル

リダイレクトは、>の後に、保存するファイルを指定して使用する。

$ cat a.txt > b.txt

パイプ

あるプログラムの標準出力を、別のプログラムの入力に与える機能がある。この機能のことをパイプという。

標準入力 -> プログラム0 -> プログラム1 -> ... -> 標準出力

パイプは、|の後に、出力を入力としたいプログラムを指定して使用する。

$ cat 100.txt
0
1
...
99
100
$ cat 100.txt | head
0
1
3
4
5
6
7
8
9

組合せの力

Parenをcompileできる環境の場合、GNU Coreutilsというプログラム群が使用できる。

個々のプログラムの用途は限定的であっても、組み合わせによって莫大な生産性を生み出す。

カレントディレクトリのファイル数を出力
$ ls -l | wc -l
write-args.pの中身を行数付きで出力
$ cat write-args.p | nl
a.txtの中身を辞書順にソーティングし、重複した行を取り除いてuniq.a.txtに保存する。
$ cat a.txt | sort | uniq > uniq.a.txt
100回Paren is coolと出力する
$ yes Paren is cool | head -n 100

Coreutilsのプログラムは、以下のようにしてそのプログラム自身のマニュアルを表示できる。

$ program --help

Parenと標準入出力

Parenにはコマンドライン引数を与える事ができるが、その有無によって動作の違いがある。

コマンドライン引数がない場合、REPL(Read-Eval-Print-Loop)が起動する。これは、ちょっとしたプログラムの動作確認するのに便利である。

$ paren

一方で、コマンドライン引数がある場合、引数をプログラムが記述されたファイルと見做して、上から順に読み込み、そして評価する。

$ paren file

さらに、二つ以上のコマンドライン引数がある場合は、fileの中にmain関数が定義されていた場合に引数として受け渡すことができる。

$ paren file arg1 arg2 ...

以下のファイルは、コマンドライン引数を出力するプログラムである。拡張子がなくても、.pが暗黙的に探索されることに注意されたい。

$ cat write-args.p
(function! main (args)
    (write args))
$ paren write-args
nil
$ paren write-args 1 2 3
("1" "2" "3")
$ paren write-args hello world
("hello" "world")

Parenには(defaultでは標準)入出力を行うための関数群が定義されている。それぞれの対称性に注意されたい。

read 
    read -- read S-expression.
    read-byte -- read 1 byte.
    read-bytes -- reads the specified length or all the rest.
    read-char -- read 1 character.
    read-line -- read one line.
write
    write -- write in a format that can be read by read function.
    write-byte -- write 1byte.
    write-bytes -- write bytes-like object.
    write-line -- write bytes-like object and new line.

例えば、標準入力から一行読み込んで標準出力に一行出力するプログラムは以下のようになるだろう。

$ cat head1.p
(function! main (args)
  (write-line (read-line)))
$ yes Paren is cool | paren head1
Paren is cool

課題

  1. 標準エラー出力について調べよ。
  2. 標準入力へのリダイレクト<と追記モードのリダイレクト>>について調べよ。
  3. 例示したCoreutilsのプログラムにコマンドライン引数--helpを与えて実行せよ。

    1. オプションについて調べよ。
    2. 興味を持ったGNU Coreutilsのプログラムを実行せよ。
  4. 各行が、現在の行を表すような、100行からなるファイルをVimで作成せよ。
  5. 先のファイルをseqコマンドとリダイレクトを用いて作成せよ。
  6. WindowsユーザはDOS Batch、Mac利用者はBash Scriptについて調べよ。

    1. Paren is coolと出力するスクリプトを作成せよ。
    2. コマンドライン引数で出力する回数を指定できるように修正せよ。
  7. 独立した二つの処理a、処理bを行うプログラムABを作成することと、処理aを行うプログラムAと処理bを行うプログラムBを作成することとどちらが望ましいか。
  8. head1.pを参考に、コマンドライン引数で出力する行数を指定できるheadn.pを作成せよ。
  9. $paren-home/tools/coreutilsには、Coreutilsの模倣プログラムがある。いくつか動作を確認せよ。
  10. 確認したプログラムのマニュアルを確認せよ。マニュアルはparen man catなどのように実行する。