awk(オーク)*1とは数値や文字列が規則正しく並んでいるテキストファイルに対して,以下の操作を行うのに適したコマンド(スクリプト言語)で,1977年から利用されている最古参のスクリプト言語。
文法はC言語に似ているんで,比較的覚えやすいかな。
Hello worldを表示するプログラムは次のようになる。スクリプトファイルの拡張子は特に決まりがない。
hello.awk
{ print "Hello world!" }
awkはgrepやsedのようなフィルタコマンドとして作られているので,何らかの入力が必要。
$ echo | awk -f hello.awk
コマンドラインにプログラムを直接指定することもよく利用される。
$ echo | awk '{ print "Hello world!" }'
ls -lの結果から所有者とグループとファイル名を表示する。
$ ls -l | awk '{ print $3, $4, $9 }' yueno Administrator hello.awk yueno Administrator sample.awk
ファイル名がhello.awkだけ表示する。
$ ls -l | awk '$9=="hello.awk" { print $3, $4, $9 }' yueno Administrator hello.awk
/etc/passwdを:をデリミタとしてユーザ名とホームディレクトリのみを表示する。
$ cat /etc/passwd | awk -F: '{print $1, $6}' yueno /home/ueno
/etc/passwdのユーザ名とホームディレクトリを整形して表示する。
$ cat /etc/passwd | awk -F: '{printf("%-8s %s\n", $1, $6)}' yueno /home/ueno
df実行結果から2行目以降の第2カラムの合計を表示する
$ df | awk 'NR>=2 {z+=$2}; END {print z}' 2850302964
プログラムはコマンドラインに直接指定するか,-fオプションでプログラムファイル名を指定します。
入力ファイルは標準入力または引数としてファイル名を渡す。
$ cat data.txt | awk '{ print $1, $2 }' # プログラムを直接指定 $ cat data.txt | awk -f sample.awk # プログラムファイルで指定 $ awk -f sample.awk data1.txt data2.txt # 入力データを引数で指定
bashを使っている場合,プログラムファイルの先頭を#!/usr/bin/awk -fとすると,プログラムファイルをコマンドとして直接実行することが出来る。
sample.awk
#!/usr/bin/awk -f { print $1, $8 }
$ chmod 775 sample.awk $ ./sample.awk
入力データはレコードとフィールドに分割される。
レコードは行,フィールドは空白文字で区切られたカラムに相当する。レコードとフィールドの区切り文字は,ビルトイン変数RSとFSで変更することが出来る。
field1 field2 field3 # record1 field1 field2 field3 # record2 field1 field2 field3 # record3
レコード全体は$0,フィールドは$1,$2,$3,...で参照出来る。
{ print $0 } # レコード全体を表示 { print $1, $2, $3 } # 1,2,3フィールドを表示
awkのプログラムは,「パターン-アクション規則」と「関数定義」でプログラムで記述する。
パターン{アクション} パターン{アクション} ... function 名前(引数の並び){ 文 } function 名前(引数の並び){ 文 } ...
awkは入力レコードを1行読むたびに,パターン-アクションを順に実行する。
パターンが真となるレコードに対して,対応するアクションが実行される。
パターンあるいは{アクション}のいずれかを省略することができる。
パターンを省略するとすべてのレコードにアクションが実行されて,{アクション}を省略するとパターンが真となるレコードを表示する。
パターンは次のどれかになる。
BEGIN END 式 /正規表現/ パターン,パターン
パターンには /.../ で正規表現を記述出来る。
/ABC/ { ... } # レコードの中にABCという文字列が含まれていればマッチ
if (...) 文の ... に記述するような条件式を記述することもできる。
$1 == "ABC" { ... } # 第1フィールドがABCであればマッチ
条件式で正規表現を使用することもできる。~は正規表現にマッチしたらという演算子。
$1 ~ /^#/ { ... } # 第1フィールド先頭が#で始まっていたらマッチ
条件1,条件2を記述した場合,条件1が真になる行から,条件2が真になる行までマッチする。
$1=="START", $1=="END" { ... } # 1フィールドがSTARTである行から,ENDである行までマッチ
BEGIN,ENDはプログラムの一番最初と一番最後にアクションを実行する。
BEGIN { ... } # 最初に1度だけアクションを実行 END { ... } # 最後に1度だけアクションを実行
BEGINFILE,ENDFILEは各入力ファイルの開始時と終了時にアクションを実行する。
BEGINFILE { ... } # 最初に1度だけアクションを実行 ENDFILE { ... } # 最後に1度だけアクションを実行
アクションは文の並び。
文は次のいずれかになる。
式 制御文 入出力文 {文の並び} 空文
文は,改行か;で区切る。;が単独で用いられると,空文を表す。
文の前後には,空行を挿入してもよい。
長い文は,行末に\を置いて次行に続けることができる。
, { && || do elseのあと,およびif( ),for( )のあとは,\を置かずに改行しても継続とみなされる。
アクションには,printで値を出力したり,if,whileなどで制御したり出来る。
{ if ($1 == "D") { # 1フィールドの値が"D"であれば print $2, $3 # 2,3フィールドの値を出力する } }
セミコロン(;)を使用すると、1行に複数の文を記述することができる。
{ a = 1; b = 2; c = 3; print a, b, c }
制御文は次のいずれかである。
break continue do 文 while(式) exit exit(式) if(式) 文 if(式) 文 else 文 for(式;式;式) 文 for(変数 in 配列) 文 next return return(式) while(式) 文
if-else文で最初の文がelseと同じ行にある場合,この文は;で終了するか{ }で囲まないとダメ。
文の種類 | 記法 |
逐次構造(文の並び) | {文1; 文2; ... 文n} |
選択構造(if文) | if(式) 文 |
選択構造(if-else文) | if(式) 文1 else 文2 |
ループ構造(for文) | for(式1; 式2; 式3) 文 |
ループ構造(while文) | while(式) 文 |
ループ構造(do-while文) | do 文 while(式) |
入出力文は次のとおりである。
getline 入力レコードを"$0"にセット getline 式 0または1
文の種類 | 書式 | 戻り値 |
比較 | 式>=式 | 0または1 |
式!=式 | 0または1 | |
式==式 | 0または1 | |
配列要素 | 式 in 式 | 0または1 |
パターンの検査 | 式~/正規表現/ | 0または1 |
式!~/正規表現/ | 0または1 | |
和 | 式+式 | 演算結果 |
差 | 式-式 | 演算結果 |
積 | 式*式 | 演算結果 |
商 | 式/式 | 演算結果 |
剰余 | 式%式 | 演算結果 |
累乗 | 式^式 | 演算結果 |
文字列をつなげる | 文字式 文字式 | 文字列 |
変数名[添え字]で配列を扱える。
foo[1] = 123 foo[2] = "ABC" print foo[1] # => 123 print foo[2] # => ABC
添え字には文字列を使用することができる。
foo["name"] = "Ueno" foo["age"] = 64 print foo["name"] # => Ueno print foo["age"] # => 64
配列の個数はlength()で求められる。数値を添え字とする配列は次のようにしてループを回す。
foo[0] = "ABC" foo[1] = "DEF" for (i = 0; i < length(foo); i++) { print foo[i] }
添え字が文字列の場合は,次のようにしてループを回す。
foo["name"] = "Ueno" foo["age"] = 64 for (x in foo) { print x "=" foo[x] # => name=Ueno, age=64 }
配列の中に該当の要素があるかどうかを調べる場合。
foo["name"] = "Ueno" if ("name" in foo) { print "Exist" }
配列要素を削除するには,deleteを使う。
foo["name"] = "Ueno" foo["age"] = 64 delete foo["name"] # foo["name"]を削除 delete foo # 配列foo自体を削除
次のようにして多次元配列を扱うことができる。
foo[1, 1] = 1001 foo[1, 2] = 1002 foo[2, 1] = 2001 foo[2, 2] = 2002 for (i = 1; i <= 2; i++) { for (j = 1; j <= 2; j++) { print foo[i, j] } }
配列の配列の場合も可能。
foo[1][1] = 1001 foo[1][2] = 1002 foo[2][1] = 2001 foo[2][2] = 2002 for (i in foo) { for (j in foo[i]) { print foo[i][j] } }
printは,変数や値を出力する。
print $1, $2, $3
値をカンマ,で連結すると,ビルトイン変数OFSに指定された出力フィールドセパレータ(デフォルトは半角スペース)で区切って出力する。
a = "AA"; b = "BB"; c = "CC"; print a, b, c # => AA BB CC
OFSを変更することで,区切り文字を変更することが出来る。
a = "AA"; b = "BB"; c = "CC"; OFS = "," print a, b, c # => AA,BB,CC
値をカンマ,で区切らない場合は,文字列の連結が行われた後に出力される。
a = "AA"; b = "BB"; c = "CC"; print a b c # => AABBCC
次のような組み込み変数が用意されている。
組み込み変数 | 値 |
ARGC | コマンド行の引数の数 |
ARGV | コマンド行の引数の配列 |
FILENAME | 現在の入力ファイル名 |
ENVIRON["..."] | 環境変数の値 |
FS | 入力のフィールドセパレータ(はじめはスペースまたはタブ) |
RS | 入力のレコードセパレータ(はじめは改行) |
NF | 現在レコードのフィールド数 |
NR | 現在の通算レコード |
FNR | 現在の入力ファイルの通算レコード |
OFS | 表示のフィールドセパレータ(はじめはスペース) |
ORS | 表示のレコードセパレータ(はじめは改行) |
OFMT | 数の表示のフォーマット(はじめは"%.6g") |
RSTART | matchでマッチした文字列の開始位置 |
RLENGTH | matchでマッチした文字列の長さ |
$0 | 現在の入力レコード |
$1,...,$NF | 第1フィールド,...,第NFフィールド |
組み込み文字列変数 | 値 |
gsub(r,s,t) | 文字列tの中に現れる文字列rをすべて文字列sで置換する。置換した数を返す。tを省略すると$0が使われる。 |
index(s,t) | 文字列sの中の文字列tの位置を返す。tが現れない場合は0 |
jindex(s,t) | 日本語文字列sの中の文字列tの位置を返す。tが現れない場合は0。 |
length(s) | 文字列sの長さを返す。 |
jlength(s) | 日本語文字列sの長さを返す。 |
match(s,r) | 文字列sが文字列rにマッチする位置を返す。マッチしないときは0。 |
split(s,a,fs) | fsをフィールドセパレータとして文字列sを配列aに分解し,フィールド数を返す。 |
sprintf(書式,式) | 書式で整えた式の並びを返す。 |
sub(r,s,t) | gsub()と同様。ただしはじめの1回だけ置換する。 |
substr(s,i,n) | 文字列sのi番目から始まるn文字を返す。 |
jsubstr(s,i,n) | 日本語文字列sのi番目から始まるn文字を返す。 |
組み込み算術変数 | 値 |
atan2(y,x) | atan(y/x)で-π~πの値 |
sin(x) | sin関数 |
cos(x) | cos関数 |
exp(x) | exp関数 |
log(x) | 自然対数 |
sqrt(x) | 平方根 |
int(x) | 小数点以下を切り捨て |
rand() | 疑似乱数 0以上1未満 |
srand() | 乱数の初期化 |
printfとsprintfの中では次のような変換が利用できる。
記法 | 値 |
printf("|%c|",65) | A |
printf("|%d|",65) | 65 |
printf("|%5d|",65) | 65 |
printf("|%05d|",65) | 00065 |
printf("|%f|",65) | 65.000000 |
printf("|%5.1f|",65) | 65.0 |
printf("|%e|",65) | 6.500000e+01 |
printf("|%5.1e|",65) | 6.5e+01 |
printf("|%g|",65) | 65 |
printf("|%o|",65) | 101 |
printf("|%s|","yuji") | yuji |
printf("|%10s|","yuji") | yuji |
printf("|%-10s|","yuji") | yuji |
printf("|%.4s|","yujiueno") | yuji |
printf("|%10.4s|","yujiueno") | yuji |
printf("|%-10.4s|","yuji") | yuji |
printf("|%%|") | % |
新しくコメントをつける