パターン処理言語

UNIX Tool (パターン処理言語 grep, sed, awk))

役に立つ文字処理ツールのメモ(大学院の教育用資料)

コンピュータと対話していると,わざわざ高級言語でプログラムを作って実行する必要のないような処理に遭遇する.このような時に役に立つツールが用意されている.ここでは文字の抽出・加工に用いられるパターン処理言語を紹介する.

パターン処理とは文字列の検索,抽出をおこなうことで,この処理に使われるスクリプトをパターン処理スクリプトという.パターン処理に適したツールとして以下の言語が用いられる.

・grep

grep は最も簡単な文字抽出スクリプトである.使用法は,

grep 検索したい文字列 ファイル名 である.

対象となる文字列を見つけると,grep はその文字列を文字列を含む行を表示する.該当文字列がない場合は何も出力しない.実は grep という言葉自体が構文を表している.

g/re/p g は global, re は正規表現 (regular expression), p は print である.つまり正規表現に適する文字をグローバルに(全て)プリントしなさい,ということを意味する.

grep で検索できるものはファイルに限らない.次の例ではコマンドの実行結果をパイプで grep に引き渡し,抽出している.

rpm -qa ¦ grep XFree :「XFree」という名前を含む rpmプ ログラムを全て表示

・sed : stream editor

sed は文字列の置換を行うのに便利なツールである.文法は至って簡単で,

sed 's/検索文字列/置換文字列/g' ファイル名である.

初めの「s」は置換を表し,最後の「g」はグローバルに,という意味である.「g」を外すと初めに出てくる文字列のみが置換され,あとは置換されない.重複した空白文字を1文字にするときは,sed '/s/□□/□/g' でよい.

2文字以上の空白を1文字にするときは,sed '/s/□□*/□/g' となる.

もう一つよく使われるコマンドに「y」オプションがある.

sed 'y/検索文字列/置換文字列/g' ファイル名

「s」オプションとの違いは,検索文字列と置換文字列が1文字ずつ対応することである.次の例は全ての大文字を小文字変換するスクリプトである.

sed 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/g' ファイル名

sed を使ってより高度な処理を行うことも可能だが,複雑な処理は次の awk を使う方が断然早道である.

・awk

awk はパターン処理の定番ツールである.awk には明確な文法がある.

awk '/パターン/{アクション}' ファイル名

パターンは対象文字列,アクションは処理内容である.

パターンを省略すると全ての行に対して処理を行い,アクションを省くとパターンにマッチするものが全て出力される. パターンは grep や sed のようにただ抽出したい文字列をおくだけでよく,アクションは主に print (表示)である.awk の特徴は文字列をフィールドと呼ばれるセルで区切って認識できることである.デフォルトのフィールドセパレータ(区切り文字)は半角スペースで,フィールドを表す記号は「$」である.

実行例を見て欲しい.

ファイルには氏名,得点,出席日数が記載されている.

出席日数が10回以上の者に点数を与える場合,適合者をリストさせるには,次行をコマンドラインで実行すればよい.

awk '$3>10 {print $1, $2}' ten

この例では簡単のため,6名であるが,数千人のデータでも構わない.

例は,$3>10 はパターン,{print $1, $2} がアクションとなる.

kuma1% cat ten

aaaaa 76 10

bbbbb 65 11

ccccc 80 8

ddddd 72 12

eeeee 61 14

fffff 50 9

kuma1% awk '$3>10 {print $1, $2, $3}' ten

bbbbb 65 11

ddddd 72 12

eeeee 61 14

出席点を加算するには下記のようにする.アクションの中に加算式を加える.

kuma1% awk '$3>10 {print $1, $2+$3}' ten

bbbbb 76

ddddd 84

eeeee 75

カンマで区切られたデータに対して,フィールドを定義した例を以下に示す.何も書かなければ空白が区切りとなる.この例では,カンマ区切りデータを読込,出席回数10回以上の者だけを表示させている.

kuma1% cat ten

aaaaa, 76, 10

bbbbb, 65, 11

ccccc, 80, 8

ddddd, 72, 12

eeeee, 61, 14

fffff, 50, 9

kuma1% awk -F"," '$3>10 {print $1, $2, $3}' ten

bbbbb 65 11

ddddd 72 12

