よくわかるテキスト処理

目次

スクリプトを簡単に書くこつ

パイプとリダイレクト

あるコマンドの入力としてファイルの内容を使ったり、 コマンドの出力を、また別のコマンドの入力にしたりできます。 既にいろいろなコマンドがあるので、 それらを繋げるだけでもいろいろとできます。

パイプ

あるプログラムの出力を、別のプログラムに流しこむ。 | を使ってあらわし、いくつも繋げることができる。

例:
% ls -la | less

リダイレクト

プログラムの入力/出力をファイルにする。 通常は標準入出力(キーボードとディスプレイ)。 %lt; と %gt; を使ってあらわす。

例:
% less < readme.txt
% ls -la > filelist.txt

各種コマンド

grep

ある文字列を含む行のみを表示する。

例: /etc/passwd に書かれた masa のユーザ情報を見る。
% grep masa: /etc/passwd
masa:x:10262:1000:Nakayama Masahiro,t99683mn,sprng:/home/masa:/usr/local/bin/zsh

egrep

検索文字列として正規表現を利用できる grep。

^行の先頭
$行の最後
[13579]
[a-z]
[ ] にくるまれた文字列の、 どれか一文字に対応する。 数字やアルファベットでは先頭と最後を - で繋げることで、 その間の全ての文字を含むことができる。
この場合、前者は奇数の数字一文字に対応し、 後者は小文字のアルファベットのどれかに対応。 英数字のどれか一つと言う場合には、[a-zA-Z0-9] とすればよい。
* 直前の文字(あるいは括弧の中身)を0回以上くり返す。
例えば、「abc*」 は、「ab」「abc」「abcccc」などに対応する。 0回、つまり直前の文字が含まれない場合も含まれることに注意。例えば、1文字以上続く場合は、「abcc*」などとしなければいけない。
( ) 文字列をグループ化する。
例えば * などと共に利用する。 「a(bc)*」は、「a」「abc」「abcbcbc」などに対応し、 「abbc」「abcc」などには対応しない。
| 前後のどちらかの文字列に対応する。 ( ) と併用することで便利に使うことができる。
「ab|c」は「ab」か「c」に対応し、「abc」「bc」などには対応しない。
「a(b|c)」は「ab」か「ac」に対応し、「a」「abc」などには対応しない。

sort

ファイルの内容を、文字の順番に整列する。

入力: (input.txt)
hogehoge
hagehage
higehige

% sort < input.txt > output.txt

出力: (output.txt)
hagehage
higehige
hogehoge

uniq

同じ内容で連続する行をまとめる。

入力: (input.txt)
inu
neco
neko
neko
neko
hebi
inu
inu

% uniq < input.txt > output.txt

出力: (output.txt)
inu
neco
neko
hebi
inu

注意:
同じ内容であっても、上記の例のように連続していなければそのままなので、 下のように sort と併用することも多い。

% sort < input.txt | uniq > output2.txt

出力: (output2.txt)
inu
hebi
neco
neko

-c オプション

uniq -c とすることで、各行の先頭に、その行が入力中に連続して現れた回数を表示する。

% sort < input.txt | uniq -c > output3.txt

出力: (output3.txt)
   3 inu
   1 hebi
   1 neco
   3 neko

サンプル

例: /etc/passwd に書かれた CNS のアカウント名から rg-99っぽい人を探して、アカウント名順に表示
% egrep '[st]99' /etc/passwd | sort
aiko:x:10352:1000:Aiko Shiwaki,t99459as:/home/aiko:/bin/tcsh
ami-ta:x:10350:1000:Takashi Amino,t99801ta:/home/ami-ta:/bin/tcsh
ami:x:10344:1000:Ami Kurimoto,t99339ak:/home/ami:/bin/tcsh
...

例: メールのファイル(mbox)から"From: "で始まる行を抜き出す。
% egrep '^From: ' mbox

例: "From: "で始まる行を数える(メールの数)。
% egrep '^From: ' mbox | wc -l

例: 自分が出したメールの数
% cat mbox | egrep -c '^From: .*masa@sfc.wide.ad.jp'

例: メーリングリスト参加者の重複
% cat neco | sort > neco.sorted
% cat sprng | sort > sprng.sorted
% comm -1 -2 neco.sorted sprng.sorted

Perl

変数

ふつうの値(スカラー値)
$var、$subject…
配列
@LIST、$LIST[0]、$LIST[1]…
ハッシュ
%HASH、$HASH{"From"}、$HASH{"To"}

接頭語($@%)で変数の種類を区別する。
配列やハッシュに含まれる一つの値を取りだすときは、 配列(ハッシュ)の接頭語を $ にすることに注意。

省略形

Perl には、見えない変数として「$_」があり、 デフォルトの操作対象として使われる。 例えば、関数に引数を指定しないと$_と見なされる。

うまく使うと直感的なプログラムが書けるが、 下手に使うと混乱のもと。
諸刃の剣

構文

while(条件){…}
条件が真の場合while内を繰り返す。
while(<>) とすると標準入力から$_に一行ずつ代入して、 {…}の内容が繰り返される。
if ( $var eq "文字列" ) {…}
Perl で文字列の比較をするときは、 == や != ではなく、eq(等しい)と ne (等しくない) を使う。
if ( /正規表現・文字列/ ) {…}
普通の if 文の条件として正規表現を使うことができる。 この場合は、$_ に正規表現や文字列が見つかれば {…}の中が実行される。
また、$_ ではなく変数と比較をする場合には、 if ( $var =~ /正規表現/ ) と =~ を使う。

関数

print
表示
chomp
最後の改行を取り除く。
while(<>) などを使う場合には、 ループの先頭で改行を除きます。
split /区切り文字/
区切り文字で分割、リストで返す。

Perlの基本パターン

タブ(あるいはカンマ)区切りされたデータを一行ずつ処理すると言うパターンが多い。

# 1行ごとに$_に入れる。
while (<>) {
    # 最後の改行を取り除く。
	chomp;
    # タブで区切り配列@dataに入れる。
	@data = split /\t/;
	…いろいろな処理…
    # 結果を表示
	print;
# while からここまでを繰り返す。
}

今回のヒント

  1. splitで分割して、変数に代入。
    ( $kg, $msgid, $replyto ) = split /\t/;
    このとき、あらかじめ chomp をしていないと、 $replyto には行末の改行付で入ってしまうので注意。

  2. Message-IdからKGがわかるように、$kgと$msgidをハッシュに入れる。
    $DATA{$msgid} = $kg;

  3. リプライ先のKGをハッシュから探す。
    $replykg = $DATA{$replyto};

  4. 発言者のKGとリプライ先のKGを表示
    print “$kg -> $replykg\n”;

  5. 1〜4の出力を、uniq -cでカウント。
    sortしてからuniq ?cすればいい。

  6. 多い順に並べたければさらにsortする。
    数字の大小なので、sort ?n とする。

これらをうまく組み合わせることで紐帯図を作ることができます。