awkはパターンマッチやテキスト処理が得意なプログラミング言語です。awkはプログラムのコンパイルが必要ないインタプリタ言語になります。
awkコマンドは、テキストデータである入力ファイルを行ごと処理することができ、一行で記述できる程度のプログラム量でも様々な処理を行うことができます。また、awkはインタプリタ言語であるため、PerlやRuby、Pythonのようにシバン(#!)を用いれば、自己完結型のスクリプトを作成することができます。
awkプログラムについて
awkプログラムは基本的にパターン(pattern)とアクション(action)で成り立つルールを記述していきます。awkが入力ファイルから行を読み込んだときに当てはまるルールの処理を行います。ルールは基本的に以下のように記述できます。
1 2 |
pattern { action } pattern { action } |
また、awkプログラムはルールのほかに関数を定義する場合もあります。関数の構文は以下のように記述できます。
1 |
function name(args) { action } |
また、awkプログラムの文は改行もしくはセミコロン(;)で区切ることができます。
1 2 3 4 |
{ print $0 x=3; print x } |
また、上のプログラムは入力ファイルの行を出力した後、次の行に3を出力します。なので、入力ファイルの全ての行の後に3という数字が挿入される結果が得られます。
コマンド例と実行結果
1 2 3 4 5 |
$ printf 'data1\ndata2\n' | awk '{ print $0; x=3; print x }' data1 3 data2 3 |
パターンがないルール
パターンがないルールは、awkが行を読み込むときに常に処理されます。
testdata.txt
1 2 3 4 5 |
data1 1 data2 3 data3 5 data4 7 data5 9 |
コマンド例と実行結果
1 2 3 4 5 6 |
$ awk '{print $2}' testdata.txt 1 3 5 7 9 |
$1,$2,...の変数は行がスペースやタブで区切られたデータ列とみて、1列目、2列目、...を表します。
この区切り文字は組み込み変数FSで変更できます。例えば、testdata.txtがcsvのようなファイルの場合は
1 |
awk 'BEGIN {FS="," } { print $2 }' |
または、awkコマンドのFオプションを用いて、
1 |
awk -F, '{ print $2 }' |
のようにすることで同様な結果を得ることができます。
アクションがないルール
アクションがないルールにはデフォルトのアクションが設定されています。デフォルトのアクションは、
1 |
{ print $0 } |
になります。
testdata.txt
1 2 3 4 5 |
data1 1 data2 3 data3 5 data4 7 data5 9 |
コマンド例と実行結果
1 2 3 4 |
$ awk '$2>3' testdata.txt data3 5 data4 7 data5 9 |
また、BEGINやENDのような特別なパターンにはデフォルトのアクションはありません。
基本的な特別なパターン
awkプログラムのパターンにはBEGINとENDの特別なパターンがあります。
BEGINはawkが処理し始める最初の処理を記述できます。主に、変数の初期化などを行うことができます。
例えば、以下は行番号を出力するコマンド例になります。
fruits.txt
1 2 3 |
apple banana melon |
コマンド例と実行結果
1 2 3 4 |
$ awk 'BEGIN { x=1 } { print x,$0; x++ }' fruits.txt 1 apple 2 banana 3 melon |
行番号を表示する場合は、もっと簡単に記述できます。組み込み変数NRはawkが処理した行数が設定されています。このNRを利用すると
1 2 3 4 |
$ awk '{print NR,$0}' fruits.txt 1 apple 2 banana 3 melon |
のように同様の結果を得ることができます。
同様に、ENDはawkの処理が終了するときの最後の処理を記述できます。
例えば、以下は行を逆順にする例になります。
コマンド例と実行結果
1 2 3 4 |
$ awk '{ lines[NR]=$0 } END { for(i=NR; i>0; i--) print lines[i] }' fruits.txt melon banana apple |
また、他の特別なパターンにBEGINFILEとENDFILEがあります。これはファイルの読み込み開始と読み込み終了の処理を記述できます。
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 |
$ echo 'lemon' | awk '{ print $0 } BEGINFILE { print "--BEGIN FILE--"} ENDFILE { print "--END FILE--" } ' - fruits.txt --BEGIN FILE-- lemon --END FILE-- --BEGIN FILE-- apple banana melon --END FILE-- |
awkプログラムの実行
awkコマンドは、awkのプログラムファイルを用いない場合、
1 |
awk [option]... 'program-text' inputfile ... |
のように利用できます。そして、inputfileがない場合は標準入力から読み込まれます。
awkコマンドでinputfileがないときにパイプやリダイレクトを用いていない場合、端末から入力を読み込むために入力待ちの状態になります。これはCtrl+dで入力を終了できます。ただし、BEGINパターンの文の場合は、端末からの入力を行わずに終了します。
awkのプログラムをファイルから実行させたい場合は、-fオプションを用います。
1 |
awk [option]... -f program-file inputfile... |
また、自己完結型のスクリプトを作成する場合も-fオプションを用います。
script.awk
1 2 3 4 5 |
#!/usr/bin/awk -f BEGIN { print "test script" } |
スクリプトを実行するために、chmodコマンドで実行権限を付与すると、スクリプトを実行できるようになります。
1 2 3 |
$ chmod u+x script.awk $ ./script.awk test script |
ファイルの最初の行にある#!(シバン,shebang)の後に、インタプリタ言語のプログラムを指定することで、その後の行はそのインタプリタでのプログラムとして解釈し、実行できます。
#!のプログラムの指定は絶対パスで指定し、#!のあるスクリプトを実行させた場合、シェルは、#!の行、スクリプトファイル名、実行するときのスクリプトファイルにある引数となるように実行されます。
つまり、上のscript.awkの実行コマンド
$ ./script.awk
は
$ /usr/bin/awk -f ./script.awk
のように解釈されて実行されます。
awkの変数と定数
awkの変数の初期値は空の文字列になります。また、変数を数値として扱う場合、その初期値は0になります。
script2.awk
1 2 3 4 5 6 |
#!/usr/bin/awk -f BEGIN { varname="test variable" print varname } |
コマンド例と実行結果
1 2 |
$ ./script2.awk test variable |
コマンドラインでも変数を代入することができます。一つは-vオプションを用いて変数を代入する方法があります。もう一つはコマンドライン引数に変数の代入を行う方法になります。
testdata.txt
1 2 3 4 5 |
data1 1 data2 3 data3 5 data4 7 data5 9 |
コマンド例と実行結果(-vオプションを用いた場合)
1 2 3 4 5 6 7 8 9 10 11 12 |
$ awk -v num=1 '{print $num}' testdata.txt data1 data2 data3 data4 data5 $ awk -v num=2 '{print $num}' testdata.txt 1 3 5 7 9 |
コマンド例と実行結果(コマンドライン引数に変数を代入する場合)
1 2 3 4 5 6 7 8 9 10 11 |
$ awk '{print $num}' num=1 testdata.txt num=2 testdata.txt data1 data2 data3 data4 data5 1 3 5 7 9 |
組み込み変数
awkで利用する組み込み変数の一部紹介します。変数$nはほしい列を抜き出すのに便利な変数になります。また、変数NRは、行ごとの処理内容を配列に保持したり、終了時の処理時にその配列から値をすべて取り出すような場合に便利な変数です。
組み込み変数を覚えておくとawkをより活用できるようになります。
組み込み変数
変数 | 意味 |
$n (nは数字) | $0は行全体を表します。 $1,$2,$3,…は空白文字を区切り文字とした列を表します。 区切り文字は変数FSや-Fオプションで変更可能 |
NR | プログラム開始時からの処理した行数 |
NF | 入力行の列数 |
FS | 列の区切り文字 デフォルトは空白文字 |
RS | 行の区切り文字 デフォルトは改行 |
OFS | print文等の出力列の区切り文字 デフォルトはスペース |
ORS | 出力行の区切り文字 デフォルトは改行 |
OFMT | print文の数値の出力形式 デフォルトは%.6g |
CONVFMT | 数値から文字列の変換の形式 デフォルトは%.6g |
FILENAME | 入力ファイルの名前 |
FNR | 現在のファイルの現在の行数 |
ARGC | コマンドラインの引数の数 |
ARGV | コマンドラインの値の配列 添え字は0からARGC-1まで |
ENVIRON | 環境変数の連想配列 環境変数PATHの値がほしい場合は、ENVIRON["PATH"]のように指定 |
定数
awkの定数は主に数値、文字列、正規表現になります。
数値は以下のようなものになります。
1 2 |
1234 1.234e+5 |
文字列は以下のようなものになり、ダブルクォーテーションで文字列を囲みます。
1 |
"Hello World" |
正規表現は/.../で文字列を囲んだものになります。この正規表現の定数は'~'や'!~'の右辺として利用します。また、/.../の文字列を単独の場合、その意味は'$0 ~ /.../'のような意味になります。
1 2 |
"Hello World" ~ /or/ /or/ # $0 ~ /or/ と同じ |
文字列と数値の変換
awkは文字列と数値を文脈によって、変換します。例えば、
1 |
awk 'BEGIN{ print 1 2 + 3 }' |
の結果は
1 |
15 |
のように表示されます。これはまず、数値1と数値2が文字列として変換・結合されます。その後、前の結果の文字列12は数値12に変換され、数値の3が加算されます。結果として、15と表示されます。これは変数でも同様です。
1 2 |
$ awk 'BEGIN{ one=1; two=2; three=3; print one two + three }' 15 |
文字列から数値の変換について、文字列から数値の変換は文字列の最初の数字が数値として変換されます。例えば、
1 |
awk 'BEGIN{ print "12abc" + 0 }' |
は
1 |
12 |
のようになります。また、数値として変換できないような文字列は0として扱われます。
1 2 |
$ awk 'BEGIN{ print "abc" + 0 }' 0 |
awkの文
print文/printf文
print文やprintf文は、文字や数値を出力できます。
print文の構文は
1 |
print item1, item2,... |
になります。引数全体やアイテムの項目は、'()'のような括弧でくくることができます。括弧の有無によって、'>'の演算子をリダイレクトとして扱わずに、関係の演算子で扱うようにすることができます。
アイテムの引数は組み込み変数OFSで設定された区切り文字で、区切られて出力されます。また、組み込み変数OFSのデフォルトはスペースになります。
コマンド例と実行結果
1 2 3 4 |
$ awk 'BEGIN{ print "aaa", "bbb", "ccc" }' aaa bbb ccc $ awk 'BEGIN{ OFS=","; print "aaa", "bbb", "ccc" }' aaa,bbb,ccc |
printf文の構文は
1 |
printf format, item1, item2,... |
であり、これはC言語のprintf関数のように、引数はフォーマット文字列とフォーマット指定子に入る値になります。
コマンド例と実行結果
1 2 |
$ awk 'BEGIN{ printf "%s %s %s\n", "aaa", "bbb", "ccc" }' aaa bbb ccc |
リダイレクション(redirection)
print文やprintf文はシェルのように出力をリダイレクト(redirect)したり、パイプ(pipe)に送ることができます。
例えば、特定のファイルに出力する場合は
1 2 3 |
$ awk 'BEGIN{ print "aaa", "bbb", "ccc" > "file.txt" }' $ cat file.txt aaa bbb ccc |
のようにファイルにリダイレクトすることができます。また、パイプを用いる場合は以下のように利用できます。
testdata.txt
1 2 3 4 5 |
data1 1 data2 3 data3 5 data4 7 data5 9 |
コマンド例と実行結果
1 2 3 4 5 6 7 |
$ awk '{ print $2 | "sort -r > file.txt" }' testdata.txt $ cat file.txt 9 7 5 3 1 |
リダイレクトやパイプを利用すると、ファイルやパイプはオープンされた状態になります。これはawkが終了するまで、クローズされません。多くのファイルをオープンするとエラーが発生するため、多くのファイルをオープンする場合などは、必要な処理が終了した後にできるだけすぐにclose関数を呼び出し、ファイルやパイプをクローズします。
また、close関数で閉じるファイルやパイプの指定は、リダイレクトやパイプを使用した文字列を引数にします。なので、変数などにファイル名やコマンドを代入するとよいでしょう。
script3.awk
1 2 3 4 5 6 7 |
#!/usr/bin/awk -f { command = "od -tx1c > " "dir/data_" sprintf("%05d", NR) ".txt" print $0 | command close(command) } |
コマンド例と実行結果
1 2 3 4 5 6 7 8 |
$ mkdir dir $ ./script3.awk testdata.txt $ ls dir data_00001.txt data_00002.txt data_00003.txt data_00004.txt data_00005.txt $ cat dir/data_00003.txt 0000000 64 61 74 61 33 20 35 0a d a t a 3 5 \n 0000010 |
条件分岐
if文は
1 |
if (condition) then-body [else else-body] |
のような構文になります。
if-statement.awk
1 2 3 4 5 6 7 8 9 10 |
#!/usr/bin/awk -f { printf "%s", $0 if (NR % 2 == 0){ print ": even" } else { print ": odd" } } |
コマンド例と実行結果
1 2 3 4 5 6 |
$ ./if-statement.awk testdata.txt data1 1: odd data2 3: even data3 5: odd data4 7: even data5 9: odd |
if文の内容の文が一つならば、
1 2 3 4 |
if (NR % 2 == 0) print ": even" else print ": odd" |
のように'{}'で囲む必要はありません。また、そのまま一文で記述するならば、上のスクリプトは以下のように記述し実行できます。
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 |
$ awk '{printf "%s",$0; if(NR%2==0) print ": even"; else print": odd"}' testdata.txt data1 1: odd data2 3: even data3 5: odd data4 7: even data5 9: odd $ awk '{printf "%s",$0; if(NR%2==0){print ": even"}else{print": odd"}}' testdata.txt data1 1: odd data2 3: even data3 5: odd data4 7: even data5 9: odd |
switch文は
1 2 3 4 5 6 |
switch { case value/regexp: case-body default: default-body } |
のような構文になります。switch文はC言語のswitch文のように利用できます。
switch-statement.awk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/usr/bin/awk -f { switch($1){ case "data1": print "--", $1, $2, "--" break case /data3/: print "<<", $1, $2, ">>" break default: print "##", $1, $2, "##" break } } |
コマンド例と実行結果
1 2 3 4 5 6 |
$ ./switch-statement.awk testdata.txt -- data1 1 -- ## data2 3 ## << data3 5 >> ## data4 7 ## ## data5 9 ## |
ループ
for文は
1 2 3 |
for (initialization; condition; increment) { body } |
のような構文になります。
コマンド例と実行結果
1 2 3 4 |
$ awk 'BEGIN{ for(i=1; i<=3; i++) print i}' 1 2 3 |
また、for文の別の形式として
1 2 3 |
for (i in array) { body } |
のような構文もあります。これは配列の全要素に対して、アクセスするのに便利な構文になります。
コマンド例と実行結果
1 2 3 |
$ awk 'BEGIN{ for(elem in ARGV) print elem, ARGV[elem]}' testdata.txt 0 awk 1 testdata.txt |
while文は
1 2 3 |
while (condition) { body } |
のような構文になります。
コマンド例と実行結果
1 2 3 4 5 6 |
$ awk 'BEGIN{ i=0; while(i<5){print i;i++} }' 0 1 2 3 4 |
do~while文は
1 2 3 |
do { body } while (condition) |
のような構文になります。
コマンド例と実行結果
1 2 3 4 5 6 |
$ awk 'BEGIN{ i=0; do {print i; i++} while(i<5) }' 0 1 2 3 4 |
continue文はループ処理の残りの部分を飛ばし、即座に次のループに移行できます。
break文は、ループ処理の抜け出しやswitch文の抜け出しに利用できます。
next文/nextfile文
next文は呼び出した時点で、現在行の処理を飛ばして、次の行の処理に移行します。
コマンド例と実行結果
1 2 3 4 5 6 7 |
$ awk '{if(FNR%2==0) next; print $0}' testdata.txt testdata.txt data1 1 data3 5 data5 9 data1 1 data3 5 data5 9 |
nextfile文も同様に、現在のファイルの処理を飛ばして、次のファイルの処理に移行します。
コマンド例と実行結果
1 2 3 |
$ awk '{if(FNR%2==0) nextfile; print $0}' testdata.txt testdata.txt data1 1 data1 1 |
exit文
exit文はawkの処理を終了し、ENDパターンの処理を行い、終了ステータスを返します。また、ENDパターン内でのexit文はそのままawkの処理が終了します。
コマンド例と実行結果
1 2 3 |
$ awk '{if(FNR%2==0) exit 1; print $0} END{print "exit"}' testdata.txt data1 1 exit |
getline文
getline文は次の行を読み込みます。next文と異なり、処理は継続して行われます。また、getline文は引数に変数を取ることができます。変数が存在する場合はその変数に行の値が代入されます。引数が存在しない場合は$0の変数が変更されます。また、NR等の変数も書き換わります。
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 |
$ awk '{if(NR%2==0){getline line; print "getline:",line}; print "not getline:",$0}' testdata.txt not getline: data1 1 getline: data3 5 not getline: data2 3 getline: data5 9 not getline: data4 7 $ awk '{if(NR%2==0){getline; print "getline:",$0}; print "not getline:",$0}' testdata.txt not getline: data1 1 getline: data3 5 not getline: data3 5 getline: data5 9 not getline: data5 9 |
getline文はリダイレクトを用いて、ファイルの行を読み込むことができます。
コマンド例と実行結果
1 2 3 4 5 6 |
$ awk 'BEGIN{ while(getline line < "testdata.txt"){print line} }' data1 1 data2 3 data3 5 data4 7 data5 9 |
他にも、パイプから行を読み込むこともできます。
コマンド例と実行結果
1 2 3 4 5 6 |
$ awk 'BEGIN{ while("sort -r testdata.txt" | getline line ){print line} }' data5 9 data4 7 data3 5 data2 3 data1 1 |
ファイルのオープンやパイプからの読み込みも一度使用したら、awkが終了するまでオープンされた状態になります。これはprint文のリダイレクトのようにclose関数でファイルやパイプをクローズできます。
awkのパターン
awkのルールは
1 |
pattern { action } |
になります。このパターンに当てはまる式は大まかに3つあります。それは正規表現と式と範囲があります。
正規表現(regular expression)
正規表現は文字列を/.../のように囲んだものになります。パターンがこの正規表現にマッチしている行のときにアクションが実行されます。
testdata.txt
1 2 3 4 5 |
data1 1 data2 3 data3 5 data4 7 data5 9 |
コマンド例と実行結果
1 2 3 4 |
$ awk '/data[234]/{print $0}' testdata.txt data2 3 data3 5 data4 7 |
式(expression)
パターンの式の値が数値の0ではない、または、空文字列ではない場合にアクションが実行されます。
コマンド例と実行結果
1 2 3 |
$ awk 'NR%2==0{print $0}' testdata.txt data2 3 data4 7 |
範囲(range)
パターンをカンマで区切ることで始まりのパターンから終わりのパターンまでの範囲に対して、アクションを実行することができます。
コマンド例と実行結果
1 2 3 4 |
$ awk '/data2/,/data4/{print $0}' testdata.txt data2 3 data3 5 data4 7 |
その他のパターン
その他のパターンとして以下の特別のパターンがあります。
特別なパターン
パターン | 意味 |
BEGIN | 開始時の処理 |
END | 終了時の処理 |
BEGINFILE | ファイルの読み込み開始時の処理 |
ENDFILE | ファイルの読み込み終了時の処理 |
記述なし | 全ての行に対する処理 |
awkの関数
関数の定義
関数は以下のように定義することができます。
1 2 3 |
function name([parameter-list]){ body } |
script4.awk
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/awk -f BEGIN { myfunc("test") } function myfunc(string){ print "myfunc:", string } |
awkで関数での変数のローカル宣言を行いたい場合は、関数の引数にその変数を追加することで、ローカル宣言のように扱うことができます。また、関数の引数かまたは関数のローカル変数かを区別するために、ローカル変数に余分な空白を追加するようなコーディング規則があります。
script5.awk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/usr/bin/awk -f BEGIN { i=1234 print "start i=", i myfunc("test") print "end i=", i } function myfunc(string, i){ for (i=1; i<=3; i++) { print "myfunc:", string, "i=", i } } |
コマンド例と実行結果
1 2 3 4 5 6 |
$ ./script5.awk start i= 1234 myfunc: test i= 1 myfunc: test i= 2 myfunc: test i= 3 end i= 1234 |
関数はreturn文を利用することで値を返すこともできます。
script6.awk
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/awk -f BEGIN { print myfunc(5) } function myfunc(num){ return num ** 2 } |
コマンド例と実行結果
1 2 |
$ ./script6.awk 25 |
組み込み関数
組み込み関数については、The GNU Awk User’s Guide: Built-in Functionsで確認できます。