パイプラインとリダイレクション

Unixコマンドの良い所は、単純なコマンドを幾つも組み合わせて、複雑な処理を実現できるところにあります。そのために用意されているパイプライン、リダイレクション、といった機能について説明します。

標準入出力

Unixにおける各コマンド(=プログラム)は「標準入出力」と呼ばれる入力・出力のインタフェイスが用意されています。標準入力インタフェイスは「 stdin 」、標準出力インタフェイスは「 stdout 」と呼ばれます。ここでは以下のように図示します。

image-20240310143537353

プログラム実行時、特に指定しなければ stdinにはキーボードが接続されています(接続されているように見えるように設定されています)。stdoutにはディスプレイが接続されています。

たとえば電卓機能を持った bc コマンドも、その実行プログラムには標準入力と標準出力が備わっており、stdinに接続されたキーボードから計算式や quit コマンドを受け取り、stdoutに接続されたディスプレイに結果を表示しています。

image-20240310143613400

bcの動作を標準入出力を意識して詳しく見ると、以下のように解釈できるはずです。

% bc                     <<< プログラムを起動
>>> 1 + 1                <<< キーボードを操作して、標準入力に「 1 + 1 」という文字列を与えた
2                        <<< 計算結果である「 2 」が標準出力に出てた結果、ディスプレイに表示された
>>> quit                 <<< キーボードを操作して、標準入力に「 quit 」という文字列を与えた
%                        <<< (標準出力=ディスプレイには何も出さずに)実行を終了した

なお、date コマンドでも stdin はありますし、そこにはキーボードが接続されているでしょうが、ただ date コマンドは stdin からの入力を見ずに動作しています。

ターミナル
あなた方がコマンドを実行している時、標準入出力はそれぞれ「ターミナル・アプリケーションへのキー入力」と「ターミナル・アプリケーション画面への出力」に接続されているように見えるでしょう。

リダイレクション

前章で「特に指定しなければ」標準入出力はキーボードとディスプレイが接続されている、と説明しました。Unix ではコマンド実行時に標準入出力をキーボードやディスプレイからファイルに変更することができます。これを「リダイレクション( redirection - 方向転換、転送)」と呼びます。以下に図示します。

image-20240310145458517

標準入力のリダイレクションは入力リダイレクション、標準出力のそれは出力リダイレクションと言います。

「 > 」による出力リダイレクション

出力の方が直感的なので、先に出力リダイレクションの例を出します。

コマンドの実行結果の出力先を(ディスプレイではなく)ファイルに切り替えるには「 > 」記号を使います。

% コマンド > 出力先ファイル名

つまり「 ls > filelist.txt 」のように書いて実行します。以下にその結果を示します。

yasuda@Lily images % ls                             <<< いつものようにディスプレイに出力
KSU.jpg                 icon02.png    icon05.png
confectioneries.jpg     icon03.png    icon06.png
icon01.png              icon04.png    leaves.jpg
yasuda@Lily images % ls > filelist.txt              <<< 出力先を filelist.txt ファイルに設定
yasuda@Lily images % ls                             <<< 再び ls を実行
KSU.jpg                 icon02.png    icon06.png
confectioneries.jpg     icon03.png    leaves.jpg
filelist.txt            icon04.png                  <<< filelist.txt ファイルが出来ている!
icon01.png              icon05.png
yasuda@Lily images % cat filelist.txt               <<< ファイルの中身を確認
KSU.jpg                                             <<< 結果が(一行一件の形式で)保存されていた
confectioneries.jpg
filelist.txt
icon01.png
icon02.png
icon03.png
icon04.png
icon05.png
icon06.png
leaves.jpg
yasuda@Lily images % 

注意1. ls の結果が異なる

通常、ターミナルで ls コマンドを実行すると、出力表示は「マルチカラム」と呼ばれる形になります。以下に例を示します。

% ls
KSU.jpg                 icon02.png    icon05.png
confectioneries.jpg     icon03.png    icon06.png
icon01.png              icon04.png    leaves.jpg
%

