AWK リファレンス

AWK とは?

AWK はフィルタリングによく使用されるコマンドであるが、同様にしてフィルタリングに使用される grep や cut と決定的に違うところは、AWK 自体が独立した一つのスクリプト言語であるということだ。つまり、AWK は正確にはコマンドではなく、AWK スクリプト・インタプリタである。

例えばテキストから 1 フィールド目を抽出する場合には、awk '{ print $1 }' と実行するが、ここで引数として AWK に渡している { print $1 } の部分が AWK スクリプトのソースになっている。

ちなみにこの { print $1 } は「全ての行に関して 1 フィールド目を標準出力に出力せよ」という意味になる。

この AWK は Perl などといった他のスクリプト言語ほど高機能ではないが、単一のテキストファイルや標準入力からのテキストをフィルタリング処理するには十分な機能を持っている。

また、AWK をシェルスクリプトに組み込むことで、高度で高速なフィルタリング機能を備えたシェルスクリプトを作成することが可能になる。

シンプルな構文のスクリプト言語であるため、習得するにも Perl ほど学習コストはかからず、シェルスクリプトで少し高度なフィルタリング処理を書きたい人には打って付け言語である。

筆者の経験では、1 週間もあれば実用レベルのスキルを身につけることが可能である。

AWK の構文

パターンとアクション

AWK スクリプトは、パターンとアクションからなる単一もしくは複数ルールによって構成される。AWK はテキストファイルもしくは標準入力からテキストを1行ずつ読み込み、各パターンとのマッチングを行う。パターンとのマッチングが成功した場合は、そのパターンに対応するアクションが実行されることになる。

awk '{ print $1 }'

この AWK スクリプトもシングルクォートに囲まれた部分がパターンとアクションになっている。ここではパターンの指定は省略されており、このようにパターンが省略された場合は、全行とマッチングが成功する。

つまり、全ての行に対してアクションが実行されることになる。

上記の例の場合は {} で囲まれた部分がアクションに該当し、「1 フィールド目を出力する」という処理になっている。したがって、この AWK スクリプトは「全ての行の1フィールド目を出力する」という処理になる。

AWK スクリプトの基本的な構文は以下のとおりだ。

pattern { action }

パターンとアクションの組を複数指定することも可能である。

pattern1 { action1 }
pattern2 { action2 }
pattern3 { action3 }

複数のパターンとアクションの組み合わせを指定した場合は、

  1. 上から順にパターンが評価され、
  2. 一致したパターンに対応するアクションが実行される。

つまり複数のパターンに一致した場合は、複数のアクションが実行されることになる。

パターン

アクションと対になるパターンには正規表現を使用することができる。比較対象が省略された場合は、現在読み込んでいる行と比較される。

/^AWK/ { print "この行は AWK ではじまる行です。" }

パターンに正規表現を使用する

正規表現を使用する場合は必ず右側に指定すること。左側に指定するとエラーになる。文字列と正規表現とのマッチングには ~ を使用する。

$1 は現在読み込んでいる行の 1 フィールド目の値を表す AWK の組み込み変数である。

$1 ~ /^[A-Z][a-z0-9][a-z0-9]*$/ { print "正規表現は必ず右側に指定する。" }

パターンに評価式を使用する

パターンには正規表現などとのマッチング以外にも、一般的な数値の評価式なども指定することが可能である。NR は AWK の組み込み変数で、現在読み込んでいる行の行数を表す。

NR == 10 { print "現在、10行目です。" }

AWK が現在読み込んでいる行とまったく無関係なパターンを指定することも可能。

count > 50 { print "変数countの値が50を超えました。" }
パターンに論理演算子を使用する

さらに、パターンは AND や OR で複数の条件を結合することもできる。

$1 == "first" && $2 ~ /^second/ { print "1フィールド目が「first」、かつ2フィールド目が「second」で始まる文字列の行です。" }
NR >= 10 || $NF ~ /[0-9]+/ { print "10行目以降、もしくは一番後ろのフィールドが数字を含む行です。"}

BEGIN を使用した初期化処理

BEGIN は特殊なパターンで、このパターンを指定したアクションは、処理の開始直前に一度だけ実行される。一般的にこのパターンに対応するアクション部分には、変数の初期化などの前処理を記述する。

BEGIN { print "処理を開始します。" }

END を使用した終了処理

ENDBEGIN と同様に特殊なパターンで、このパターンを指定したアクションは、処理の終了直前に一度だけ実行される。一般的にこのパターンに対応するアクション部分には、処理結果表示などの終了処理を記述する。

