2    awk によるパターンの照合と情報の処理

この章では,awk コマンドについて説明します。awk コマンドは,ファイル内のテキスト行を照合するためのツールであり,照合した行を操作するための一連のコマンドを提供しています。

awk コマンドは,第 1 章で説明した拡張正規表現に従ってテキストを照合するだけでなく,各行すなわちレコードをフィールドの集合として扱い,各フィールドを個別にまたは組み合わせて処理することができます。 そのため,awk コマンドを使用して,次のような複雑な操作を実行することもできます。

この章では,次の事項について説明します。

2.1    awk プログラムの実行

awk コマンドの構文は,次のとおりです。

awk [ [-FERE] ] [ [-v var=val] ] { [-f prog_file] | [prog_text] } [ file1 [ file2 ... ] ]

表 2-1 に,awk コマンドのフラグを説明します。

表 2-1:  awk コマンドのフラグ

フラグ 説明
-FERE

フィールド・セパレータとして使用する拡張正規表現を指定します。 省略時の設定では, awk は空白 (タブまたはスペース) を使用してレコード内のフィールドを区切ります。 空白またはシェル・メタキャラクタを含む別のセパレータを使用する場合は,次のようにオプション全体を引用符 (') で囲みます。

%echo $PATH | awk -F':'  '{for(n=1;n<=NF;n++)print $n}'
 

 
-v var=val 変数 varval を割り当てます。 このような割り当ては,プログラムの BEGIN ブロックに対してのみ有効です。 awk コマンドでは,複数の -v フラグを指定することができます。
-f prog_file

awk プログラムを含むファイルの名前を指定します。 awk コマンドの場合,複数の -f フラグを指定することができます。 この場合,指定したすべてのプログラムを連結して単一のプログラムとして処理します。

awk プログラムは,-f prog_file フラグを使用してを実行することも,コマンド行にプログラムを記述して実行することもできます。 ファイル名の展開あるいは変数の代入が必要な場合は,コマンド行プログラムを引用符 (' あるいは ") で囲みます。 一重引用符 ( ' ' ) を使用し,-v var=val オプションを使用して awk プログラムへシェル変数を引き渡すと,awk プログラムが読みやすくなります。

通常,awk を実行する前に awk プログラム・ファイルを作成します。 プログラム・ファイルの内容は,次のような文の連続です。

pattern { action }
 

pattern とは,照合するテキストを定義する 1 つまたは複数の式です。 パターンは次のものから構成されます。

action とは,awk コマンド,オペランド,および演算子によって指定される,1 つまたは複数の処理です。 アクションは次のものから構成されます。

中カッコ ( { } ) は,検索パターンとアクションを区別するためのデリミタです。 アクションは,1 行にまとめて指定しても,見やすいように複数行にまたがって指定してもかまいません。 複数のコマンドから構成されるアクションを 1 行で記述する場合は,セミコロンでコマンドを区切ります。 たとえば,次の 2 つのプログラムはどちらも,`Gunther' または `gunther' を含むすべてのレコードを検出します。awk は,検出したすべてのレコードに関して,次のような 2 行の情報を出力します。 1 行目には照合したレコードの番号を,2 行目には照合したレコードの最初の 2 フィールドを出力します。

プログラム 1:

/[Gg]unther/ { print "Record:", NR ; print $1, $2 }
 

プログラム 2:

/[Gg]unther/ {
  print "Record:", NR
  print $1, $2
}

このプログラムからの出力は,次のようになります。

Record: 382
Schuller Gunther
Record: 397
schwarz gunther
 

パターンおよびアクションはいずれも,プログラム行ではオプションの要素です。 パターンを省略した場合,awk はファイル内のすべてのレコードにアクションを実行します。 アクションを省略した場合,awk はレコードを標準出力にコピーします。 パターンとアクションの両方を省略した空プログラムの場合は,入力を変更しない状態で出力に渡します。

プログラム・ファイルを作成した後,次のように awk コマンドをコマンド行に入力してください。

$ awk -f progfile infile > outfile
 

このコマンドは,progfile のプログラムを使用して,infile を処理し,出力を outfile に書き込みます。 入力ファイルは変更されません。

プログラムが短い場合は,入力ファイル名の前にコマンド行上にプログラムを入力することによって,同じジョブを実行することができます。 たとえば,次のようにします。

$ awk '/[Gg]unther/ { print $1, $2 }' infile
 

この方法で awk を使用する場合は,一重引用符 ( ' ' ) でプログラムを囲み,-v var=val オプションを使用してすべてのシェル変数を引き渡します。

awk を実行すると,awk は,プログラムを読み取り,構文をチェックします。 次に,awk は入力ファイルの最初のレコードを読み取り,パターンの出現順にプログラム・ファイルのそれぞれのパターンとレコードを突き合わせます。awk が,レコードに照合するパターンを検出した場合は,awk は対応するアクションを実行します。 次に awk は,プログラム・ファイルの中で照合を検索し続けます。awk は,プログラム・ファイルのすべてのパターンに対して最初の入力レコードを突き合わせて,必要なすべてのアクションをレコードに実行した後,次の入力レコードを読み取り,そのレコードに対してプログラムを繰り返します。 このように,処理は入力ファイルの終わりまで継続されます。図 2-1 は,この順序のフローチャートです。awk の動作と,図 3-1 で示した sed エディタの類似の動作とを比較してください。

図 2-1:  awk 処理の順序

2.2    awk における出力

print コマンドまたは printf コマンドを使用して,awk で出力を生成することができます。 print コマンドの構文では,引数をコンマまたは空白で区切ることができます。 コンマで区切られた引数は,現在の出力フィールド・セパレータ (OFS,省略時は空白) を使って出力されます。 空白で区切られた引数は,連結されて出力されます。 たとえば,次のようになります。

awk 'BEGIN{ x=22; print "ABC" x, "DEF" }'
ABC22 DEF
 

printf( "format", value1 [, value2 , ...] )

このコマンドは,format 文字列の定義に従って value1value2 などの引数をフォーマットし,出力します。 フォーマット指定子の構文については, awk(1) および printf(3) を参照してください。

2.3    awk 変数の使用

awk プログラムでは,情報を処理するために変数を使用します。 変数には次の 3 つのタイプがあります。

awk 言語は表 2-2 に示す組み込み変数をサポートしています。 また,上記の 3 つのタイプの変数は,作成したり変更したりすることもできます。 たとえば,次の代入文では,var という名前の変数を作成しています。 変数の値は,現在のレコードの第 3 および第 4 フィールドを合計したものです。

var = $3 + $4
 

変数をパターンの一部として使用したり,その変数をアクションで処理したりすることもできます。 たとえば,次のプログラムは,変数 tst に値を割り当て,以後のアクションのパターンの一部として tst を使用しています。

{ tst = $1 }
tst == $3 { print }
 

2.3.1 項2.3.2 項,および 2.3.3 項で,3 つのタイプの変数について説明します。 これらの項で使用する例では,2.4 節以降で説明するいくつかの awk 機能についても触れます。

2.3.1    単純変数

単純 (スカラ) 変数は,必要に応じて値を割り当てることによって,任意の数だけ作成することができます。 明示的に値を割り当てる前に変数を参照した場合,awk は,その変数を作成し,空文字列値 ("") を代入します。 変数には,アクション式内での使用方法に応じて,数値 (浮動小数点) または文字列値を指定することができます。 たとえば,x = 1 という式では,x は数値変数になります。 同様に,式 x = "smith" では,x は文字列変数になります。 しかし,awk では,必要に応じて,文字列と数値は,互いに自由に変換されます。 その結果,たとえば x = "3"+"4" という式では,引数がリテラル文字列であるにもかかわらず,awkx に値 7 を代入します。 数値以外の値を含む変数を数値式で使用した場合には,awk は,数値 0 を変数に代入します。 次に例を示します。

y = 0
z = "ABC"
x = y+z
print x, z
 

この例の場合,"0 0" が出力されます。 これは,y には値 0 が代入され,z に代入された値 ABC も数値 0 に変換されるためです。

たとえば,x = 2 "" のように,変数値にヌル文字列 ( "" ) を連結することによって,変数を強制的に文字列として処理することができます。 文字列を連結する方法についての詳細は,2.12 節を参照してください。 変数値の先頭に 0 を追加すれば,変数を強制的に数値として処理することができます。 これらの方法は,変数のタイプを固定したい場合に便利です。 たとえば,x の値が 0100 で y の値が 1 の場合,awk は通常,両方の変数を数値として処理して,xy よりも大きいものと見なします。 両方の変数を強制的に文字列として処理すると,xy よりも小さくなります。 これは,ASCII コードで 0 が 1 よりも前にあるためです。

2.3.2    フィールド変数

現在のレコード内の各フィールドはフィールド変数とも呼ばれ,単純変数の特性を共有しています。 これらのフィールドは,算術または文字列演算に使用され,数字または文字列の値を代入することができます。 awk では,現在のレコード ($0) を明示的に変更することができます。 次のアクションは,第 1 フィールドをレコード番号に置き換えて,結果のレコードを出力します。

{ $1 = NR; print }
 

次の例は,第 2 および第 3 フィールドを加算して,結果を第 1 フィールドに保存します。

{ $1 = $2 + $3; print $0 }
 

$0 をプリントすることは,引数を付けないでプリントすることと同じです。

フィールド参照に数値式を使用することができます。 次の例は,第 1,第 2,および第 6 フィールドを出力します。

i = 1
n = 5
{ print $i, $(i+1), $(i+n) }
 

2.3.1 項で説明されているように,awk は,文字列と数値間の変換を行います。 フィールドの使用方法によって,awk がそのフィールドを文字列または数値のいずれで処理するかが決まります。 指定されたフィールドの使用方法が決められていない場合には,awk はフィールドを文字列として処理します。

awk プログラムは,必要に応じて入力レコードを複数のフィールドに分割します。

2.3.3    配列変数

フィールド変数と同じように,配列変数も単純変数の特性を持っています。 配列変数は,算術演算または文字列演算で使用し,数字または文字列の値を代入することができます。 配列要素を宣言したり,初期化する必要はありません。awk によって配列要素が作成され,最初に参照された時点で,配列要素は空文字列 ("") に初期化されます。 delete 文を使って不要な配列要素を削除することができます。 詳細は,表 2-7 を参照してください。

添字は,大カッコで囲むことによって示します。 添字には,文字列の値を含む,ヌル以外の値を使用することができます。 数字の添字の例を次に示します。

x[NR] = $0
 

この式は,配列 xNR 番目の要素を作成し,現在の入力レコードの内容をその要素に代入します。 次に,文字列の添字を使用する方法の例を示します。

/apple/  { x["apple"]++ }
/orange/ { x["orange"]++ }
END      { print x["apple"], x["orange"] }
 

このプログラムは,apple を含むそれぞれの入力レコードに対して,配列 xapple 番目の要素をインクリメントします。orange についても同じ処理を行います。 その結果,このプログラムは,これらのそれぞれの単語を含むレコードの合計を計算し,出力します (単語はレコード内に複数含まれることがあるため,これは単語の数ではありません)。

配列要素を配置するために if または while 文を使用した場合,問題が発生する可能性があります。 制御構造については 2.10 節を参照してください。 配列の添字が存在しない場合は,これらの文でヌル値の配列要素を持った新しいハッシュ・テーブル・エントリとして添字が追加されてしまいます。 たとえば,次のような場合です。

if (exists[$2] == 1) print i
 

このような問題を避けるには,次のようなコードを使用してください。 このコードでは,配列要素が存在し,かつ配列要素の値が 1 の場合のみ i がプリントされます。

if (i in exists) {
  if (exists[i]== 1) print i
}
 

配列の全要素を処理するには,次のような for ループを使用します。

for(i in exists) {
  print exists[i]
}

また,while と関係演算子を併用する場合にも,このようなコーディングを使用してください。

リテラル文字列や文字列変数の値は,split 関数を使用して分割し,配列に格納することができます。 たとえば次のようにします。

n = split("Thu Mar 18 11:19:40 EST 1999", array1)
m = split(array1[4], array2, ":")
 

この例の 1 行目では,リテラル文字列を分割して配列 array1 の要素 (array1[1] から array1[n]) に格納します。 このとき,n は文字列内のフィールドの数です。 2 行目では,コロン (":") をセパレータとして,変数 array1[4] を分割し,array2 に格納します (2.9 節 を参照)。

2.3.4    組み込み awk 変数

awk プログラムでは,表 2-2 に示す組み込み変数を使用できます。

表 2-2:  awk の組み込み変数

変数名 説明
$0 現在のレコードの内容
$n

入力レコードのフィールド n の内容 -- awk の場合レコード全体 ( $0 ) を変更できます。

ARGC

awk コマンド行の引数の数 -- この変数は変更可能です。 コマンド名,マイナス記号付きのオプション,スクリプト・ファイル名 (もしあれば),または変数割り当ては含みません。

ARGV

awk コマンド行の,コマンド名とそれに続く引数の配列 (ARGV[0] から ARGV[ARGC-1]) -- この配列の要素は変更可能です。 マイナス記号が前に付いたオプション,スクリプト・ファイル名 (もしあれば),または変数への代入は含みません。

CONVFMT

数値の変換フォーマット -- 省略時の設定は %.6g です。

ENVIRON

ENVIRON["name" ] でアクセス可能な現在の環境変数を含む変更可能な配列 -- "name" は環境変数の名前を持つ変数またはリテラルです。 この配列内の要素を変更しても,awk がリダイレクション,パイプ処理,system() 関数で生成したコマンドを渡す環境には影響しません。

FILENAME

現在の入力ファイルの名前 -- 入力ファイル名が指定されていない場合は,FILENAME にはマイナス記号が含まれます。BEGIN アクション内では,FILENAME は定義されません。END アクション内では,FILENAME は最後のファイル読み取りを使用します。

FNR

現在のファイル内の現在のレコードの番号 -- 複数のファイルを処理しているときに,現在のファイルが複数ファイルの最初のファイルではない場合は,NR とは異なります。

FS

フィールド・セパレータとして使用する文字または式 -- 省略時の設定は任意の数の空白です。awk は,フィールド・セパレータとして複数バイトの正規表現を指定したり,複数のフィールド・セパレータを定義することができます。 たとえば,次の例では,任意の数の空白が続くコンマ,または最低 1 つの空白を,フィールド・セパレータとして定義しています。

FS = ",[ \t]*|[ \t]+"
 

NF 現在のレコードのフィールド数
NR

最初に読み取られたファイルの先頭から順番に数えた場合の,現在のレコード番号 -- 複数のファイルを処理しているときに,現在のファイルが最初の読み取りファイルではない場合,FNR とは異なります。

OFMT

数値の出力書式の指定 -- 省略時の設定は %.6g です。

OFS

出力フィールド・セパレータ -- データ書き込み時にフィールド間に出力される文字または文字列です。 省略時の設定は空白です。

ORS

出力レコード・セパレータ -- データ書き込み時にレコード間に出力される文字です。 省略時の設定は改行文字です。

RLENGTH

match()によって照合した文字列の長さ -- 一致するものがない場合は,-1 を設定します。

RS 入力レコード・セパレータとして使用する文字
RSTART

match()で照合した最初の文字のインデックス (文字列内の位置) -- 一致するものがない場合は,0 を設定します。

SUBSEP

配列要素内の複数の添字用のセパレータ -- 省略時の設定は,ASCII FS 文字 \034 です。

これらの変数の詳細については, awk(1) を参照してください。

2.4    パターンとしての正規表現

最も単純な正規表現は,リテラル文字列です。awk では,正規表現をスラッシュで囲まなければなりません。 正規表現の中にスラッシュを入れる場合には,バックスラッシュでスラッシュをエスケープします。 たとえば,/\/usr\/share/ は,文字列 /usr/share と一致する正規表現です。

次に示すのは,文字列 the を含むすべてのレコードを出力する awk プログラムの例です。

/the/
 

この正規表現には空白または他の修飾子が指定されていないため,プログラムは,単語として "the" を含むレコードと,northern のように単語の一部分として文字列 "the" を含むレコードの両方を表示します。

正規表現は,大文字と小文字を区別します。 "The" または "the" のいずれかを検出するには,次のように正規表現をカッコで囲ってください。

/[Tt]he/
 

awk プログラムは,第 1 章 で説明した拡張正規表現をサポートします。 また,^ 記号および $ 記号も,行全体とともに,特定のフィールドまたは変数に適用できます。 次の例は,文字列 cat または cats のフィールドを照合しますが,これらの文字列を含む単語 (たとえば concatenate など) は照合しません。

{ for (i=1;i<=NF;i++) if ($i ~ /^cats?$/) print }
 

2.5    パターンとしての関係式および論理式

関係式を使用すると,レコードの特定のフィールドだけを照合したり,あるいは,数字または文字列に関連した他の照合を行うことができます。2.3 節では,パターンに関係式を使用する例を示しました。awk プログラムでは,パターンの作成に使用できるように,次の関係演算子を定義しています。

== 等しい
!= 等しくない
< より小さい
> より大きい
<= より小さいか等しい
>= より大きいか等しい
~ 正規表現と照合する
!~ 正規表現と照合しない

リテラル文字列と数値を照合する場合には,== (等しい) および != (等しくない) 演算子を使用します。 次に例を示します。

str == "literal string"
num != 23
$NF == 1991
 

この例の最後の行は,$n 構文に組み込み変数 NF を組み合わせて,レコードの最後のフィールドの値と照合しています。 正規表現と照合させるには,次のように ~ (正規表現と照合する) および !~ (正規表現と照合しない) 演算子を使用します。

str ~ /[Ll]iteral/
 

組み立て式に対しても,関係式をテストすることができます。 たとえば,次のパターンは,第 2 フィールド ($2) が第 1 フィールド ($1) よりも 100 以上大きいレコードを検出します。

$2 > $1 + 100
 

次のパターンは,偶数個のフィールドからなるレコードを検出します。

NF % 2 == 0
 

式には,2.8 節に示されている演算子を使用します。

絶対比較演算子を使用して,文字列と照合させることができます。 たとえば,次のパターンは,"s" で始まるレコードまたは文字セットの中で s の後にある任意の 1 文字 (t,u,v,w,x,y,z) を検出します。

$0 >= "s"
 

次のブール演算子を使用すれば,2 つ以上のパターンを結合することができます。

&& 論理積 (AND)
|| 論理和 (OR)
! 否定 (NOT)

たとえば,前述した例で英数字以外の記号と照合しないようにするためには,次のように 2 つの式を組み合わせることもできます。

($0 >= "s" && $0 < "{")
 

左中カッコは,ASCII コードの英字 z の次の文字です。

2.6    パターン・レンジの使用

操作するレコードのグループを選択する場合には,パターン・レンジを使用します。 パターン・レンジは,コンマによって区切られた 2 つのパターンから構成されています。 最初のパターンは,レンジの開始を指定し,2 番目のパターンはレンジの終了を指定します。awk プログラムは,2 つのパターンと照合するレコードも含め,レンジ内のすべてのレコードに対して対応するアクションを実行します。 たとえば,次のとおりです。

NR==100,NR==200 { print }
 

このプログラムは,レコード 100 で始まり,レコード 200 で終わる 101 個のレコードを入力ファイルからプリントします。

パターン・レンジを使用した場合に,他のパターンが,レンジ内のレコードと照合しなくなることはありません。 ただし,入力ファイルは 1 レコードずつ処理され,次のレコードが処理される前に,それぞれのレコードに対する適切なすべてのアクションが行われるため,次の例で示すように,順序どおりにアクションが行われていないようにみえることもあります。

2,4 { print }
/share/ { print "Found share" }
 

このプログラムを次の入力ファイルに適用します。

This is a test file
Line two
Try to share things
Line four
Last line of file
 

このファイルが awk によって処理されると,出力は次のようになります。

Line two
Try to share things
Found share
Line four
 

レコード 4 が最初のパターンに照合するかどうかを検証される前に,2 番目のアクションがレコード 3 に適用されます。

2.7    awk のアクション

アクションには,たとえば print のように単一のコマンドを指定することも,複数のコマンドを指定することもできます。 アクションとして,レコードまたはレコードの一部の選択を指定することもできます。 アクション内の関係式を代わりに使用して,明示的にパターンを指定しないプログラムを作成することもできます。 このようなプログラムは,次の例に示すように,C プログラムに非常に似ています。

{
  if ($1 == 0) {
    print;
    printf("%5.2f\n", $2+$3)
  } else {
    printf("%5.2f\n", $1+$2)
  }
}
 

注意

print コマンドの後にあるセミコロンは,C プログラムでは必須ですが,awk には必須ではありません。 しかし,このセミコロンがあっても,エラーの原因となることはありません。

2.8    アクション内での演算子の使用

アクション文内の式を作成する場合には,表 2-3 に示されている演算子を使用します。

表 2-3:  awk アクションの演算子

優先順位 演算子 説明
1 () カッコ 3+x*4 = 3+(x*4)
2 $ フィールド参照 $(NF-1) = 最後のフィールドの次
3 ++ インクリメント この表の後の説明を参照
3 -- デクリメント この表の後の説明を参照
4 ^ べき乗 2^3 = 8
5 ! 論理否定 !x は x と等しくない
6 + 単項演算子のプラス +4 = 4
6 - 単項演算子のマイナス -4 は負の 4
7 * 乗算 2*4 = 8
7 / 除算 6/3 = 2
7 % モジュロ (剰余) 7%3 = 1
8 + 加算 2+3 = 5
8 - 減算 7-3 = 4
9 空白 連結 "a" "b" = "ab"
10 < より小さい 5 < 6
10 > より大きい "qrs" > "abc"
10 <= より小さいか等しい 3 <= 3
10 >= より大きいか等しい 4 >= 2
10 == 等しい 9 == 9
10 != 等しくない "xyz" != "abc"
11 ~ 正規表現と一致 "tmp.c" ~ /[a-z]+\.[ch]/
11 !~ 正規表現と一致しない "tmp.o" !~ /[a-z]+\.[ch]/
12 in 配列のメンバシップ for (j in arr) print arr[j]
13 && 論理積 (AND) X
14 || 論理和 (OR) X
15 ?: 条件式 x == -1 ? "error" : "OK"
16 = 代入 x = 3
16 ^= 値をべき乗 x^=3 は x = x^3 と同等
16 *= 値を乗算 x*=y は x = x*y と同等
16 /= 値を除算 x/=y は x = x/y と同等
16 %= 値のモジュロ x%=y は x = x%y と同等
16 += 値をインクリメント x+=y は x = x+y と同等
16 -= 値をデクリメント x-=y は x = x-y と同等

入力ファイルのすべての第 1 フィールドの合計,およびすべての第 2 フィールドの合計をプリントする例を次に示します。

{ s1 += $1; s2 += $2 }
END { print s1,s2 }
 

インクリメントおよびデクリメント演算子の位置は,解釈に大きな影響を与えます。i++ という式は,i の現在の内容を評価してから,i をインクリメントします。++i という式を使用すると,awk は評価の前に i をインクリメントします。 次に例を示します。

$ echo "3 3" | awk '{
>   print "$1 =", $1 "; $1++ =", $1++ "; new $1 =", $1
>   print "$2 =", $2 "; ++$2 =", ++$2 "; new $2 =", $2
> }'
$1 = 3; $1++ = 3; new $1 = 4
$2 = 3; ++$2 = 4; new $2 = 4
 

2.9    アクション内での関数の使用

awk は,表 2-4 に示す組み込み算術関数をサポートしています。

表 2-4:  組み込み awk 算術関数

関数 説明
atan2(x,y) x/y で指定された値の逆正接を返します。
cos(expr) expr で指定された値 (ラジアン) の余弦を返します。
exp(arg) arg の自然真数 (底はe) を返します。 たとえば,exp(0.693147) は値 2 を返します。log(arg) を参照。
int(arg) arg の整数部を返します。
log(arg) arg の自然対数 (底はe) を返します。 たとえば,log(2) は 0.693147 を返します。exp(arg) を参照。
rand 偽似乱数 (0 <= n < 1) を返します。
sin(arg) arg で指定された値 (ラジアン) の正弦を返します。
sqrt(arg) arg の平方根を返します。
srand(seed) rand に引き続くコールに対する偽似乱数のシードとして seed を使用します。 シードが何も指定されない場合には,日付の時間が使用されます。 返す値は,その前のシードです。

awk は,表 2-5 に示す組み込み文字列関数をサポートしています。

表 2-5:  組み込み awk 文字列関数

関数 説明
gsub(expr,s1,s2) 文字列 s2 内で正規表現 expr に一致するすべての文字列を,s1 で指定された文字列に置換します。s2 が指定されていない場合には,現在の入力レコードが使用されます。 正規表現 expr は照合のたびに再評価されます。 この関数は置換した数を表わす値を返します。sub(expr,s1,s2) を参照。
index(s1,s2) 文字列 s2 中の部分文字列 s1 の文字位置を返します。s2s1 に含まれない場合,この関数は 0 を返します。
length 現在のレコードの文字列の文字数を返します。
length(arg) arg で指定された文字列の長さを返します。length を参照。
match(s,expr) 文字列 s 内で正規表現 expr との一致が見つかった文字位置を返します。RSTART 変数を照合が始まる文字の位置に設定し,RLENGTH 変数を照合文字列の長さを表す値に設定します。 一致するものがない場合,この関数はゼロ (0) に返します。
split(s,array,sep) 文字列 sarray[1]...[n] の連続する要素に分割し,要素の数を返します。 オプションの sep 引数には,現在使用されているフィールド・セパレータ (省略時は FS 変数の内容) 以外のフィールド・セパレータを指定することができます。
sprintf(f,e1,e2 ,...) 引数 e1 等を含む文字列を printf コマンドと同じ方法でフォーマットして返します。 プリントは行いません。
sub(expr,s1,s2) 文字列 s2 内で正規表現 expr に一致する最初の文字列を,s1 で指定される文字列と置換します。 s2 が指定されていない場合は,現在の入力レコードが使用されます。 この関数は置換した数 (0 か 1) を返します。gsub(expr,s1,s2) を参照。
substr(s,m,n) 文字位置 m から始まり,長さが n 文字の,文字列 s の部分文字列を返します。 s の最初の文字の文字位置は 1 です。n が省略された場合,あるいは文字列の長さが n 文字末満の場合は,残りの文字列が返されます。
tolower(s) 文字列 s 内の大文字をすべて小文字に変換する。 引数がない場合には,現在のレコードを処理します。
toupper(s) 文字列 s 内の小文字をすべて大文字に変換する。 引数がない場合は,現在のレコードを処理します。

awk は,表 2-6 に示すその他の組み込み awk 関数をサポートしています。

表 2-6:  その他の組み込み awk 関数

関数 説明
close(arg) arg で指定したファイルまたはパイプをクローズします。
system("command") システム・コマンドを実行し,終了 (Exit) 状態を返します。 変数名として awk が解釈しないように,コマンド全体を " 記号で囲みます。

awk プログラムでは,次の構文で関数を作成することもできます。

function name ( parameter-list ) {    statements }

function の代わりに func を使用することもできます。

新たに作成した関数の場合,関数を定義する場合も関数を作成する場合も,関数名の後ろの左カッコと関数名との間にはスペースを入れないで記述する必要があります。

関数宣言のパラメータのリストで使用した名前は,その関数内で使用する形式的なものです。 実際に関数を呼び出す際に,これらの形式的なパラメータは呼び出し文で指定された値に awk によって置き換えられます。 関数は再帰的に使用できます。

関数エントリ上で形式的なパラメータを特別に宣言することによって,関数に対してローカル変数を定義することができます。 すべてのローカル変数は空の文字列あるいは 0 に初期化されます。 実パラメータとローカル変数とを混同しないように,次の例のように余分なスペースを使用してローカル変数を区別することができます。

function foo(in, out,      local1, local2) {
  local1 = "foo"
  local2 = "bar"

.
.
.
}

2.10    awk プログラムでの制御構造の使用

awk 言語は,表 2-7 に示す制御構造をサポートしています。 特に明記しない限り,これらの制御構造は C 言語の場合と同じように動作します。 単一の制御構造のアクションとしていくつかの文を実行する場合は,それらの文を中カッコで囲みます。 制御構造内で実行する文が 1 つだけの場合は,中カッコは省略できます。

次の例の最初の 2 つの if 制御構造には,実行する文がそれぞれ 1 つ含まれています。 これらの制御構造の機能は,どちらも同じです。

{
  if (x == y) print
  if (x == y) {
    print
  }
  if (x == y) {
    print $3
    printf("Sum = %d\n", x+z)
  }
}
 

表 2-7:  awk の制御構造

制御構造 説明
if-else

if-else 制御構造の小カッコ内の条件が評価されます。 真の場合は,if に続く文が実行されます。 偽の場合は,else に続く文が実行されます。else キーワードは省略することもできます。 連続した if 文は,else if 文で指定できます。

次のように,"else" と "if" を記述する順番は重要です。

if ( $1 == "abc" ) {
 
   print("found abc\n");
}
else if ( $1 == "qrs" ) {
   print("found qrs\n");
}
else if ( $1 == "xyz" ) {
   print("found xyz\n");
}
else {
   print("did not find "abc", "qrs", or "xyz"\n");
}

delete

delete 文を使用すると,配列要素を削除することができます。 たとえば次の例では,配列 x の要素がすべて削除されます。

{
  for(j in x)
     delete x[j]
}

while

テストされた条件が真でなくなるまでの間,while 文に続く文が実行されます。 次の例では,入力レコードのすべてのフィールドを,1 行に 1 フィールドずつ出力します。

{
  i = 1
  while(i<=NF) print $i++
}

for

for(expr1;expr2;expr3 statements の形式の制御構造は,次の while 制御構造と同じ動作をします。

{
  expr1
  while(expr2) {
    statements
    expr3
  }
}
 

たとえば,上記の while の例を,次のように記述することもできます。

{
  for(i=1;i<=NF;++i) print $i
}

for(i in array) 文は,配列のすべての要素を処理します。

$2=="="{name_value_pairs[$1]=$3}
end{
  for (i in name_value_pairs)
   print name_value_pairs[i]
}

break break 文を実行すると,while ループまたは for ループから即座に抜けます。
コメント

awk プログラム・ファイル内にプログラムの説明を記述します。 コメントは,( # ) 記号で始まり,行の最後で終了します。 次に例を示します。

{
  print x,y     # This is a comment
}

continue continue 文は,ループを最初から繰り返します。
getline getline 文を実行すると,awk は,現在の入力レコードを捨て,次の入力レコードを読み取り,現在の位置からパターンの走査を開始します。 getline var を使用すると,getline の入力を変数に割り当てることができます。var を指定しなければ,入力は現在のレコードに割り当てられます。
next next 文を実行すると,awk は,現在の入力レコードを捨て,次の入力レコードを読み取り,プログラム・ファイルの先頭からパターンの走査を開始します。
exit exit 文は,入力ファイルの終端に到達したときのようにプログラムを停止します。

2.11    入力の処理前および処理後に実行するアクション

awk プログラムは,入力ファイルの開始および終了を定義するための 2 つの特別なパターン・キーワード BEGIN および END を識別します。

BEGIN は,最初のレコードを読み取る前に,入力の先頭と一致します。 このため awk は,入力ファイルを処理する前に,このパターン (BEGIN) に対応するアクションを 1 回実行することになります。 たとえば,ファイル内のすべてのレコードのフィールド・セパレータをコロン ( : ) に変更する場合は,プログラム・ファイルの最初に次の 1 行を記述します。

BEGIN { FS = ":" }
 

この例のアクションは,コマンド行で -F: フラグを使用した場合と同じです。

同様に,END は,最後のレコードを読み取った後に,入力ファイルの最後と一致します。 このため,awk は,入力ファイルを処理した後,このパターン (END) に対応するアクションを 1 回実行することになります。 たとえば,入力ファイルのレコードの総数を出力するためにはプログラム・ファイルに次のように記述します。

END { print NR }
 

2.12    文字列の連結

変数名を 1 つの式にまとめることによって,文字列を連結します。 たとえば,コマンド print $1 $2 は,現在のレコードの最初の 2 つのフィールドを構成する文字列を,空白を入れないでプリントします。 文字列を連結する場合は,変数,数値演算子,関数を使用することができます。 変数と数値演算子についての詳細は,2.3.1 項および 2.8 節を参照してください。 関数 length($1 $2 $3) は,最初の 3 つのフィールドの長さを文字数で返します。awk 関数のリストについては,2.9 節を参照してください。 連結する文字列がフィールド変数 (2.3.2 項参照) である場合は,空白で名前を区切る必要はありません。 たとえば,式 $1$2 は,$1 $2 と同一です。

2.13    リダイレクションとパイプ

特に指定しない限り,print 文および printf 文は,出力を標準出力ファイルに書き込みます。 標準リダイレクション演算子を使用すれば,プリント文の出力をリダイレクトすることができます。 次に例を示します。

print $0, $3, amt >> "reportfile"
 

この例は,その出力を標準出力に書き込まないで,reportfile という名前のファイルに追加します。 リダイレクションの最初のインスタンスの前に reportfile がない場合は,reportfile が作成されます。 この例の出力ファイル名は引用符で囲まれています。 引用符は,ファイル名を変数名と区別するために必要です。 書き込みは,指定されたファイル,標準出力のいずれにも行うことができます。

printprintf 文は常に,その出力を標準出力に送ります。 次の例では,出力を標準エラー出力に送ります。

print "oops: did not find expected input" | " cat 1>&2"
 

また,プリントされる出力を他のコマンドにパイプすることもできます。 次に,awk の出力を tr コマンドにパイプして,大文字をすべて小文字に変換する例を示します。

print | "tr '[A-Z]' '[a-z]'"
 

リダイレクションを使用する場合と同様に,出力のパイプ先のコマンドは,引用符で囲まなければなりません。

awk は,標準リダイレクト演算子を使用して getline ヘの入力をリダイレクルトすることができます。 そのため,次のように getline 文への入力をパイプで提供することができます。

expr | getline

この場合,expr はシステム・コマンドとして解釈されます。

次の例では,システム・コマンドからの出力を読み取ります。

BEGIN {
  cmd = "ps aux"
  while( cmd | getline > 0 ) {
   if ( $2 == "PID" ) continue
   unique_users[$1]++
   }
   close(cmd)
 
   for(i in unique_users) {
    printf("%3d %s\n", unique_users[i], i)
   }
  }
 

出力用にオープンできるファイルの数は制限されています。awk プログラムには,ユーザのオープン・ファイル記述子の省略時の制限が適用されます。 しかし,効率を高めるために,close(arg) 文を使用して,出力用にオープンしたファイルのうち不要なものをクローズすることもできます。 たとえば,次のようにします。

{
if ( cur_file != "/tmp/" $1 ) {
   close(cur_file)
   cur_file = "/tmp/" $1
 }
 print $2 >cur_file
}
END { close(cur_file) }