なるべく書かないawkの使い方

awkという、古くからのスクリプト言語がある。(1977年生まれ。読み方は「オーク」である。エイ・ダブリュ・ケイではない)man awkをPDFに変換してみると、たったの3ページ強しかない。

$ man -t awk|pstopdf -i -o ~/Downloads/awk.pdf

とてもシンプルな言語仕様ではあるが、awkには必要十分な表現力がある。特にテキストを処理する場面においては、最小限のシンプルな記述で、気の利いた処理を素早くこなす。無駄のないawkワンライナーを見ると、ある種の感動を覚える。awk以降に生まれたスクリプト言語は、少なからずawkの影響を受けていると思われる。

awkを知ることで、間違いなく幸福度は上がると思う。いつかきっと「知ってて良かった」と思える時が来るはず。もっともっと、awkを知りたくなってきた。

基本動作

awkの基本動作は、とってもシンプルである。

  1. テキストを1行読み込んで、
  2. 読み込んだ行を空白区切りのデータと解釈して、
  3. 何らかの処理を行う。

$ cat <<EOS > abc.txt
> a b c
> d e f
> g h i
> EOS

$ cat abc.txt
a b c
d e f
g h i

$ cat abc.txt | awk '{print $0}'
a b c
d e f
g h i

つまり...


$ cat abc.txt | awk '{print $2}'
b
e
h

つまり...


$ cat abc.txt | awk '{print $1 $2 $3}'
abc
def
ghi

$ cat abc.txt | awk '{print $1,$2,$3}'
a b c
d e f
g h i

条件と処理

$ cat abc.txt | awk '/e/{print $0}'
d e f

$ cat abc.txt | awk '$1=="a"{print $0}'
a b c

$ cat abc.txt | awk 'NR==3{print $0}'
g h i


$ cat abc.txt | awk '
> /^a/{print $0}
> /^g/{print $0}
> '
a b c
g h i
$ cat abc.txt | awk '/^a/{print $0}/^g/{print $0}'
a b c
g h i

grep+cutとの違い

# 2列目を取り出す
$ cat abc.txt | cut -d' ' -f2
b
e
h

# aかgで始まる行を取り出す
$ cat abc.txt | grep -E '^a|^g'
a b c
g h i

# aかgで始まる行の2列目を取り出す
$ cat abc.txt | grep -E '^a|^g' | cut -d' ' -f2
b
h
$ ls -l
total 16
drwx------+  31 bebe  staff   1054 12  4 16:21 Desktop
drwx------+ 450 bebe  staff  15300 12  5 10:37 Documents
drwx------+ 332 bebe  staff  11288 12  5 14:24 Downloads
drwx------@  72 bebe  staff   2448 10 30 17:44 Library
drwx------+   9 bebe  staff    306 10 30 06:42 Movies
drwx------+   9 bebe  staff    306  2 28  2013 Music
drwx------+  14 bebe  staff    476 11  5 15:51 Pictures
drwxr-xr-x+  12 bebe  staff    408 12  2 16:14 Public
drwxr-xr-x+   5 bebe  staff    170 12  2 16:14 Sites
...中略...
$ ls -l | cut -d' ' -f5


staff
staff

bebe
bebe


bebe
...中略...
$ ls -l | sed 's/ \{1,\}/ /g' | cut -d' ' -f5

1054
15300
11288
2448
306
306
476
408
170
...中略...

$ ls -l | awk '{print $5}'

1054
15300
11288
2448
306
306
476
408
170
...中略...

awkには、人の感覚に合った区切り文字がデフォルトで設定されている。だから使いやすい!

集計の技

そして、awkの処理は列を取り出すだけに留まらない。

$ ls -l | awk '{sum+=$5; print $5} END{print "--------\n" sum}'

1054
15300
11288
2448
306
306
476
408
170
136
136
18
343
136
136
340
--------
33001

$ cat <<EOS > 123.txt
> 1 2 3
> 4 5 6
> 7 8 9
> EOS

$ cat 123.txt
1 2 3
4 5 6
7 8 9

$ cat 123.txt | awk -v OFS="\t" '{rt=$1+$2+$3; c1+=$1; c2+=$2; c3+=$3; c4+=rt; print $1,$2,$3,"",rt} END{print ""; print c1,c2,c3,"",c4}'
1	2	3		6
4	5	6		15
7	8	9		24

12	15	18		45

$ seq 100 | awk '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"} {print $1 $3 $5}'
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...中略...

これはもう、表計算アプリと同じ感覚である。

awkとは、表計算コマンドなのかもしれない。

省略の技

awkの心地よさは、省略可能な表現力だと思う。