eeeee 61 14

kuma1%

氏名,3教科の点数データの最初に通し番号を付ける方法

bunsi%> cat -n ten1

1 aaaaa, 76,64,58

2 bbbbb, 65,73,91

3 ccccc, 80,48,62

4 ddddd, 72,71,55

5 eeeee, 61,82,73

6 fffff, 50,52,66

bunsi%> awk -F"," '{print NR, $0}' ten1

1 aaaaa, 76,64,58

2 bbbbb, 65,73,91

3 ccccc, 80,48,62

4 ddddd, 72,71,55

5 eeeee, 61,82,73

6 fffff, 50,52,66

一般式は下記のとおりである.

awk [-F区切り文字] スクリプト [入力ファイル名]

awk [-F区切り文字] -f スクリプト [入力ファイル名] スクリプトがファイルの場合

次の例は,予めエディターで編集し,保存しておいたデータファイル,ten(カンマ区切りデータファイル)内容を表示(cat) ,確認した後,ave(スクリプト)を実行した例である.

データファイルten の第2カラムの数値を合計し,平均値を出している.行数分$2 を合計する.aは合計値である.ENDは全ての行を処理し終わった時にマッチして,結果を出力する.NR はシステム変数に格納されている行番号である.

Trying telnet connection ---------- 実行例(kuma1というUNIXマシンにioginし,viエディターを使用した具体例)

SunOS UNIX (kuma1)

login: p4080

Password:

Last login: Mon Sep 23 17:37:24 from 133.95.74.145

SunOS Release 4.1.1-JLE1.1.1 (GENERIC) #1: Tue Nov 13 16:34:44 JST 1990

You have mail.

kuma1% cat ten

aaaaa, 76,64,58

bbbbb, 65,73,91

ccccc, 80,48,62

ddddd, 72,71,55

eeeee, 61,82,73

fffff, 50,52,66

kuma1% vi ave ---------- viでファイルaveを作成

"ave" [New file]

---------- 入力画面が現れるので,i(挿入)を打ち込み続けて下記を入力する.

{a += $2

}

end {print a/NR

}

---------- escを押し,ZZ(shift zz)を入力すると保存される

"ave" [New file] 5 lines, 30 bytes

kuma1% awk -F"," -f ave zzz ---------- 実行してみる

awk: syntax error near line 3 ---------- 3行目にエラー検出

awk: bailing out near line 3

kuma1% vi ave ---------- viで修正する

"ave" 5 lines, 30 bytes

{a += $2 ☜ a = a + $2 代入演算子

}

