■レシピ7.10~7.16 awk
【例1】手始め
・awkコマンドの第一引数はawkコード
awkコードのキーワードや変数をbashが解釈させず、bashにとっては無意味な文字列と解釈させるために単一引用符で囲う。
・awkコマンドの第二引数はawkに食わせるテキストファイル名
・awkコマンドへの標準入力はテキストストリームとしてawkコマンドの第二引数の代わりにawkに入力される。
下記はすべて同じ結果になる。
awk '{print $1}' text.txt ←awkコマンドの第2引数はawkに食わせるテキストファイル名
awk '{print $1}' < text.txt ←awkコマンドの第2引数の代わりに食わせるテキストファイルをリダイレクトしてもよい
cat text.txt|awk '{print $1}' ←awkコマンドの標準入力にパイプラインでテキストストリームで渡してもよい
【例2】awkコマンドは単一引用符で囲む (理由はbashに一つの文字列と解釈させるため)
$ ll
total 8
-rw-r--r-- 1 root root 7 Jul 29 05:44 chinko.txt
-rw-r--r-- 1 root root 5 Jul 29 05:44 unko.txt
$ ll|awk '
> {for (i=NF;i>0;i--)
> {printf "%s ", $i;}
> printf "\n" }
> '
8 total
chinko.txt 05:44 29 Jul 7 root root 1 -rw-r--r--
unko.txt 05:44 29 Jul 5 root root 1 -rw-r--r--
・bashは引用符が終端していないと、コマンドがまだ完成していないことを示す「>」プロンプトを表示して入力の継続を促す。
上記の【例2】は下記のように1行にすることもできる。
$ ll|awk '{for (i=NF;i>0;i--) {printf "%s ", $i;} printf "\n" }'
8 total
chinko.txt 05:44 29 Jul 7 root root 1 -rw-r--r--
unko.txt 05:44 29 Jul 5 root root 1 -rw-r--r--
・引用符以外にパイプライン直後にエンターを押しても「>」になる。
$ ll|
> awk '
> {for (i=NF;i>0;i--)
> {printf "%s ", $i;
> } printf "\n"
> }
> '
8 total
chinko.txt 05:44 29 Jul 7 root root 1 -rw-r--r--
unko.txt 05:44 29 Jul 5 root root 1 -rw-r--r--
・「>」継続行内では、ホワイトスペースが入っても意味が変わらない任意の箇所で改行が許される。
$ ll|awk '{
> for (i=NF;i>0;i-
> -) {pri
> ntf "%s ", $i;} printf "\n" }'
awk: cmd. line:2: for (i=NF;i>0;i- ←awk変数iへの単項演算子「--」の途中「-」と「-」の間にホワイトスペースは禁止
awk: cmd. line:2: ^ unexpected newline or end of string
awk: cmd. line:3: ntf "%s ", $i;} printf "\n" } ←awk組込関数「printf」の「pri」と「ntf」の間にホワイトスペースは禁止
awk: cmd. line:3: ^ syntax error
上記の【例2】のawkコマンド第一引数の内容(awkプログラム)を下記のようにテキストファイルにしてawkコマンドの「-f」オプションで指定して実行することもできる。
$ cat test.awk
{
for(i=NF;i>0;i--) {printf "%s ", $i;}
printf "\n"
}
$ ll|awk -f test.awk
12 total
chinko.txt 05:44 29 Jul 7 root root 1 -rw-r--r--
test.awk 06:49 29 Jul 52 root root 1 -rw-r--r--
unko.txt 05:44 29 Jul 5 root root 1 -rw-r--r--
■awkの文法
・awkプログラムの本文は引数テキストの全行に対するイテレーションである。
このイテレーションの際、テキスト各行をスペースで区切った第一フィールドから順にawkの組込変数$1、$2、・・・、$NFに格納する(NFはawkの組込変数で最終フィールドの番号を格納する)
$0はテキスト行一行をまるごと格納する
・awkプログラムの変数は代入も参照も「x」のように書いてよく、「$x」としなくてよい。
・awkプログラムの本文(コードブロック)はいくつあってもよく、それぞれのコードブロックを中括弧で囲む。
【文法】 awk '{あ } {い } ・・・ {ん }' 第二引数
上記の場合ではテキスト行に対する各イテレーション内で、コードブロック{あ } {い } ・・・ {ん }の順番で処理されることを意味する
・awkプログラムのコードとコードブロック
下記の【例3】と【例4】の結果は同じになる。
$ ll
total 16
-rw-r--r-- 1 dagyah users 0 Jul 29 07:02 a
-rw-r--r-- 1 root root 7 Jul 29 05:44 chinko.txt
-rw-r--r-- 1 root root 52 Jul 29 06:49 test.awk
-rw-r--r-- 1 root root 116 Jul 29 07:01 test2.awk
-rw-r--r-- 1 root root 5 Jul 29 05:44 unko.txt
【例3】 一つのコードブロック内でコードは左から右の順へ実行される
$ ll|awk '{ x=$1; $1=$NF ;$NF=x ;print $0 }'
16 total
a 1 dagyah users 0 Jul 29 07:02 -rw-r--r--
chinko.txt 1 root root 7 Jul 29 05:44 -rw-r--r--
test.awk 1 root root 52 Jul 29 06:49 -rw-r--r--
test2.awk 1 root root 116 Jul 29 07:01 -rw-r--r--
unko.txt 1 root root 5 Jul 29 05:44 -rw-r--r--
【例4】 一つのawkプログラム内でコードブロックは左から右の順へ実行される
$ ll|awk ' {x=$1} {$1=$NF} {$NF=x} {print $0 } '
16 total
a 1 dagyah users 0 Jul 29 07:02 -rw-r--r--
chinko.txt 1 root root 7 Jul 29 05:44 -rw-r--r--
test.awk 1 root root 52 Jul 29 06:49 -rw-r--r--
test2.awk 1 root root 116 Jul 29 07:01 -rw-r--r--
unko.txt 1 root root 5 Jul 29 05:44 -rw-r--r--
・awkプログラムのコードブロックには、入力が読み取られる前に実行されるコードブロックとして「BEGIN { } 」とプログラムの残りの部分が完了したあとに一回だけ実行するコードブロックとして「END { } 」がある。
【文法】 awk 'BEGUIN {び } {あ } {い } ・・・ {ん } END {ぴ}' 第二引数
・printf組込関数
awkにはC言語とほぼ同様の使い方ができるprintf組込関数がある
・forループ
awkにはC言語とほぼ同様の使い方ができるforループが組み込まれている。
【文法】 for (初期値;論理式;算術式) { イテレーションごとに実行する文 }
また、forループでは下記のようにbashと似た使い方もできる
【文法】 for (i in 連想配列名) { イテレーションごとに実行する文 }
awkのコードブロックの範囲を示す中括弧と、forループの実行文の範囲を示す中括弧は当然別物。
※bashのように下記のようなことはできなかった。
for (i in 1 2 3) {printf "%s %s", i , $i }
【例5】
$ ll|awk '{for (i=1;i<=NF;i++) {printf "★%s %s ",i,$i} {printf "\n"}}'
★1 total ★2 16
★1 -rw-r--r-- ★2 1 ★3 dagyah ★4 users ★5 0 ★6 Jul ★7 29 ★8 07:02 ★9 a
★1 -rw-r--r-- ★2 1 ★3 root ★4 root ★5 7 ★6 Jul ★7 29 ★8 05:44 ★9 chinko.txt
★1 -rw-r--r-- ★2 1 ★3 root ★4 root ★5 52 ★6 Jul ★7 29 ★8 06:49 ★9 test.awk
★1 -rw-r--r-- ★2 1 ★3 root ★4 root ★5 116 ★6 Jul ★7 29 ★8 07:01 ★9 test2.awk
★1 -rw-r--r-- ★2 1 ★3 root ★4 root ★5 5 ★6 Jul ★7 29 ★8 05:44 ★9 unko.txt
$ ll|awk '{for (i in 1 2 3 4 5 6 7 8 9) {printf "★%s %s ",i,$i} {printf "\n"}}'
awk: {for (i in 1 2 3 4 5 6 7 8 9) {printf "★%s %s ",i,$i} {printf "\n"}} ←エラー
awk: ^ syntax error
awk: {for (i in 1 2 3 4 5 6 7 8 9) {printf "★%s %s ",i,$i} {printf "\n"}}
awk: ^ syntax error
【例6】END { } コードブロック
$ ll
total 16
-rw-r--r-- 1 dagyah users 0 Jul 29 07:02 a
-rw-r--r-- 1 root root 7 Jul 29 05:44 chinko.txt
-rw-r--r-- 1 root root 52 Jul 29 06:49 test.awk
-rw-r--r-- 1 root root 116 Jul 29 07:01 test2.awk
-rw-r--r-- 1 root root 5 Jul 29 05:44 unko.txt
$ ll|awk '{sum += $5} END {print sum}'
180
※上記でsumの初期値はNullである。awkの変数の初期値はNullであることが保証されている
※上記のsum変数は、ひとつのawkプログラム内で(コードブロックをまたいで)グローバルである。
したがってEND{}コードブロックの前に実行されるコードブロック内でテキスト行イテレーション中最初から最後まで共有される。さらに、最後に実行されるEND{}コードブロック内でもsum変数の値は最後まで共有される。
【例7】 正規表現マッチングと処理の制御
下記のように書くと、テキスト行イテレーションで正規表現にマッチした行のときだけ続きのコードブロックを実行する
【文法】 /正規表現/{コードブロック}
$ ll|awk '/^total/{getline} {sum += $5} END {print sum}'
180
※上記のgetlineはawkの組込コマンド。意味は現在のテキスト行を飛ばして次のテキスト行を読み取る。
【例8】 awkの連想配列
$ ll
total 16
-rw-r--r-- 1 dagyah users 0 Jul 29 07:02 a
-rw-r--r-- 1 root root 7 Jul 29 05:44 chinko.txt
-rw-r--r-- 1 root root 52 Jul 29 06:49 test.awk
-rw-r--r-- 1 root root 116 Jul 29 07:01 test2.awk
-rw-r--r-- 1 root root 5 Jul 29 05:44 unko.txt
$ cat test2.awk
NF > 7 { ←「論理式 {コードブロック}」。この論理式「NF > 7」はコードブロック修飾子に含まれる
user[$3]++ ←「$3」をキーにした連想配列 user[ ] (整数値(初期値0))をテキスト行イテレーションごとにインクリメントしてる
}
END {
for (i in user) ←「for (変数 in 連想配列名)」
{
printf "%s owns %d files\n",i,user[i] ←C言語のprintf関数と同様に「%s」は変数「i」、「%d」は「user(i)」に対応している
}
}
$ ll|awk -f test2.awk
dagyah owns 1 files
root owns 4 files
※user[]は宣言(連想配列としての)や定義(整数型としての)をしないでいきなり使える!
※user[dagyah]やuser[root]の初期値はすべて0であることが保証されている
※user[]の要素数は上記の【例8】の場合は2つとなったが、user[]の参照時点では当然未定。
つまりawkの連想配列は可変長配列
※for (変数 in 連想配列名)から返されるデータの順序は決まっておらず、ハッシュテーブルに都合の良い順序になる。
※【例7】の「/正規表現/」もコードブロック修飾子?
※【例6】の「for ()」はコードブロック修飾子ではなさそう。なぜならfor文をコードブロックの外に書くとエラーになる。一方、「NF > 7」や「/正規表現/」はコードブロックの外に書ける
→GNU AWK ユーザガイド http://gauc.no-ip.org/translation/gawk.html#SEC114
では、コードブロック「{ }」の前に付けてコードブロックを修飾するキーワードのことを、「パターン」と呼称している。
・awkのパターンの定義
awkのパターンはルールの実行を制御する。 あるルールはそのパターンとカレント入力レコードがマッチしたときに実行される。
・パターンの種類
/正規表現/
比較演算式
ENDとBEGIN
※パターンでは、正規表現と比較演算式を組み合わせてさらに&&や||などを使って合成論理演算にすることもできる。
【例9】 関数の定義
$ cat test3.awk
function max(arr,big)
{
big=0;
for (i in user)
{
if (user[i] > big) { big=user[i];}
}
return(big)
}
NF > 7 {
user[$3]++
}
END {
maxm = max(user);
for (i in user)
{
scaled = 60 * user[i] / maxm ;
printf "%-10.10s [%8d]:",i,user[i]
for (i=0; i<scaled; i++) {
printf "#";
}
printf "\n";
}
}
$ ll -R /var|awk -f test3.awk
ntp [ 2]:#
dagyah [ 20]:##
uucp [ 1]:#
mail [ 1]:#
news [ 5]:#
messagebus [ 1]:#
statd [ 3]:#
man [ 131]:########
at [ 3]:#
avahi [ 3]:#
postfix [ 43]:###
nobody [ 1]:#
root [ 1067]:############################################################
wwwrun [ 3]:#
lp [ 3]:#
【例10】 検索文字列を含む段落の表示
$ cat test.txt
ちんこしこしこ
ちんこでなぐれ
ちんこぱーーんち
うんこばきばき
うんこぶりぶり
ぶりぶりんぐ
おまんこおめこ
かんとうではおまんこ
かんさいではおめこ
$ keyphrase="ちんこ";awk '/'"${keyphrase}"'/ {flag=1} { if (flag == 1) { print $0 }} /^$/ { flag=0 }' test.txt
ちんこしこしこ
ちんこでなぐれ
ちんこぱーーんち
$ keyphrase="うんこ";awk '/'"${keyphrase}"'/ {flag=1} { if (flag == 1) { print $0 }} /^$/ { flag=0 }' test.txt
うんこばきばき
うんこぶりぶり
ぶりぶりんぐ
$ keyphrase="まんこ";awk '/'"${keyphrase}"'/ {flag=1} { if (flag == 1) { print $0 }} /^$/ { flag=0 }' test.txt
おまんこおめこ
かんとうではおまんこ
かんさいではおめこ
$ keyphrase="んこ";awk '/'"${keyphrase}"'/ {flag=1} { if (flag == 1) { print $0 }} /^$/ { flag=0 }' test.txt
ちんこしこしこ
ちんこでなぐれ
ちんこぱーーんち
うんこばきばき
うんこぶりぶり
ぶりぶりんぐ
おまんこおめこ
かんとうではおまんこ
かんさいではおめこ
■bashからawkのパターンにbash変数値をわたす
bash変数をセットしたシェル上で、
awk '/'"${keyphrase}"'/ {コードブロック}'
のようにbash変数「$」つきで指定して「'"」「"'」で囲う
■bashからawkのコードブロック内のawk変数にbash変数値をわたす
bash変数をセットしたシェル上で、
awk -v awk変数="${bash変数}" '{コードブロック}' 第二引数
のように「-v」オプションを使う。
※【例9】や【例10】のように関数を使ったり、awkにbashから変数を渡して複雑な処理をさせる場合は、むしろほぼ同じことをできるperlを使うのが常套手段。
awkはワンライナーで使われることが多い。