$ #cat abc.txt | awk '{print $0}'
$ cat abc.txt | awk '{print}'
a b c
d e f
g h i

$ #cat abc.txt | awk '/e/{print $0}'
$ #cat abc.txt | awk '/e/{print}'
$ cat abc.txt | awk '/e/'
d e f

$ #cat abc.txt | awk '{print $0}'
$ #cat abc.txt | awk '{print}'
$ cat abc.txt | awk '1'
a b c
d e f
g h i

$ #seq 100 | awk '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"} {print $1 $3 $5}'
$ seq 100 | awk '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"}1'
1
2
  Fizz
4
    Buzz
  Fizz
7
8
  Fizz
    Buzz
11
  Fizz
13
14
  Fizz  Buzz
...中略...
$ seq 100 | awk -v OFS="" '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"}1'

$ #cat abc.txt | awk '{gsub(/a/,"A",$0);print}'
$ cat abc.txt | awk '{gsub(/a/,"A");print}'
A b c
d e f
g h i
$ #cat abc.txt | awk '{gsub(/^/,"#");print}'
$ cat abc.txt | awk 'gsub(/^/,"#")'
#a b c
#d e f
#g h i

省略しても、awkが良きに計らい解釈してくれる所が嬉しい。

省略の落とし穴

但し、省略のルールによって、awkに予想外の解釈をされることもある。

$ echo apple orange melon | awk '{if($0 ~ /apple/ || $0 ~ /orange/)print "Hit!"}'
Hit!
$ echo apple orange melon | awk '{if(/apple/ || /orange/)print "Hit!"}'
Hit!

$ echo apple orange melon | awk '{if(/apple/ ~ $1)print "Hit!";else print "Unmatched"}'
Unmatched
$ echo apple orange melon | awk '{if(($0 ~ /apple/) ~ $1)print "Hit!";else print "Unmatched"}'

$ echo 1 apple orange melon | awk '{if(/apple/ ~ $1)print "Hit!";else print "Unmatched"}'
Hit!

気の利いたawkのお節介が誘発する唯一のマイナス面と思われる。気をつけよう!

文字と数値と変数

awkは必要に応じて、文字や数値を良きに計らい自動変換してくれる。

$ echo | awk '{print 1 + 2}'
3

$ echo | awk '{print "1" + "2"}'
3

$ echo | awk '{print "a" + "b"}'
0

$ echo | awk '{print 1 2}'
12

$ echo | awk '0{print "Trueです。"}'

$ echo | awk '""{print "Trueです。"}'
$ echo | awk '"0"{print "Trueです。"}'
Trueです。

$ seq 10 | awk '{sum+=$1; print sum}'
1
3
6
10
15
21
28
36
45
55

プログラマーが面倒だと思うことは、ことごとくawk側でうまく取り計らってくれるのだ!

言語仕様

ここまでawkの概要が分かると、詳しい言語仕様を知りたくなる。よく使いそうな部分を抜粋してみた。

組み込み変数

たった7つの変数($数値、NF、NR、FS、RS、OFS、ORS)を覚えておくだけで、かなり便利に使える。


$1

FS

$2

FS

$3

FS
(NF=フィールド数)
$NF

RS
フィールド1 フィールド2 フィールド3 フィールド末尾\n←レコード1(処理中ならNR=1)
フィールド1 フィールド2 フィールド3 フィールド末尾\n←レコード2(処理中ならNR=2)
フィールド1 フィールド2 フィールド3 フィールド末尾\n←レコード3(処理中ならNR=3)
$ echo A B C | awk '{OFS=",";$1=$1;print}'
A,B,C
$ echo A B C | awk '{OFS=",";print}'
A B C

$で始まるフィールド変数は、配列のように振る舞う!

 $ echo A B C D E F G | awk '{num=5; print "$num =", "$" num, "=", $num}'
 $num = $5 = E
 $ echo A B C D E F G | awk '{print $(NF-1)}'
 F
 $ echo A B C D E F G | awk '{print $NF-1}'
 -1
制御文
if (条件) 真の処理 else 偽の処理       条件によって、真偽どちらかの処理を実行する。(else以降は省略可能)
$ echo A B C D E F G | awk '{if(length > 8)print $1,$2 "..." $NF; else print}'
A B...G

$ echo A B C D | awk '{if(length > 8)print $1,$2 "..." $NF; else print}'
A B C D
$ echo A B C D E F G | awk '{if(length > 8){s=$1 FS $2 "..." $NF; print s;} else print}'
A B...G

