UNIXコマンドの応用

コマンドの神髄は複数のコマンドを組み合わせて使うことにある。単独のコマンドでは大したことができなくても、組み合わせて使うことで多様な操作が実現できる。

標準入出力

UNIXコマンドの基本的な考え方として、標準入出力がある。コマンドに対する入力を標準入力(standard inputを略してstdinと称される)、コマンドの出力を標準出力(standard outputを略してstdout)と呼ぶ。図で表すと次のようになる:

標準入力と標準出力は何か、というと、特に指定しなければ「端末」である。コマンド操作のために使っている「ターミナル」アプリケーションは昔の「端末装置」をソフトウェアで再現したものであることを思い出して欲しい。「端末」にはキーボードと画面があり、それぞれが標準入力と標準出力に対応している。

標準出力は分かりやすいだろう。dateやcalなどのコマンドを実行すると画面に現在時刻やカレンダーがターミナルに表示される。つまり、ターミナルが標準出力ということだ。

標準入力については、これまで紹介したコマンドでは扱っていないが、例えばcatコマンドをファイル名を指定せずに実行してみよう。

% cat

するとプロンプトが返らず止まってしまったかのように見える。ここに、なにか適当な文字列を入力してReturnキーをタイプすると、同じ文字列が表示される。

% cat
hogehoge
hogehoge
▌

別の文字列を入力すると同じように表示される。これは、catコマンドの基本動作が、標準入力から受け取った文字列をそのまま標準出力に吐き出す、となっているからである。catコマンドの入力待ち状態を終了するにはControl+dをタイプする。

UNIXコマンドは、標準入力からデータを受け取り、標準出力に結果を出力する、という動作を基本としている。ただし、コマンドによっては受け付けるデータが無いものもあるし(例えばls、pwd)、結果を何も出力しないものもある(例えばcp、rm)。

catコマンドでファイル名を指定できるのは、標準入出力が基本であることを考慮すると「おまけ」的な機能とも言える。実用上はファイル名を指定する方が便利なのだが。

コマンドライン引数はコマンドにとっての標準入力とは異なる点に注意。標準入出力やコマンドライン引数がコマンド内でどのように扱われるかは、Cプログラムを学習していけば分かるだろう。というのも、UNIXはC言語で書かれているからだ。

リダイレクション

前章で標準入出力は「特に指定しなければ」端末である、と述べた。では指定する方法は?指定するとどうなる?

UNIXでは、標準入出力を端末ではなくファイルに変更することができる。これをリダイレクション redirection (方向転換、転送という意味)と呼ぶ。イメージとしては次の図のようになる。

UNIXでは接続された装置との入出力もファイルとして扱うので(デバイスファイルと呼ばれる)、装置への読み書きもリダイレクトで可能となる。

出力リダイレクション

コマンドの実行結果の出力先をターミナルではなくファイルに切り替えるには > を使う。

コマンド > ファイル名

例えば、lsコマンドの結果をファイルfilelist.txtに出力するには次のようにする。

% ls > filelist.txt

すると、ファイル名一覧が表示されずにプロンプトが返ってくる。

lsコマンドを実行してfilelist.txtというファイルが出来ていることを確認しよう。また、catコマンドでfilelist.txtの内容を表示してみよう。

ターミナル上で実行すると複数カラムにファイルを表示するが、リダイレクトすると1行に1ファイルずつが記録される。

もう一つ別の例をやってみよう。先ほどのcatコマンドの出力先をファイルにリダイレクトしてみる。すると、入力した文字はターミナルには表示されなくなる。catコマンドの実行を終了させるにはControl+d。

% cat > hoge.txt hogehoge %

hoge.txtの内容を表示させて確認しよう。

catコマンドはファイル名を与えて実行するとそのファイルの内容を表示するが、複数のファイルを指定すると連続して内容を表示する。

次のコマンドを実行して表示内容を確認せよ。