ところが出力リダイレクションを設定すると(後述のパイプへの出力でも同じ)ls コマンドはその出力を「 -1 (数字の 1 )」オプションを付けたときと同じ体裁、つまり「一行一ファイル」の形で出力します。以下に -1 オプションを付けた出力例を示します。

% ls -1 filelist.txt               <<< -1 オプションを付けた
KSU.jpg                            <<< 結果が(一行一件の形式で)出力される
confectioneries.jpg
filelist.txt
icon01.png
icon02.png
icon03.png
icon04.png
icon05.png
icon06.png
leaves.jpg
%

標準出力がディスプレイに設定されている時の ls はマルチカラム、出力リダイレクション(およびパイプへの出力)の場合は一行一件での出力がデフォルトとなります。

注意2. 上書き挙動

ところで「 > 」によるリダイレクションは出力先を常に上書きします。たとえばこの状態で date コマンドの出力を同じ filelist.txt に出力すると、その中身は一旦消され、date の出力だけになります。

yasuda@Lily images % date > filelist.txt          <<< filelist.txt に date の結果を書き込む
yasuda@Lily images % cat filelist.txt             <<< 中身を確認
2024年 3月10日 日曜日 15時15分06秒 JST               <<< 元の結果は消え、日付の一行になった
yasuda@Lily images % 

ここで再び date > filelist.txt とすると、同様に今の結果に書き換えられます。

yasuda@Lily images % date > filelist.txt          <<< 再度実行
yasuda@Lily images % cat filelist.txt             <<< 中身を確認
2024年 3月10日 日曜日 15時15分22秒 JST               <<< 元の結果(15時15分06秒)では無くなった
yasuda@Lily images % date > filelist.txt          <<< またもや実行
yasuda@Lily images % cat filelist.txt             <<< 中身を確認
2024年 3月10日 日曜日 15時15分25秒 JST               <<< 元の結果(15時15分22秒)では無くなった
yasuda@Lily images % 

新しい結果で完全に上書きされている(元のデータは消えてしまう)ことが分かるでしょうか。

「 >> 」による追記型の出力リダイレクション

上書きではなく、元の結果に追記していくことも出来ます。そのためには「 >> 」記号を使います。

% コマンド >> 出力先ファイル名

つまり「 date >> filelist.txt 」のように書いて実行します。以下にその結果を示します。

yasuda@Lily images % date >> filelist.txt       <<< 上書き指定でリダイレクション
yasuda@Lily images % cat filelist.txt           <<< 結果を確認
2024年 3月10日 日曜日 15時15分25秒 JST             <<< 元の結果(15時15分25秒)を残して、
2024年 3月10日 日曜日 15時20分19秒 JST                 次の行に今の結果を追記した
yasuda@Lily images % date >> filelist.txt       <<< 何度か追記指定で実行
yasuda@Lily images % date >> filelist.txt
yasuda@Lily images % date >> filelist.txt
yasuda@Lily images % cat filelist.txt           <<< 結果を確認
2024年 3月10日 日曜日 15時15分25秒 JST             <<< 元の結果が残っている
2024年 3月10日 日曜日 15時20分19秒 JST
2024年 3月10日 日曜日 15時20分24秒 JST             <<< ここから新しく3回分の結果が追記されている
2024年 3月10日 日曜日 15時20分25秒 JST
2024年 3月10日 日曜日 15時20分26秒 JST
yasuda@Lily images % 

後始末を忘れずに

この作業の結果「 filelist.txt 」というファイルが出来てしまうので、動作確認が済めば「 % rm filelist.txt 」などとして作成したファイルを削除しておくことを勧めます。

確認課題1.

ファイル一覧の記録をバックアップデータとして残しておく、という状況を想像して下さい。以下の操作を行います。

その結果、作成したファイルの中身は以下のようになりました。