while (継続条件) ループ処理         継続条件が真の状態なら、ループ処理を続ける。
do ループ処理 while (継続条件)       継続条件が真の状態なら、ループ処理を続ける。(継続条件が偽でも、最低1回はループ処理を実行する)
for (初期設定; 継続条件; 増減設定) ループ処理 継続条件が真の状態なら、ループ処理を続ける。(for = 初期設定と増減設定が付属したwhile)
for (変数 in 配列) ループ処理        配列のキーを順に変数に代入しながら、ループ処理を繰り返す。
break                   for、while、doのループ処理を抜け出す。
continue                 for、while、doの次のループ処理へ移行する。
$ echo A B C D E F G | awk '{for(i=2;i<NF;i++)printf "%s ",$i; print ""}'</span>
B C D E F 
$ echo A B C D E F G | awk '{for(i=2;i<NF;i++){s=s $i; print s;}}'</span>
B
BC
BCD
BCDE
BCDEF

getline                   awkは1行ずつ自動的に読み込んでくれるが、getlineは1行読み込みを自分で制御する時に使う。
next                    awkの処理を次の行に進める。
nextfile                   awkの処理を次のファイルに進める。
exit [ 式 ]                  awkの処理を中断して、式の結果をステータスコードとして返す。(ENDパターンは実行される)

print                    指定された内容を、出力する。(改行あり)
printf                    指定された内容を、指定されたフォーマットで、出力する。(C言語のprintfフォーマットに準ずる)
$ echo 1234567| awk '{printf "%\047d\n", $0}'
1,234,567
$ echo A B C D| awk '{printf "%s\n", $0}'
A B C D
$ echo A B C D| awk '{printf "|%10s|\n", $0}'
|   A B C D|
$ echo A B C D| awk '{printf "|%-10s|\n", $0}'
|A B C D   |

delete array[index]             配列の指定されたindexを削除する。([index]指定なしだと、配列全体を削除する)
$ echo | awk '{a[1]=100; print a["1"]}'
100

$ echo | awk '{a["dog"]="one!"; a["cat"]="nya-"; for(i in a)print a[i]}'
nya-
one!
$ echo | awk '{a["dog"]="one!"; a["cat"]="nya-"; delete a["dog"]; print a["cat"], a["dog"]}'
nya- 

$ echo | awk '{a["dog"]="one!"; a["cat"]="nya-"; delete a; print a["cat"], a["dog"]}'
 
演算子
優先順位演算子意味
高い( )グルーピング
$フィールドの参照
++ --インクリメント、デクリメント
^ **べき乗(どちらも同じべき乗)
+ - !プラス、マイナス、論理否定
∗ / %乗算、除算、剰余
+ -加算と減算
半角スペース文字列連接
< <= > >= != ==関係演算子
~ !~正規表現のマッチ、非マッチ
in配列への個別アクセス
&&論理的なAND
||論理的なOR
? : 3項演算子(条件 ? 真の処理 : 偽の処理)
低い= += -= *= /= %= ^=代入(例:n+=2は、n=n+2と同等)
関数
数値関数atan2 cos exp int log rand sin sqrt srand
文字関数gsub index length match split sprintf sub substr tolower toupper
その他の関数close fflush system
関数機能引数を省略したときの意味
sub(正規表現, 変換語句, 処理対象の変数)正規表現にマッチした部分を、変換語句に、1回だけ置き換える。sub(/mac/, "Mac") == sub(/mac/, "Mac", $0)
gsub(正規表現, 変換語句, 処理対象の変数)正規表現にマッチした部分を、変換語句に、すべて置き換える。gsub(/mac/, "macintosh") == gsub(/mac/, "macintosh", $0)
index(文字列, 検索語)文字列に含まれる検索語の位置を返す。
length(文字列)文字列の長さを返す。length == length($0)
split(文字列, 配列変数, 区切り文字)文字列を区切り文字で分解して、配列変数に代入する。split("A B C", a) == split("A B C", a, FS)
sprintf("フォーマット", 値, 値...)printfの結果を文字列として返す。
tolower(文字列)小文字に変換する。tolower == tolower($0)
toupper(文字列)大文字に変換する。toupper == toupper($0)
$ echo Apple | awk '/apple/'

$ echo Apple | awk 'tolower ~ /apple/'
Apple
$ echo Apple | grep 'apple'

$ echo Apple | grep -i 'apple'
Apple

参考ページ

さらに詳しいawkの仕様については、以下のページがたいへん参考になりました。(素晴らしい情報に、感謝です!)


「awkは書かねぇ、たった一行」って何?

第37話「三方一両損」(落語の小噺の落ち)

「お〜かぁ〜(大岡)食わねぇ、たった一膳(越前)」。

第37話「三方一両損」

しまった...。タイトルを「多くは書かないawkの使い方」にしておけば良かった。

コメント
22件
トラックバック
4件
ブックマーク
0 users
zariganitosh
zariganitosh