% date > date.txt
% cat date.txt filelist.txt

この2つめのcatコマンドの実行結果をファイルfiles.txtにリダイレクトして保存せよ。

catコマンドはconcatenateという名前の通り、本来はこのように複数のファイルをつなげるためのコマンドである。

> でのリダイレクション先に指定したファイルが既に存在すると、上書きして元の内容は消えてしまうことに注意。元の内容を消さずに、追記するには >> を使う。

コマンド >> ファイル名

次のコマンドを順に実行せよ。catコマンドの実行前に、表示(date.txtの内容)がどうなるか予想すること。

% cat date.txt
% date > date.txt
% cat date.txt
% date >> date.txt
% cat date.txt

入力リダイレクション

コマンドへの入力をキーボードから行うのではなく、ファイルから読み込むには < を使う。

コマンド < ファイル名

例えば簡易電卓コマンドbcは単独で実行するとキーボードから計算式を入力する使い方となるが、入力リダイレクションを使うとファイルに書かれた計算式の計算結果を表示させることができる。

次の2行の計算式が書かれているファイルcalc.txtをテキストエディタで作成し、bcコマンドに入力リダイレクトで与えて実行して結果を確認せよ。

calc.txtの内容:
1+2+3
2*3*4

コマンド:
% bc < calc.txt

もう一つ別の例を試してみよう。

まず次の内容のファイルnumbers1.txtをテキストエディタで作成せよ。

3 three
7 seven
4 four
1 one
9 nine

sortコマンドは標準入力から受け取った文字列を、行ごとに辞書順で(数字は小さい方から順に)並び替えるコマンドである。sortコマンドを単体で使うとcatコマンドと同様にキーボードから入力することになるが、入力リダイレクションを使えばファイルの内容を並び替えることができる。

次のコマンドを実行せよ。

% sort < numbers1.txt

この並べ替えた結果をsorted.txtに保存するにはどのようにすればよいか?考えてコマンドを実行せよ。
ヒント:入力リダイレクションと出力リダイレクションを一度に合わせて使う。

パイプライン

パイプラインを使うと、あるコマンドの実行結果を別のコマンドに直接引き渡すことができる。

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

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

これを図示すると次のようになる

このように描くと、filelist.txtというファイルはコマンド間の中継の役割しかしていないことがわかると思う。このような一時的に使うファイルは残しておく必要が無く、消しておかないと邪魔になるので少々面倒くさい。

そこで、あるコマンドの標準出力を直接次のコマンドの標準入力に渡す方法が用意されている。これをパイプライン(あるいは単にパイプ)と呼ぶ。コマンドを繋ぐには | (縦棒、フォントによっては真ん中に切れ目のある字体で表示される)を使う。

コマンド1 | コマンド2

先ほどのコマンド例は、パイプラインを使って次のように書ける。

% ls | sort -r

図示すると次のような、lsの標準出力がsortの標準入力にパイプでつなげられるイメージになる。

別の例で練習してみよう。

次の内容のファイルnumbers2.txtをテキストエディタで作成せよ。

6 six
2 two
8 eight
3 three
5 five

numbers1.txtとnumbers2.txtの内容をまぜて、昇順(小さい方から大きい方に増える順序)に並べ替えるコマンドを考えて実行せよ。結果は次のようになる。
ヒント:numbers1.txtとnumbers2.txtをまとめて内容表示させ、その出力をsortコマンドにパイプラインで渡す。catコマンドは引数に複数のファイル名をスペースで区切って指定すると、それらの内容を連続して表示する。

1 one
2 two
3 three
...(途中省略)
8 eight
9 nine

ワイルドカード

コマンドを使うときにファイル名を指定することがよくある。シェルでファイル名を入力するときにはTABキーによる補完機能を使えるが、それとは別の楽をする方法、あるいは一度に複数のファイル名を指定する方法を紹介しておこう。