yasuda@Lily % date > backups/filelist20240310.txt      <<< 現在日付を書き込み
yasuda@Lily % pwd >> backups/filelist20240310.txt      <<< 作業ディレクトリをで追記
yasuda@Lily % ls -l >> backups/filelist20240310.txt    <<< ファイル一覧を追記
yasuda@Lily % cat -n backups/filelist20240310.txt      <<< cat -n で行番号をつけて内容を確認
     1  2024年 3月10日 日曜日 15時45分27秒 JST
     2  /Users/yasuda/Documents/kisopa
     3  total 7128
     4  drwxr-xr-x@ 6 yasuda  staff      192  3  8 21:24 CommandMore.assets
     5  -rw-r--r--@ 1 yasuda  staff   700551  3  9 22:45 CommandMore.html
     6  -rw-r--r--@ 1 yasuda  staff    14641  3 10 13:58 CommandMore.md
(以下略)

各行の操作と結果の内容に納得できるでしょうか?受講生の皆さんも、同様の作業をして、出来上がったテキストファイルをMoodleに提出してください。

入力リダイレクション

「 < 」による入力リダイレクション

コマンドへの入力を(キーボードからではなく)ファイルから読み込ませるには「 < 」記号を使います。

% コマンド < 入力元ファイル名

つまり「 bc < calc_in.txt 」のように書いて実行します。以下にその結果を示します。

yasuda@Lily % cat calc_in.txt             <<< 与える予定のファイルの中身を確認
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10    <<< 計算指示一行目(1から10までの和)
2^32                                                 二行目(2の32乗)
yasuda@Lily % bc < calc_in.txt            <<< 入力をファイルからとする
55                                        <<< 結果一行め
4294967296                                       二行目
yasuda@Lily %                             <<< 自動的に終了する

bc がファイルの内容を一行ずつ処理して表示し、入力のすべてを処理し終わったら自動的に実行終了したことが分かるでしょうか。

もちろん、「 bc < calc_in.txt > calc_out.txt 」のように入力・出力のリダイレクションを合わせて使うこともできます。

End of File の入力による終了

なお、最後に quit と書いてもその指示通り終了しますが、ここでは省略しています。つまり bc は quit コマンドでも終了しますが、標準入力からの入力が尽きた場合も終了します。この状況を「入力が End of File に達した」と表現することもあります。なお End of File を EOF と略す場合もあります。

そしてキーボードからもこの EOF を入力することは可能です。Control - D (Control キーを押しながら英字の d キーを押す)がそれに当たります。

yasuda@Lily % bc             <<< bc を起動した
>>> 1 + 1                    <<< キーボードから 1 + 1 を入力して計算させた
2
>>> ^D                       <<< Control - D を入力すると、画面に ^D と表示されたうえで、
yasuda@Lily %                <<< bc は自動的に終了した

パイプライン

Unix では、あるコマンドの実行結果を別のコマンドに直接引き渡すことができます。これを「コマンド・パイプライン」と呼びます。

例えば、ls コマンドで得られるファイルの一覧は辞書順(アルファベット順)となっていますが、これを逆順にしてみます。sort コマンドはオプションに -r を指定すると辞書の逆順(降順)に並び替えるので、リダイレクションを使って次のようにすれば逆順のファイル一覧が得られます。

% ls > filelist.txt
% sort -r < filelist.txt

この状況を図示します。

image-20240310164221038

ところでこの処理では中間のファイルはデータの受け渡し、それも標準出力と標準入力の中継のためにしか使われていません。一時ファイルは消し忘れたり面倒な事が多く、不便です。

そこで、あるコマンドの標準出力を直接次のコマンドの標準入力に渡す方法が用意されています。これをパイプライン(あるいは単にパイプ)と呼びます。パイプには「 | 」記号を使います。(縦棒記号、ただしフォントによっては真ん中に切れ目のある字体で表示される場合があります。)

% コマンド1 | コマンド2

先のコマンド例は、パイプを使って次のように書けます。

% ls | sort -r

以下に図示します。ls の標準出力が sort の標準入力にパイプで接続されていることを意識してください。

image-20240310170045126

確認課題2. ファイルサイズの大きなもの上位 5 つを出す

作業ディレクトリの中で、ファイルサイズが大きなものを抜き出すためのコマンド列を作ってみます。要素として使えそうな(部品となる)コマンドは以下の三つでしょうか。

これまで通り図にするとこんな形でしょうか。

image-20240310210544427