END { print a/NR ---------- ddで行を削除し,i入力後修正データを打ち込む

---------- escを押し,ZZ(shift zz)を入力すると保存される

"ave" 5 lines, 31 bytes

kuma1% awk -F"," -f ave ten ---------- 実行する

67.3333 ---------- 出力結果

kuma1% logout

現在は,パソコンで実行出来る. Macの場合はターミナルを開く.

全教科の平均点を求めるには

% cat aver1

{a += $2

}

{b += $3

}

{c += $4

}

END {print a/NR, b/NR, c/NR

}

% awk -F"," -f aver1 tensu

67.3333 65 67.5

%

複数の処理はセミコロンで区切ってもよい.

{a += $2;b += $3;c += $4

}

END {print a/NR, b/NR, c/NR

}

---------- 入力ファイル

aaaaa, 76,64,58

bbbbb, 65,73,91

ccccc, 80,48,62

ddddd, 72,71,55

eeeee, 61,82,73

fffff, 50,52,66

----------

すべての行の各フィールドの合計

% cat ten -------------------------------上記データ

% cat sumall

{ sum=0

for (i = 1; i<= NF; i = i + 1) sum = sum + $i ☜ NF はフィールドの数,範囲に注意

print sum

}

% awk -F"," -f sumall ten

198

229

190

198

216

168

点数の分布

%cat bunpu.dat ファイルを表示させる ----------以下表示

67

82

76

91

39

中略

36

51

62

73

86

%cat bunpu

{

for(i =1;i<=NF;i++){

$i=int($i/10)*10

ten[$i]++ ☜ 説明は後述

}

}

END {

for(i in ten){

print i "-" i+10, ten[i]

}

}

%awk -f bunpu bunpu.dat 実行結果 ---------->以下の通り

70-80 6

40-50 4

90-100 3

10-20 3

60-70 8

30-40 3

80-90 8

50-60 5

20-30 4

三教科の合計

bunsi%> cat sumall

{ sum=0

for (i = 1; i<= NF; i = i + 1) sum = sum + $i

print $0,sum

}

bunsi%> awk -F"," -f sumall ten

aaaaa, 76,64,58 198

bbbbb, 65,73,91 229

ccccc, 80,48,62 190

ddddd, 72,71,55 198

eeeee, 61,82,73 216

fffff, 50,52,66 168

連想配列(文字列を配列の添え字にする)

AWKと他言語の配列で最も大きな違いは、配列の添え字に文字列が使えることである.これを連想配列と言い、テキスト処理を柔軟に行うことができる.

例 製薬会社毎の医薬品数を調べる

bunsi%> cat drugsu

BEGIN {

FS = " ";

}

{

kaisya[$4] += 1; # $4=kaisya name to array

}

END {

for (i in kaisya)

print i ":" kaisya[i] " articles";

}

読み込んだフィールドのうち、製薬会社($4) を配列kaisya の添え字に使っている.初めて使う変数は0 に初期化されているので,医薬品データを読む度に製薬会社の配列kaisya["aaa"] 値に1 を加えていけば、入力が終ったときにはaaa の薬品数がkaisya["aaa"] に保存されることになる.aaa は武田製薬等の固有名詞である.

このスクリプトではkaisya に保存された結果をパターンEND で出力している.連想配列の添え字には任意の文字列が使えるので、どんな添え字が使われているのかをプログラムの実行前に知ることはできない.そこで全要素を出力するためにfor文を使う.

薬品名 価格 有効期限 会社名 個数

alicin 2200 2003/09/01 aaa 20

west 1600 2005/06/05 eee 16

cercin 850 2010/02/20 ccc 62

kerol 250 2009/01/31 ggg 60

renta 870 2006/04/10 ggg 25

star 950 2005/01/15 bbb 38

elepin 2900 2004/08/31 eee 30

germin 1100 2006/09/46 ggg 30

hart 1800 2005/01/08 bbb 22

linex 680 2003/12/31 fff 90

jet 1320 2006/01/03 eee 35

kage 760 2004/02/25 ggg 26

happi 2010 2008/12/31 mmm 30

xerol 620 2009/01/31 ccc 34

manex 1900 2007/12/05 ddd 16

danzen 3100 2003/06/15 ddd 20

nemax 1400 2005/11/02 aaa 30

opex 880 2004/01/25 ccc 24

Verol 120 2006/01/31 rrr 70

force 650 2004/11/05 fff 18

penex 550 2003/12/31 bbb 80

queen 1900 2007/01/10 eee 23

yonex 1400 2005/03/31 ddd 10

benzal 1000 2004/04/23 bbb 12

trees 1700 2004/03/20 fff 14

uran 2100 2005/06/10 bbb 31

vio 830 2008/09/10 aaa 25

inden 950 2004/07/14 aaa 18

zerok 3100 2006/08/31 fff 12

for (変数名 in 配列名) {

処理;

}

上記のように記述すると、配列に使われている添え字全てに対して、添え字を変数に代入して「処理」を実行する.

実際に、このスクリプトを実行すると次のようになる。

bunsi%> awk -f drugsu drug.dat

ddd:3 articles

kkk:1 articles

rrr:1 articles

ccc:3 articles

bbb:5 articles

aaa:4 articles

ggg:4 articles

fff:4 articles

mmm:1 articles

eee:4 articles

bunsi%>

組み込み関数

次の例は組み込み関数を使った計算例である.sin の値を10度刻みで90度まで計算する.

bunsi%> cat sin_awk

BEGIN {

for (i = 0; i <= 9; i++)

printf("%1.6f¥n", sin(i*10*3.1415/180));

}

bunsi%> awk -f sin_awk

実行結果

0.000000

0.173643

0.342010

0.499987

0.642772

0.766028

0.866010

0.939680

0.984801

1.000000

九九算

ファイルは、"kuku.awk" 7 lines, 164 characters である。

bunsi%> cat kuku.awk

BEGIN {

for (i = 1; i < 10; i++) {

for (j = 1; j < 10; j++)

printf("%3d", i * j);

printf("¥n");

}

}

bunsi%> awk -f kuku.awk

実行結果

1 2 3 4 5 6 7 8 9

2 4 6 8 10 12 14 16 18

3 6 9 12 15 18 21 24 27

4 8 12 16 20 24 28 32 36

5 10 15 20 25 30 35 40 45

6 12 18 24 30 36 42 48 54

7 14 21 28 35 42 49 56 63

8 16 24 32 40 48 56 64 72

9 18 27 36 45 54 63 72 81

bunsi%>

入力データ変換の実例

下記のような分子のX線三次元座標ファイルがある.分子を市販のアプリケーションを使ってグラフィック表示させるには括弧内の数値(標準偏差値)および括弧は不要である.

C 7.2325(5) 2.1458(4) 5.7894(3)

C 1.2345(5) 7.7451(4) 1.7894(3)

C 8.2346(5) 1.1458(4) 5.1894(3)

C 1.2395(5) 2.1458(4) 6.7874(3)

C 2.2345(5) 4.8452(4) 5.0891(3)

C 1.2345(5) 2.1258(4) 8.7894(3)

C 5.2387(5) 2.2418(4) 5.7887(3)

C 6.2345(5) 2.1458(4) 2.7894(3)

下記のようなデータを得る方法を考える.数が少ない場合は,テキストエディターで消してもよいが,生体高分子の座標などの場合は数が多いためプログラミングする以外に方法はない.

C 7.2325 2.1458 5.7894

C 1.2345 7.7451 1.7894

C 8.2346 1.1458 5.1894

C 1.2395 2.1458 6.7874

C 2.2345 4.8452 5.0891

C 1.2345 2.1258 8.7894

C 5.2387 2.2418 5.7887

C 6.2345 2.1458 2.7894

方法1

dragon% awk -F"[()]" '{print $1, $3, $5}' cord

方法2

下記のファイルを作り,ファイル名kakkoで保存する

BEGIN { FS = "[()]"

}

{ print $1, $3, $5

}

以下を実行する

dragon%awk -f kakko cord

正規表現

正規表現(regular expression) は,ある「規則」に従う文字列を指定(識別)するために使われる規則である.

例えば,ID等において「Aで始まって9で終わるような文字列」とか,「文字列の先頭にアルファベットがある行」というような指定をしたいことがある.このようなときには,可能性のある全ての場合を想定し,列挙するわけにはいかないので,特定の文字を使って一般的な文字列を指定する方法をとることになる.

正規表現が使われるのは文字列を処理する関数やパターンの指定のときである.

例えば, $0 ~ /this is reg¥. exp¥./ というパターンは,入力した行の中に,’this is reg. exp.’と いう文字列が含まれているとき「真値」を返す.

正規表現に使われる文字(例えば,上の例の¥ など)とその意味は以下の例のとおりである.

^ 文字列の先頭を表す.

例:^takeda というのは,先頭の文字列が takeda である文字列にマッチする.

$ 文字列の末尾を表す.

例:987$ というのは,最後の3文字が 987 である文字列にマッチする.

. 改行文字を除く任意の1文字に一致する.

例:a.x は a とx の間に任意の1文字がある3文字(例えば ayx,a9x など)にマッチする.

[ ] [ ]に含まれる1文字にマッチする.連続する文字は-でつないで指定する.

例:[abC] は,a, b, C のいずれかを表す.[0-9]は,数字1文字を表す.

[^ ] [ ]に含まれない1文字にマッチする.

例:[^0-9A-Za-z] は,英数字以外の1文字にマッチする.

¦ 「または」を意味する.

例:^A¦[0-9] 行頭の A か 数字にマッチする.'/flower¦girl/i' (perl 言語の場合)

* この記号の前にある文字の0回以上の繰り返しを意味する.

例:ab*x は ax,abx,abbx,abbbx などにマッチする.

+ この記号の前にある文字の1回以上の繰り返しを意味する.

例:ab+x は abx,abbx,abbbx などにマッチする.

? この記号の前にある文字の1回もしくは0回の繰り返しを意味する.

例:ab?x は ax,abx のいずれかにマッチする.

¥ この文字の後に続く文字をそのままの意味で使用する.

例:¥. ピリオドを表す.¥* アスタリスクを表す.

正規表現の使い方

正規表現はスラッシュで囲まれたパターンとして使うことができる.そのような正規表現は各レコードのテキスト全体に対してチェックされる.

実例で理解してほしい.

以下のような書式で書かれたデータの繰り返しが,数メガを超える長大なデータの中に数百個存在する.これから特定のキーワードを含む行を抽出し解析したい.データのファイル名はPh_MO.arc である.

省略

HEAT OF FORMATION = 13.136779 KCAL = 54.96428 KJ

ELECTRONIC ENERGY = -34825.756223 EV

CORE-CORE REPULSION = 30579.478233 EV

FOR REACTION COORDINATE = 1.5229 ANGSTROMS

REACTION GRADIENT = -0.001797 KCAL/ANGSTROM

DIPOLE = 8.90505 DEBYE SYMMETRY: C1

NO. OF FILLED LEVELS = 68

CHARGE ON SYSTEM = 1

IONIZATION POTENTIAL = 12.495880 EV

HOMO LUMO ENERGIES (EV) = -12.496 -3.898

MOLECULAR WEIGHT = 379.378

SCF CALCULATIONS = 8

COMPUTATION TIME = 0.000 SECONDS

省略

ダイポールモーメントの変化をみたい場合は,DIPOLE を含む行を抽出するため,下記のスクリプトを実行する.

画面に表示させる場合

awk '/DIPOLE/ { print }' Ph_MO.arc

DIPOLE = 8.90505 DEBYE SYMMETRY: C1

DIPOLE = 8.93499 DEBYE SYMMETRY: C1

DIPOLE = 8.97372 DEBYE SYMMETRY: C1

DIPOLE = 8.99545 DEBYE SYMMETRY: C1

DIPOLE = 9.03427 DEBYE SYMMETRY: C1

中略

DIPOLE = 9.97977 DEBYE SYMMETRY: C1

DIPOLE = 10.11048 DEBYE SYMMETRY: C1

DIPOLE = 10.23536 DEBYE SYMMETRY: C1

DIPOLE = 10.37504 DEBYE SYMMETRY: C1

DIPOLE = 10.50648 DEBYE SYMMETRY: C1

リダイレクトでファイルに出力し保存する場合は以下の一行を実行する.

awk '/DIPOLE/ { print }' Ph_MO.arc > dipole_line

数値データだけを出力するには以下を実行する.

awk '/DIPOLE/ { print $3 }' Ph_MO.arc

8.90505

8.93499

8.97372

8.99545

9.03427

中略

9.97977

10.11048

10.23536

10.37504

10.50648

正規表現は,マッチング式として,マッチングを試みる文字列を特定する. `~'と`!~' の二個の演算子は正規表現の比較をおこなう.これらの演算子を使った式はパターンとして,if, while, for, do 文中で使うこととができる.

e x p~ /r e g e x p/

これは式exp (文字として扱われる)がregexp とマッチするときに「真」となる.

次の例は,第一フィールドに大文字の`HOMO' を含んでいる入力レコードをすべて抽出するものである.

awk '$1~ /HOMO/' Ph_MO.arc

HOMO LUMO ENERGIES (EV) = -12.496 -3.898

HOMO LUMO ENERGIES (EV) = -12.493 -3.894

HOMO LUMO ENERGIES (EV) = -12.493 -3.891

HOMO LUMO ENERGIES (EV) = -12.491 -3.887

中略

HOMO LUMO ENERGIES (EV) = -12.155 -4.615

HOMO LUMO ENERGIES (EV) = -12.020 -4.775

HOMO LUMO ENERGIES (EV) = -11.893 -4.930

次のように書いても良い.

awk '{ if ($1~ /HOMO/) print }' Ph_MO.arc

ENERGY 又は HOMO を含む行を表示するには以下のようなスクリプトを実行する

awk '{ if ($1~ /HEAT|HOMO/) print }' Ph_MO.arc

HEAT OF FORMATION = 13.136779 KCAL = 54.96428 KJ

HOMO LUMO ENERGIES (EV) = -12.496 -3.898

HEAT OF FORMATION = 14.942881 KCAL = 62.52101 KJ

HOMO LUMO ENERGIES (EV) = -12.493 -3.894

中略

HEAT OF FORMATION = 89.824479 KCAL = 375.82562 KJ

HOMO LUMO ENERGIES (EV) = -12.020 -4.775

HEAT OF FORMATION = 93.794598 KCAL = 392.43660 KJ

HOMO LUMO ENERGIES (EV) = -11.893 -4.930

e x p !~ /r e g e x p/

これは式 exp (文字列として扱われる)が regexp にマッチしないときに「真」となる.次の例では,最初のフィールドに大文字の`T'を含まない入力レコードすべてを抽出する. 入力データには頭文字に”T”を持つデータが存在する.

awk '$1 !~ /T/' address.dat

Akagi 1976

Endo 1983

Kato 1990

Sato 1992

Ueno 1985

Wada 1995

...

正規表現が/xyz/ のようにスラッシュで囲まれて記述されたとき, これを正規表現定数と呼ぶ.これは3.14のような数値定数や"xyz" のような文字列定数のようなものである.

正規表現 ¥について

前出の医薬品データで説明する.

有効期限 2005 年の分だけを抽出しようとして下記のスクリプトを実行した.ところが,単価に 2005 円の医薬品があったため,2006 年の分まで拾ってしまった.これを避けるため,2005/ を含む文字を検索させようとしたが,エラーになった./ を本来の文字として認識させるためには ¥/ を使う.

bunsi%> awk '/2005/' drug.dat

west 1600 2005/06/05 eee 16

star 950 2005/01/15 bbb 38

hart 1800 2005/01/08 bbb 22

nemax 1400 2005/11/02 aaa 30

yonex 1400 2005/03/31 ddd 10

uran 2100 2005/06/10 bbb 31

kerok 2005 2006/08/31 kkk 12

bunsi%> awk '/1005//' drug.dat

awk: Cannot divide by zero.

The input line number is 1.

The file is drug.dat.

The source line number is 1.

bunsi%> awk '/2005¥//' drug.dat

west 1600 2005/06/05 eee 16

star 950 2005/01/15 bbb 38

hart 1800 2005/01/08 bbb 22

nemax 1400 2005/11/02 aaa 30

yonex 1400 2005/03/31 ddd 10

uran 2100 2005/06/10 bbb 31

bunsi%> awk '{a+=$2*$5;} END {print a;}' drug.dat

1040620

bunsi%> awk '/2003/ {a+=$2*$5;} END {print a;}' drug.dat

211200

パソコンにおけるAWK実行環境(OSがUNIXでないOS9の場合など)

パソコン上で実行できる日本語awkが開発されている. Mac ならjgawk である. JGAwkをパソコンOS上で実行する場合,実行支援のための入出力環境も用意されている.入力ファイル,スクリプトファイルの選択,結果出力ファイル指定などがマウスで選択できる.使い捨てのつもりで作ったスクリプトを保存しておくと,いつの間にかライブラリができ上がり,いつでも取り出して実行できるので便利である.

JGAwk Interface

JGAwk Interface を起動すると,上記の画面が現れる.

Input

キーボード(コンソール),クリップボード,ファイルの中から選択する.ファイルは,ファインダーからJGAwk Interface アイコンにDrag&Drop したり, オープンダイアログを使って指定する.

Output

出力先をウィンドウ,クリップボード,ファイルの中から選択する.

Script

スクリプトを指定する.ファイルか一行野郎(One Liner) のどちらかを選ぶ.ファイルは,特定のフォルダーの中から,ポップアップメニューで選択する. 一行野郎の場合は,直接入力またはライブラリから選択する.

Run ボタン

jgawk を実行する.

コマンドラインから実行する場合,Macにおいては“<“ や“>” のリダイレクションが使える.

参考資料

http://www.imasy.or.jp/~iwao/JGAwk/JGAwkMac.html

AWKの学習

スクリプトを勉強するには,書籍やインターネット上で公開されている簡単な例を実行するのが早道である.以前は,新しい言語に興味はあっても,学生実習用のワークステーションが無いので知識修得できない事情があった.ところが,最近のパソコンやソフトウエアの発達はめざましく,やる気があればほとんどのことがパソコン上で実行できる環境が整ったと言える.ただし,処理系によってはOS に依存する部分があるなど,使い勝手が異なる場合があるので,注意が必要である.