シェルでファイル名を指定する時、次の文字は特殊な意味を持つ:

? 任意の1文字

* 0文字以上の任意の文字列

これらの記号はワイルドカードと呼ばれ、特定の文字の代わりになんでもよい文字という意味になる。

ワイルドカードとはポーカーなどのトランプゲームのルールで、好きなカードに解釈してよいというカードの名称。Jokerや2などがワイルドカードとしてよく使われる。

具体例を試してみよう。次のコマンドを実行するとたくさんのファイル名が表示される。

% ls /usr/bin

ところで、lsコマンドはディレクトリ名でなく、ファイル名を引数に与えると、そのファイルだけ表示する。例えば次のコマンドを試してみよう。

% ls /usr/bin/alias

?は1文字だけに化けることができる。例えば"a?"と書くと、aで始まって2文字のもの、という意味になる。次のコマンドを実行してみよう。

% ls /usr/bin/a?

?は途中に挟んでもよい。次のコマンドは最初がa、2文字目は任意、3文字目がtという意味になる。

% ls /usr/bin/a?t

*は0文字以上の何文字にでも化けられる。0文字というのは、無くてもよいということ。例えば、次のコマンドはaで始まるファイル名だけ表示させる。それぞれの表示されるファイル名で*がどのように解釈されているか考えてみよう。

% ls /usr/bin/a*

ワイルドカード*は0文字以上の文字列に化けることができるが、それ以外の文字は固定的なので、組み合わせて使うことでマッチする範囲を絞ることができる。

/usr/binの中の、asの2文字で始まるファイル名だけ表示させる指定方法を考え、実行して確認せよ。

/usr/binの中の、aで始まりtで終わるファイル名だけ表示させる指定方法を考え、実行して確認せよ。

/usr/binの中の、ファイル名に数字の2が含まれているファイル名だけ表示させる指定方法を考え、実行して確認せよ。
ヒント:2の前後は何でもよい。

複数のファイル名をまとめて指定できるので、例えば次のような使い方ができる。

例1. Emacsはファイルを編集すると、ファイル名の最後に~を付けたファイル名としてバックアップファイル(途中経過を保存しておくファイル)を作成するが、不要になったら次のコマンドで一括して削除できる。

% rm *~

ワイルドカードを使ってファイルを削除するときは、間違えて消してはいけないファイル名が含まれていないかよく注意すること。間違って rm * としてしまうとカレントディレクトリのファイルが全部消えてしまうことになる。お薦めとしては、まずlsを使って消したいファイルが正しく指定されているか確認し、ヒストリ機能をつかって同じコマンドを呼び出して、lsをrmに書き直すとよいだろう。

例2. .cの拡張子のファイルだけまとめて、archive.zipというファイルにzip形式で圧縮する。

% zip archive.zip *.c

numbers1.txtとnumbers2.txtをまとめて指定できるワイルドカードを使った表現を考え、それを使ってcatコマンドの引数に渡し、パイプラインでsortコマンドに渡して、内容を降順(大きい方から小さい方に減る順番)に並び替え、その結果をsorted.txtに保存するコマンドを作成し、実行して結果を確認せよ。

まとめ

標準入出力を扱うコマンドはフィルタとも呼ばれる。UNIXのコマンドは機能をできるだけ小さくし、リダイレクションやパイプラインを使って複数のコマンドを組み合わせて高度な処理を行うような思想で設計されている。また、ワイルドカードを使って複数のファイルを一度に指定することで、同じようなコマンドを何回も繰り返して実行する手間が省ける。FinderなどのGUIでファイル操作を行う際にはこのような複雑な処理をまとめて行わせることが難しいので、以上のコマンドの使い方も覚えておこう。

macOSにはAutomatorという、教えた手順を自動実行させるアプリケーションがある。使い方はちょっと難しいが、使いこなせれば強力なツールである。