END { print "処理を終了します。" }

アクション

アクションは全体を中括弧 {} で囲んで記述する。

アクション処理内部では C 言語などと同様に if 文や for 文、while 文といったおなじみの制御文も使用可能である。また、アクションが実行されるのは、対で指定されるパターンの評価結果が真となった場合のみである。

/awk reference/ {
  if(NF == 5){
    # フィールド数が5つならここが実行されます。
    print "Number of fields: 5"
  }

  for(i=0; i < 10; i++){
    # 10回ループします。
    printf("%02d: %2d回目のループです。\n", i, i+1)
  }

  while(1){
    # 無限ループです。
    print "ループを開始します。"
    print "ループを終了します。"

    # breakが実行されると、その時点でループを終了します。
    break
  }

  # nextが実行されると次の行が読み込まれ、またスクリプトの先頭(BEGINを除く)から処理が実行されます。
  next
}

AWK スクリプトの例

実際に AWK スクリプト (textreader.sh) を作成してみる。簡単な内容なので、全体に目を通して流れを掴んでほしい。

#!/bin/bash

{
cat <<__EOT__
Line No. 1
Line No. 2
Line No. 3
Line No. 4
Line No. 5
__EOT__
} | awk '

  # ここはテキストの1行目を読み込む前に実行される.
  BEGIN {
    # BEGIN は特殊なパターンで初期化処理などを行いたい場合に使用する.
    print "テキストの内容を表示します..."
  }

  # 現在読み込んでいる行が1行目だったら...
  NR == 1 {
    # 組み込み変数 NR には現在読み込んでいる行の行番号が設定されている.
    print "1行目を読み込みました..."

    # 組み込み変数 $0 は現在読み込んでいる行の内容が設定されている.
    # また,文字列の結合はスペース区切りで並べるだけでよい.
    print "1行目の内容「" $0 "」"

    # next を実行すると以下のパターンマッチングを中止し次の行を読み込む.
    next
  }

  # 3フィールド目が「1」だったら...
  $3 == "1" {
    # 組み込み変数 $N はNフィールド目の内容が設定されている.
    # つまり今回は1行目が該当するが,1行目は直前のアクションでnextされているので,このアクション以下は実行されない.
    print "ここは実行されません."
  }

  # パターンを省略すると必ずアクションが実行される.
  {
    # パターンを省略すると直前にnextで飛ばされていない限り必ず実行される.
    # つまり今回は2~5行目で実行される.
    print NR "行目の内容「" $0 "」"
  }

  # ここはテキスト全行を読み込み終わった後に実行される.
  END {
    # printf も使える.
    printf("テキストは全%d行でした.\n", NR)
  }
'

実行結果は以下のとおり。

$ ./testreader.sh
テキストの内容を表示します...
1行目を読み込みました...
1行目の内容「Line No. 1」
2行目の内容「Line No. 2」
3行目の内容「Line No. 3」
4行目の内容「Line No. 4」
5行目の内容「Line No. 5」
テキストは全5行でした.

テキストの各行をパターンで振り分けて処理を分岐するという AWK 特有のスタイルが理解できれば、もうすでに AWK を習得したと言っても過言ではない。

あとは最低限の組み込み変数 (e.g. $0, NR) と関数を覚えればよい。AWK の組み込み関数は C 言語のものにかなり近いので、ほとんどのプログラマは問題なく使えるはずだ。

組み込み変数や組み込み関数は以下のサイトで調べられるので、一度目を通しておいてほしい。

Manpage of GAWK

シェルスクリプトと AWK スクリプト

シェルスクリプトから AWK スクリプトに変数を渡す 1

シェルスクリプトに AWK スクリプトを組み込んで使用する場合、シェルスクリプト側から AWK スクリプト側へ変数を渡したい場面はよくあると思う。

そのようなときのために、AWK には任意の値を指定した変数に代入した上で、AWK スクリプトを実行することができる便利なオプションが存在している。

awk --assign awk_var="$shell_var" '{ ... }'

→ シェルスクリプトから AWK スクリプトに変数を渡すには --assign (もしくは -v オプション)を使用する。

awk コマンドに --assign を指定し、続けて代入式 ([AWK 変数名]=[渡したい値]) を指定することで、AWK 変数に値が設定された状態で AWK スクリプトを実行することができる。シェルスクリプトの変数を渡したい場合はこの代入式にシェルスクリプト側から渡したい変数を指定すればよい。

また、-v オプションでも同様のことが可能だが、可読性の面から --assign の使用を推奨する

