パターン処理言語
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 に依存する部分があるなど,使い勝手が異なる場合があるので,注意が必要である.