更に高度なコマンドの扱い方として、シェルスクリプトという簡易プログラムがある。本演習では扱わないが、興味のある人は調べてみるとよいだろう。

本日の提出課題

encode.zipの中に、postal.datとjigyosyo.datというファイルがある。これらは全国の郵便番号データである。postal.datは通常の住所の郵便番号、jigyosyo.datは大口事業者の郵便番号である。これらのファイルのデータ量は大きい。postal.datをEmacsで開こうとすると本当に開いてよいか警告が出る(yesを選択すれば開けられるが)。moreコマンドで1ページ分ずつ表示させても巨大であることが実感できるだろう(moreコマンドの表示を終了させるにはqをタイプする)。

さて、これらのファイルに対してコマンドを使って以下の作業を行い、結果を下記の「提出方法」に従ってmoodleで報告せよ。

提出期限:次の土曜日の24:00まで

  1. 京都産業大学の郵便番号は6038555である。この郵便番号をこれらのデータ中から探しだし、含まれている1行を抜き出して記録せよ。

    なお、特定の文字列パタンが含まれている行を抜き出すコマンドはgrepがある。

    grep 文字列パタン ファイル名

    grepはフィルタコマンドとして設計されているので、ファイル名を指定する代わりにリダイレクションやパイプでデータを渡すこともできる。

    grep 文字列パタン < ファイル名
    cat ファイル名 | grep 文字列パタン

    抜き出した行を記録するには、grepコマンドの標準出力をリダイレクションしてファイルに保存すればよい。
  2. 自宅の郵便番号(寮生・下宿生は実家の郵便番号)で、1.と同様にして抜き出せ。
  3. 住所名や事業所名に「京都市」が含まれる郵便番号は全部でいくつあるか数えよ。postal.datとjigyosyo.datの両方をまとめて調べること。

    テキストファイルの行数を数えるコマンドにwcがある(word count)。wcにオプションとして-l (小文字のエル)を指定すると行数を返す。

    wc -l ファイル名

    wcもフィルタコマンドなので、次のようにもできる。

    wc -l < ファイル名
    cat ファイル名 | wc -l

    ヒント:まず「京都市」が含まれている行だけを抜き出すコマンドを考える。次に、その結果の行数を数えるコマンドを実行する。できれば途中経過をファイルに保存するのではなく、一発で結果が得られるコマンド列として組み立ててみよう(パイプは1段階に限らず、複数のコマンドを連結することができる)。
  4. 自分の氏名のそれぞれの文字(例えば「京産太郎」なら「京」「産」「太」「郎」の各文字)に対して、各文字が含まれている住所が「それぞれ」何件ずつあるかを調べて数えよ(事業所のデータでは住所ではなく会社名に調べる文字が含まれる行もそのまま数えて良い)。それぞれの文字に対して、両方のデータをまとめて調べること。次に、最も行数が多い文字について、その文字が含まれる行を郵便番号の降順(大きい方から小さい方)に並び替え、並び替えた結果の先頭5件分のみを記録せよ。

    指定した行数だけ表示させるにはheadコマンドを使う。オプションとして数字を指定すると、その数字の行数だけ抜き出す。5行だけ抜き出すなら、コマンドは次のいずれかになる。

    head -5 ファイル名
    head -5 < ファイル名
    cat ファイル名 | head -5

    コマンドの順序に注意!先に5行を抜き出してから並び替えるのではなく、並び替えてから5行を抜き出すこと。
    なお、留学生で日本の漢字で氏名を扱うことが難しい場合は、代わりに「京産花子」で調べること。

提出方法

以上の作業に使用したコマンドとその結果を1つのテキストファイルにまとめてmoodleで提出せよ。コマンドは必ず記載すること。コマンドが未記入の場合は課題を遂行したとは見なさないので注意。

提出ファイル名は「g2+学生番号.txt」とすること(つまり、アカウント名に.txtをつける)。例えば学生番号が12345なら、g212345.txtというファイル名になる。