以下に実行結果の例を示します。

% ls -s | sort -n | tail -5
  32 PipeRedirection.md
 448 PipeRedirection.html
1376 CommandMore.html
1552 Prepare.html
4104 MacOSView.html
%

各行の数字はファイルのサイズ、その横がファイル名です。ファイルサイズの大きなもの上位 5 つが昇順(小さいもの順)に表示されています。

課題としてやるべき作業について以下に示します。

step 0: 作業ディレクトリを決める。

自分のホームディレクトリ以下で、上のようにファイルサイズ上位5つが出せるところを見つけてください。ディレクトリではないファイルが 5 つ以上含まれているディレクトリが良いですね。

step 1: 各コマンドの動作を確認するために、段階を追って実行し、結果を見比べる。

ls -s 」「 ls -s | sort -n 」「 ls -s | sort -n | tail -5 」と少しずつパイプを延ばしながら試して、それぞれのコマンドがどのように機能しているか確認してください。

step 2: この手順から「 total 120 」といった行を取り除く。

実は上に出していた手順は不完全です。つまり「 ls -s 」はその出力の先頭に「 total xxx 」といった情報(実はファイルサイズの合計値)を出しています。ファイルが 5 つ未満しかないディレクトリで実行すると以下のようにありがたくない表示結果になってしまいます。

% ls -s | sort -n | tail -5
  6 samples
total 272
  8 test.txt
 64 HIreview.rtf
200 3dlens.png
%

これを出力の過程で取り除き、以下のようにスッキリした表示になるようにしましょう。

  0 empty.txt
  6 samples
  8 test.txt
 64 HIreview.rtf
200 3dlens.png

さまざまな方法があるでしょうが、たとえば「 tail +10 」とすると「前から10行目からあとの行だけを出力する(9行目までは捨てる)」ことができます。「 total 」という表示は常に「 ls -s 」の最初の一行に出ますから、「 tail +2 」をパイプライン(の ls の直後)に追加すれば良いのです。試して下さい。

step 3: ファイルサイズの大きなもの上位 5 つを「降順(大きいもの順)」に並べる。

つまり、以下のような結果になるようにしましょう。

% ls -s | (コマンド記述は伏せておきます)
4104 MacOSView.html
1552 Prepare.html
1376 CommandMore.html
 448 PipeRedirection.html
  40 PipeRedirection.md
%

以下のようにすれば良いでしょう。

うまくできれば、ターミナル上に表示されている実行結果を(スクリーンショットではなくテキスト形式で) Moodle に提出してください。つまりターミナル上のコマンド列、実行結果(5行?)と、コマンド実行後のコマンドプロンプトの行をコピーして、テキストエディタに貼り付けてファイル保存して出してくれれば良いかと。

テキストエディタ貼り付け時の注意

テキストエディタを起動すると下図左側の「リッチテキスト形式モード」で動作しています。今回は下図右側の標準テキスト形式モードに切り替えて、テキスト形式に保存してください。

image-20240501151135216

この二つのモードについては「テキストエディタ」にもう少し詳しい説明があります。

フィルタコマンド

上の確認課題2. を見ていると、sort や tail が、パイプラインの「上流」にいる ls コマンドの結果を「加工」して「下流」に出していることが分かるでしょうか。

このように標準入力から得たデータを加工して標準出力に流すタイプのコマンドのことを「フィルタコマンド」と呼びます。フィルターのように働いている事が把握できるでしょうか。

Unix以外のシステムにおけるコマンド
Unixコマンドはそれより前のOSのコマンド群に比べて、出力表示が簡素な場合が多いです。たいていの人は ls -l などは恐らく一番上の行に、このカラムが何を意味するのかラベルを出して欲しくなるでしょうし、実際ほかのOSではそうした見出し行がついているのが普通です。しかしターミナルの画面表示で「みやすく」整理されたり補助情報が出ていると、フィルタコマンドで加工するにはかえって不都合です。コマンドのプログラムも複雑になるしメモリも多く必要になる。それよりシンプルなプログラムで、シンプルな出力を出す方が良い。Unixはそういう設計ポリシーなんですね。