UNIX系OS新人に捧げるテキストデータ処理への手引

約25年前、LINUX JAPAN誌の原稿応募に募集してボツった文章です...
## 要旨
昨今、巷にはWindowsパソコンが氾濫し多くの人がワープロや表計算ソフトを利用して様々のデータを蓄積しています。しかし多くのユーザはそのデータが各ソフト独自の形式で保存されているが為に固有のアプリケーションでしか扱う事が出来ない事にジレンマを感じているのではないでしょうか。一方、UNIX系のOSでは歴史的にテキストファイルでのデータ管理が一般的で有った為、一種類のデータを様々なアプリケーション(コマンド)で処理する事が可能となっており、その為のコマンド類も数多く揃っています。今回はそれらのコマンドの内いくつか使って便利と思われる物を紹介したいと思います。
## 始めに
筆者は日常の仕事の大部分をメインフレーム(Hitachi MP-5600 VOS3/FS)上でJCLとCOBOL言語を使用して行っており、UNIX系OSに関しては趣味でLinuxを利用している程度の者です。

本誌の読者の中には「メインフレーム」や「COBOL」等は過去の遺物(^^;程度にしか思われない方も多くおられると思います。ところが企業等の堅牢性、安定性が要求されるシステムではハードとしての「メインフレーム」、データ処理言語としての「COBOL」がいまだに第一線で利用されています。

それには、色々な理由が有ると思うのですが、筆者が思うに、「必要以上に機能を求めないシンプルさ」がこの移り変わりの激しいコンピュータの世界でいまだに生き残っている理由のひとつではないかと思っています。そしてUNIX系のOSにもまた同じ事が言えると思っています。

本文書は私と同じように「メインフレーム、JCL、COBOL」といったレトロな環境で日頃頑張っている人達がいざUNIX系OSでデータ処理を行おうとした時に少しでも役立つ事を願って執筆しました。
## GNU fileutils
「GNU fileutils」はGNUによるファイル管理のコマンド集であり、誰もが一度はその中のコマンドのお世話になっていると思います。

詳細な説明はmanしていただくとして、ここでは幾つかの具体的な(簡単な)使用例をQ&A形式で揚げていきたいと思います。
### cp
Q. ファイル「hoge1.txt」を「hoge2.txt」に複写するにはどうしますか。
A. `$ cp hoge1.txt hoge2.txt`

まあ、見てのとうりですね(^^;。「cp」コマンドを使います。「CoPy」の略と憶えるといいでしょう。第1引数を第2引数に複写します。

Q. ファイル「hoge1.txt」「hoge2.txt」を一度にディレクトリhoge_dir」の中に複写するにはどうしますか。
A. `$ cp hoge1.txt hoge2.txt hoge_dir`

第1引数から最終引数の一つ手前までで指定したファイルを最終引数で指定したディレクトリに複写します。複数ファイルの指定にはシェルの正規表現を利用すると便利です。

Q. ディレクトリ「hoge_dir1」をまるごと(その下のファイルも含めて)ディレクトリ「hoge_dir2」の中に複写するにはどうしますか。
A. `$ cp -r hoge_dir1 hoge_dir2`

「cp」コマンドに「-r」オプションを指定しています。「Recursive」の略と憶えるといいでしょう。UNIX系OSのコマンドではこのようにオプション指定で動作を制御する事がよく行われます。
### mkdir
Q. ディレクトリ「hoge_dir」を作成するにはどうしますか。
A. `$ mkdir hoge_dir`

「mkdir」コマンドを使用します。「MaKeDIRectory」の略と憶えるといいでしょう。引数は複数指定も出来ます。
### mkfifo
Q. 名前付きパイプ「hoge_pipe」を作成するにはどうしますか。
A. `$ mkfifo hoge_pipe`

「mkfifo」コマンドを使用します。「MaKeFIFO]の略と憶えるといいでしょう。引数は複数指定も出来ます。

Q. 名前付きパイプはどんな時使いますか
A.
```
[prog1]--+-->file1-->[prog2]-->file3--+-->[prog4]-->kekka.txt
         |                            |
         +-->file2-->[prog3]-->file4--+
```
例えば上記のように、prog1〜prog4のプログラムが中間ファイルを受渡しながら順番に流れ、最終的にkekka.txtを出力するような処理の場合、以下のようにスクリプトを作成したとします。
```
#! /bin/sh
prog1 file1 file2
prog2 <file1 >file3
prog3 <file2 >file4
prog4 file3 file4 >kekka.txt
rm file[1-4]
```
この時、中間ファイルfile1〜file4が大量件数になる場合にはディスクが溢れる可能性が有りますし、プログラムprog1〜prog4の処理時間が長い場合、順次シーケンシャルに実行すのは処理時間的に効率が悪い等の問題点が浮かびます。

上記のスクリプトを名前付きパイプを使って書き直すと以下のようになります。
```
#! /bin/sh
mkfifo file1 file2 file3 file4
prog1 file1 file2 &
prog2 <file1 >file3 &
prog3 <file2 >file4 &
prog4 file3 file4 >kekka.txt
rm file[1-4]
```
この場合は、中間ファイルfile1〜file4はディスクスペースを消費せずにパイプを経由して受渡しされ、プログラムprog1〜prog4についても同時に実行されるようになり、処理の効率もアップします。
### mv
Q. ファイル「hoge1.txt」を「hoge2.txt」に移動(ファイル名を変更)するにはどうしますか。
A. `$ mv hoge1.txt hoge2.txt`

「mv」コマンドを使用します。「MoVe」の略と憶えるといいでしょう。第1引数を第2引数に移動(ファイル名の変更)します。

Q. ディレクトリ「hoge1_dir」を「hoge2_dir」にディレクトリ名を変更するにはどうしますか。
A. `$ mv hoge1_dir hoge2_dir`

ファイルの移動の場合とおなじですね。第1引数で指定したディレクトリを第2引数で指定した名称に変更します。

Q. ファイル「hoge1.txt」「hoge2.txt」をディレクトリ「hoge_dir」の中に移動するにはどうしますか。
A. `$ mv hoge1.txt hoge2.txt hoge_dir`

第1引数から最終引数の一つ手前までで指定したファイルを最終引数で指定したディレクトリに移動します。引数のファイルは複数指定可能です。
### rm
Q. ファイル「hoge.txt」を削除するにはどうしますか。
A. `$ rm hoge.txt`

「rm」コマンドを使用します。「ReMove」の略と憶えるといいでしょう。引数で指定したファイルを削除します。引数は複数指定可能です。

Q. ディレクトリ「hoge_dir」をまるごと削除するにはどうしますか。
A. `$ rm -r hoge_dir`

「rm」コマンドに「-r」オプションを指定しています。「Recursive」の略と憶えるといいでしょう。引数のディレクトリは複数指定可能です。
### rmdir
Q. 空のディレクトリ「hoge_dir」を削除するにはどうしますか。
A. `$ rmdir hoge_dir`

「rmdir」コマンドを使用します。「ReMoveDIRectory」の略と憶えるといいでしょう。引数は複数指定可能です。
## GNU textutils
「GNU textutils」はGNUによるファイル処理のコマンド集です。編集、加工、並び変え等々を行う事ができます。
### cat
Q. ファイル「hoge1.txt」「hoge2.txt」を連結し「hoge3.txt」を作成するにはどうしますか。
A. `$ cat hoge1.txt hoge2.txt >hoge3.txt`

「cat」コマンドを使用します。「ConcATenate」の略と憶えればいいでしょう。引数で指定したファイル(複数指定可能)を指定した順に連結して標準出力に出力します。出力ファイルにはシェルのリダイレクト機能を利用して出力して下さい。
### cut
Q. CSVファイル(項目が","で区切られているテキストファイル)hoge1.txt」の1、3、5番目の項目を抜きだし別のCSVファイル「hoge2.txt」を作成するにはどうしますか。
A. `$ cut -d "," -f 1,3,5 <hoge1.txt >hoge2.txt`

「cut」コマンドを使用します。「-d」オプションで項目の区切り文字を「,」に指定しています。「-f」オプションで「1,3,5」番目の項目を抜き出す事を指定しています。入力は標準入力から入力したり、オプションに続けて引数でファイルを指定(複数可)することも可能です。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用して出力して下さい。
### head
Q. ファイル「hoge1.txt」の先頭100行を抜きだして「hoge2.txt」を作成するにはどうしますか。
A. `head -100 <hoge1.txt >hoge2.txt`

「head」コマンドを使用します。「-100」オプションで100行抜きだす事を指定してます。入力は標準入力から入力したり、オプションに続けて引数でファイルを指定(複数可)することも可能です。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用して出力して下さい。
### join
joinはファイルの併合処理を行います...と言うと簡単なんですが、実に色々な使い方が考えられます。ここでは、他のコマンドより少し詳しく紹介していきます。
Q. マッチング処理を簡単に行う方法は有りますか。
ファイル「hoge1.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID04,02,Shiro
ID05,03,Pyonkichi
```
ファイル「hoge2.txt」の内容
```
01,Cat
02,Dog
```
ファイル「hoge3.txt」の内容
```
ID01,Cat,Mike
ID02,Cat,Nyanta
ID03,Dog,Pochi
ID04,Dog,Shiro
ID05,,Pyonkichi
```
「hoge1.txt」の第2項目と「hoge2.txt」の第1項目をマッチングして結果ファイル「hoge3.txt」を作成するにはどうしますか。

A. `$ join -t "," -1 2 -2 1 -a 1 -e "" -o 1.1,2.2,1.3 hoge1.txt  hoge2.txt >hoge3.txt`

「join」コマンドを使用します。「-t ","」は入出力ファイルの項目の区切り文字が「,」である事を指定しています。「-1 2」は「hoge1.txt」の2番目の項目をマッチングキーとする事を指定しています。「-2 1」は「hoge2.txt」の1番目の項目をマッチングキーとする事を指定しています。「-a 1」はマッチングしたデータに加えて、マッチングしないデータの内「hoge1.txt」側のデータを出力する事を指定しています。「-e ""」は「-o」オプションで指定した項目が存在しない場合に代わりに「」(空文字)
を出力する事を指定しています。「-o 1.1,2.2,1.3」は出力する項目が「hoge1.txt」の第1項目、「hoge2.txt」の第2項目、「hoge1.txt」の第3項目である事を指定しています。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用して出力して下さい。なお各ファイル(この例の場合は「hoge1.txt」「hoge2.txt」)はマッチングキーとなる項目の昇順にあらかじめ並んでいる必要が有ります。

Q. トランザクションデータを利用してマスタファイルの削除処理を行いたいのですがどうすればよいでしょうか。

マスタ「hoge1.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID04,02,Shiro
ID05,03,Pyonkichi
```
トランザクション「hoge2.txt」の内容
```
ID02
ID04
```
結果ファイル「hoge3.txt」の内容
```
ID01,Cat,Mike
ID03,Dog,Pochi
ID05,Frog,Pyonkichi
```
「hoge2.txt」の項目を削除キーとして「hoge1.txt」から除いて結果ファイル「hoge3.txt」を作成するにはどうしますか。

A. `$ join -t "," -1 1 -2 1 -v 1 hoge1.txt  hoge2.txt >hoge3.txt`

「-v 1」は「hoge1.txt」側のマッチングしないデータのみ出力する事を指定しています。

Q. トランザクションデータを利用してマスタファイルの更新処理を行いたいのですがどうすればいいですか。

マスタ「hoge1.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID04,02,Shiro
ID05,03,Pyonkichi
```

トランザクション「hoge2.txt」の内容
```
ID02,01,Nyankichi
ID04,02,Kuro
```

結果ファイル「hoge3.txt」の内容
```
ID01,01,Mike
ID02,01,Nyankichi
ID03,02,Pochi
ID04,02,Kuro
ID05,03,Pyonkichi
```

「hoge1.txt」の第1項目と「hoge2.txt」の第1項目をマッチングキーとして、「hoge1.txt」のデータを「hoge2.txt」のデータで置き換えた結果ファイル「hoge3.txt」を作成するにはどうすればいいですか。

A. 複数コマンドを組み合わせて処理できます。
```
$ join -t "," -1 1 -2 1 -o 2.1,2.2,2.3 hoge1.txt hoge2.txt >temp1.txt
```
「hoge1.txt」「hoge2.txt」の第1項目でマッチングしたデータについてのみ「hoge2.txt」のデータを「temp1.txt」に出力する。
```
$ join -t "," -1 1 -2 1 -v 1 hoge1.txt hoge2.txt >temp2.txt
```
「hoge1.txt」「hoge2.txt」の第1項目でマッチングしないデータの内「hoge1.txt」のデータのみを「temp2.txt」に出力する。
```
$ sort -t "," +0 -1 temp1.txt temp2.txt >hoge3.txt
```
「temp1.txt」「temp2.txt」を第1項目でソートし「hoge3.txt」に出力する。
```
$ rm temp1.txt temp2.txt
```
一時ファイル「temp1.txt」「temp2.txt」を削除する。

Q. トランザクションデータを利用してマスタファイルの追加処理を行いたいのですがどうすればいいですか。

マスタ「hoge1.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID04,02,Shiro
ID05,03,Pyonkichi
```

トランザクション「hoge2.txt」の内容
```
ID05,03,Pyonkichi_DUPLICATE
ID06,03,kermit
ID07,03,Demetan
```

結果ファイル「hoge3.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID04,02,Shiro
ID05,03,Pyonkichi
ID06,03,kermit
ID07,03,Demetan
```

「hoge1.txt」に「hoge2.txt」の内容を追加したい。但し、「hoge1.txt」の第1項目と「hoge2.txt」の第1項目が同一の場合はそのデータは追加したくない場合どうすればいいですか。

A. 複数コマンドを組み合わせて処理できます。
```
$ join -t "," -1 1 -2 1 -a 1 -o 1.1,1.2,1.3 hoge1.txt  hoge2.txt >temp1.txt
```
「hoge1.txt」「hoge2.txt」の第1項目でマッチングしないデータの内「hoge1.txt」のデータと、マッチングしたデータ(2重キー)の内「hoge1.txt」のデータを「temp1.txt」に出力する。
```
$ join -t "," -1 1 -2 1 -v 2 hoge1.txt  hoge2.txt >temp2.txt
```
「hoge1.txt」「hoge2.txt」の第1項目でマッチングしないデータの内「hoge2.txt」のデータのみ(追加データ)を「temp2.txt」に出力する。
```
$ sort -t "," +0 -1 temp1.txt temp2.txt >hoge3.txt
```
「temp1.txt」「temp2.txt」を第1項目でソートし「hoge3.txt」に出力する。
```
$ rm temp1.txt temp2.txt
```
一時ファイル「temp1.txt」「temp2.txt」を削除する。
### sort
Q. CSVファイルの任意の項目順にデータを並び替えるにはどうすればいいですか。

ファイル「hoge1.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID04,02,Shiro
ID05,03,Pyonkichi
```

ファイル「hoge2.txt」の内容
```
ID01,01,Mike
ID02,01,Nyanta
ID03,02,Pochi
ID05,03,Pyonkichi
ID04,02,Shiro
```

「hoge1.txt」を第3項目、第2項目の昇順に並べ変えて「hoge2.txt」を作成するにはどうすればいいですか。

A. `$ sort -t "," +2 -3 +1 -2 <hoge1.txt >hoge2.txt`

「sort」コマンドを使用します。入力は標準入力から入力したり、オプションに続けて引数でファイルを指定(複数可)することも可能です。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用するといいでしょう。「-t ","」は項目の区切り文字が「,」である事を指定します。「+2 -3」は最初の並べ替えの項目が第3項目である事を指定します。「+1 -2」は次の並べ替えの項目が第2項目である事を指定します。

また、以下のオプションもよく使います「-r」オプションは降順の並び替えを行います。「-n」オプションは数値としての並び替えを行います。
### tail
Q. ファイル「hoge1.txt」の末尾100行を抜きだし「hoge2.txt」を作成するにはどうしますか。
A. `$ tail -100 <hoge1.txt >hoge2.txt`

「tail」コマンドを使用します。入力は標準入力から入力したり、オプションに続けて引数でファイルを指定(複数可)することも可能です。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用して下さい。「-100」は末尾の100行を抜き出す事を指定しています。

Q. ファイル「hoge1.txt」の101行目から200行目までを抜きだし「hoge2.txt」を作成する。
A. `$ head -200 <hoge1.txt | tail -100 >hoge2.txt`

「head」と「tail」を組み合わせて行います。
### uniq
Q. ファイル「hoge1.txt」から重複した内容の行を削除し「hoge2.txt」を作成するにはどうしますか。
A. `$ uniq <hoge1.txt >hoge2.txt`

「uniq」コマンドを使用します。入力は標準入力から入力したり、オプションに続けて引数でファイルを指定(複数可)することも可能です。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用して下さい。また入力ファイルは行の昇順にあらかじめ並んでいる必要が有ります。

Q. ファイル「hoge1.txt」から重複した内容の行を抽出し「hoge2.txt」を作成するにはどうしますか。
A. `$ uniq -d <hoge1.txt >hoge2.txt`

「-d」オプションを指定します。
### wc
Q. ファイル「hoge.txt」の行数をカウントするにはどうしますか。
A. `$ wc -l <hoge.txt`

「wc」コマンドを使用します。入力は標準入力から入力したり、オプションに続けて引数でファイルを指定(複数可)することも可能です。出力は標準出力に行われるので出力ファイルにはシェルのリダイレクト機能を利用して下さい。「-l」オプションで行数カウントを指定します。

Q. ファイル「hoge.txt」の文字数をカウントするにはどうしますか。
A. `$ wc -c <hoge.txt`

「-c」オプションを指定します。
## その他のコマンド
GNU fileutils,GNU textutils以外にも役に立つコマンドが山のように有ります。その内の一つをほんのさわりだけ紹介します。
### gawk
「GNU awk」は多彩な機能を持っています。詳しくはman gawkで知っていただくとして、ここではデータ抽出に利用した例を見ていただきます。
Q. ファイルから特定の条件に当てはまるデータを抽出するにはどうしますか。

ファイル「hoge1.txt」の内容
```
ID01,Ito,100
ID02,Sato,80
ID03,Shimizu,70
ID04,Tani,85
ID05,Sawada,60
```

ファイル「hoge2.txt」の内容
```
ID02,Sato,80
ID04,Tani,85
```

「hoge1.txt」から第3項目が80台のデータを抽出して「hoge2.txt」を作成するにはどうしますか。

A. 
```
$ gawk 'BEGIN{
     FS = ",";
}
{
     if ($3 >= 80 && $3 < 90){
         print $0;
     }
}' <hoge1.txt >hoge2.txt  
```
プログラミングの経験が有る方ならなんとなく理解できたのではないでしょうか。gawkの機能はこんなものに留まりませんし、UNIX系のOS上ではこれ以外にもたくさんの便利なコマンド(sh, sed, perl, ruby, ...等々)が利用できます。
## 最後に
どうでしょうか、少しでもUNIX系OS上でデータ処理を行ってみようという気になれたでしょうか。なんだか単純なコマンドをいっぱい覚えないと役に立たないと思われた方も居られたかもしれませんね。しかし、その単純な機能を組み合わせる事が柔軟なデータ処理を可能にするのです。もしさらに興味を持たれたなら、シェル(sh,bash等)から勉強してみて下さい。シェルの持つパイプ(|)やリダイレクト(>, <, >>)の機能を知るとコマンドを柔軟に結合できますし、シェルの持つ制御命令(if, case, while等)を知ればさらに複雑な処理を効率よく出来るように成ると思います。また、上記で紹介したコマンドもほんのさわりを(表面をかすっただけ?)紹介しただけですので、manを読んで、御自分の仕事に生かせそうな機能やオプションが有ればどんどん使ってみて下さい。

最後になりますが、本文書がUNIX畑以外に埋もれている優秀な方たちがUNIX系OS上でもその知識と経験を生かせるような、そんなきっかけにでもなれば大変幸せに思います。
## P.S.
さて、約25年前に書いた文章を投稿させていただきました。文章中の「GNU fileutils」「GNU textutils」は現在「GNU coreutils」に統合されています。それ以外にも古臭い記述が散りばめられていると思いますがご容赦ください。以上、Qiita初投稿でした。