以下にシェルスクリプトから AWK スクリプトへ変数を渡す処理の例 (shell2awk.sh) を示す。

#!/bin/bash

shell_var="This is a shellscript variable."

echo "dummy" | awk --assign awk_var="$shell_var" '{ print awk_var }'

exit $?

あらかじめ受け渡すシェル変数を定義し、awk コマンドに --assign を指定して AWK 変数に定義したシェル変数の値を代入した上で、実行される AWK スクリプト内で受け渡しに使用した AWK 変数の値を出力しているだけの簡単な処理である。

実行結果は当然、渡したシェル変数 shell_var の値となる。実際の実行結果は以下のとおりである。

$ ./shell2awk.sh
This is a shellscript variable.

シェルスクリプトから AWK スクリプトに変数を渡す 2

シェルスクリプトから AWK スクリプトに変数を渡したい場合、awk コマンドの引数に指定して渡すほかに、AWK スクリプト内に直接渡したいシェル変数を指定する方法もある。

シェルスクリプトから AWK スクリプトに変数を受け渡すということは、要するに AWK スクリプト内にある代入式の代入する「値」の部分に、渡したいシェル変数の値が記述されていればよいだけである

以下に簡単な例 (shell2awk_2.sh) を示す。

#!/bin/bash

shell_var="This is a shellscript variable."

echo "dummy" | awk '
  BEGIN { awk_var = "'"$shell_var"'" }
  { print awk_var }
'

exit $?

シングルクオートの位置に注目してほしい。BEGIN アクション部分のシェル変数 shell_var の直前で一度シングルクオートが閉じられているのが分かると思う。

シングルクオートには改行や変数展開などを抑止する効果があるが、今回はそれをシェル変数部分で一度打ち切ることで AWK スクリプト内にシェル変数の値を直接指定している。また、シェル変数値にはスペースが含まれるため、変数前後をダブルクオートで括っている。

上記の AWK スクリプト部分は実行時にはシェルから次のように解釈され、awk コマンドへと渡されるはずである。

'
  BEGIN { awk_var = "This is a shellscript variable." }
  { print awk_var }
'

実際に展開された状態を bash の -x オプションで確認してみる (-x オプションに関しては「シェルスクリプトのデバッグ」を参照)。

$ bash -x ./shell2awk_2.sh
+ shell_var='This is a shellscript variable.'
+ echo dummy
+ awk '
  BEGIN { awk_var = "This is a shellscript variable." }
  { print awk_var }
'
This is a shellscript variable.
+ exit 0

実行結果からシェル変数 shell_var が展開された状態で AWK 変数 awk_var に代入されているのが確認できると思う。

このような場合のみならず、シェルスクリプトでは変数が展開された後 (シェルがスクリプトを解釈した後) に最終的にどのようなコマンドが実行されるのか、を意識してコーディングを行うと高度なスクリプトも比較的簡単に書けるようになるはずである。

逆にシェルスクリプト初心者は、そのことを意識できていない傾向があるように思われる (筆者の個人的感想)。

AWK スクリプト Tips

正規表現内に変数を使用する

パターンで正規表現を使用したい場合は、前述の通り以下のように記述する。

$1 ~ /^hoge/ { print "hoge の行" }

このとき、正規表現部分に変数を使用しても、意図した動きにはならない。

echo "hoge fuga" | \
awk '
  BEGIN {
    var="hoge"
  }
  $1 ~ /^var/ {
    print "hoge の行"
}'

例としてこれを実行してみる。

$ echo "hoge fuga" | awk 'BEGIN { var="hoge" } $1 ~ /^var/ { print "hoge の行" }'
$
#↑パターンにマッチしないので、なにも出力されない。

正規表現部分に変数を使用して、動的な正規表現を実現するためには、以下のように match(str, regex) 関数を使用する。

match($1, "^" var) { print var " の行" }

→match 関数は、第一引数の文字列を、第二引数の正規表現で検索し、見つかった場合はその位置を、見つからなかった場合は 0 を返す。

この match 関数を使用することで、以下のような正規表現に変数を使用した動的な検索を行うスクリプトを作成できる。

echo "hoge fuga" | \
awk '
  BEGIN {
    var = "hoge"
  }

  match($1, "^" var) {
    print var " の行"
  }
'

パターンに指定した match 関数は、正規表現がヒットすれば見つかった位置、つまり 1 以上を返すので「真」、逆にヒットしなければ 0 を返すので「偽」となる。

ちなみに、文字列と変数を結合したい場合は、上記の例 ("^" var) のように空白区切りで羅列する。