Awk 基礎文法最速マスター

基礎

print 文

表示は print 文である:

print "Hello Awk World!";

nawk にはデバッグ機能や変数ダンプの機能がないので, 様々な用途で print 文をよく使う.

コメント:

# 以降あコメントになる.

スクリプトの実行

以下のように “-f” に続いてスクリプトを指定する:

awk -f foo.awk
gawk -f foo.awk
nawk -f foo.awk
mawk -f foo.awk

awk 言語について様々に処理系があるので, 注意すること!!

複数の awk スクリプトを指定することもできる:

gawk -f foo1.awk -f foo2.awk -f foo3.awk

スクリプトを直接指定できる:

gawk ' BEGIN { print "Hello World" } '

BEGIN, END, パターン + アクション

BEGIN ブロック

ファイルを読む前に実行するブロックである:

# ファイルを読む前に, "Not Read" と表示
BEGIN { print "Not Read" }

END ブロック

ファイルを読み終わったら “Already Read” と表示:

# ファイルを読み終わったら "Already Read" と表示
END { print "Already Read" }

パターン + アクション

特定のパターン (または条件式) にマッチする場合に, アクションを実行する:

パターン { アクション }

BEGIN, END もある種のパターンと思えば全て同じ作法で記述することができる:

# 正規表現 reg にマッチしたら "Matched!" と表示
$0 ~ /reg/ { print "matched!" }

条件式でも問題ない:

# 行の長さが 0 場合に "Nothing" と表示
length($0) == 0 { print "Nothing" }

パターンがない場合

パターンは真であることを前提に動作する:

# 全ての行を表示
{ print $0 }

アクションがない場合

‘{print $0}’ が省略されたものとして動作する:

# 正規表現 reg にマッチする行のみを表示
$0 ~ /reg/

特殊変数

$0

特に指定のない限り 1 行 (レコード) に該当する:

# 全ての行を表示
{ print $0 }

レコードの単位は RS (レコードセパレータ) で指定することができる:

# 段落 (空行) 単位をレコードとして $0 を表示
BEGIN { RS = "" } { print $0 }

$1 ~ $NF

一行をスペースまたはタブで分解し, それぞれの項目 (フィールド) を行頭から $1 $2 の順で割り当てる. 最後の要素は $NF に格納され, NF にはフィールドが格納される:

# 最初と最後の要素を表示
[cactus:~/code_sh/bash/alarm-note/test]% ls -l | gawk '{ print $1, $NF }'
total 32
-rwxr-xr-x SoundNote.sh*
-rwxr-xr-x SoundNote.sh~*
-rw-r--r-- aki.jpg
-rwxr-xr-x test.sh*
-rwxr-xr-x test.sh~*
-rwxr-xr-x while.sh*
-rwxr-xr-x while.sh~*

フィールドの単位は FS (フィールドセパレータ) で指定することができる:

# フィールド分割を "," (カンマ) 区切り (CSV) にして $1 を表示
BEGIN { FS = "," }{ print $1 }

NR, FNR

NR には現在処理している通し行数が格納され, FNR にはファイル単位での処理が格納される:

# 最初の 10 行を表示
NR <= 10 { print $0 }

つまり, ファイルを読み終わった後の END ブロックでは NR は行数が格納されることになる:

# ファイルの行数を表示
END { print NR }

数値

数値の表現:

n1 = 10
n2 = 3.1415926
n3 = 1e10;

四則演算

四則演算, ベキ乗ならびに括弧は通常の中置記法で記述する:

num = ( (1 + 2) * 3 / 4) ^ 5

商とあまり:

n1 = 3
n2 = 2

# n1 = n2 * div + mod
div = int(n1 / n2)
mod = n1 % n2

インクリメントとデクリメント

インクリメントとデクリメントは以下のようにする:

# インクリメント (1)
i = i + 1

# インクリメント (2)
i++

# インクリメント (3)
++i

# インクリメント (4)
i += 1

# デクリメント (1)
i = i - 1

# デクリメント (2)
i--

# デクリメント (3)
--i

# デクリメント (4)
i -= 1

算術関数

int, sin, cos, atan2, log, exp, sqrt の 算術関数が使える.

tan (タンジェント) は以下のようにして求めることができる:

tan = sin(num) / cos(num);

例えば, 円周率は以下のようにして求めることができる:

pi = 2 * atan2(1, 0)

文字列

文字列の表現

ダブルクォーテーションで囲んで代入する:

str = "abc";

文字列の操作

連結

文字列を繋げるには, そのまま繋げる:

str1 = "abc"
str2 = str1 "def" # abcdef

置換

置換には一度だけ置換する sub 関数と全てを置換する gsub 関数の 2 種類が用意されている:

# AAA が表示される
BEGIN { str = "abc"; gsub(/[a-z], "A", str); print str}

sub, gsub 関数の戻り値は変換した個数であり, 置換後の文字列を返すわけではない.

配列, 連想配列

awk の配列は全て連想配列 (ハッシュ) である.

配列の代入

awk にはリストがないため, 直接代入する:

arr[1] = 123
arr[2] = "abc"
arr["item1"] = 123
arr["item2"] = "abc"

連続している場合には, split 関数を用いて分割することも可能である:

# arr[1] = 1; arr[2] = 2; arr[3] = 3; arr[4]; arr[5] = 5; と同じ
str = "1 2 3 4 5"
# num_arr には配列の個数 (5) が代入される
num_arr = splict(str, arr)

配列の個数

配列を for 文で呼び出してカウントすることで配列の個数を調べることができる:

for(index in arr){
  num_arr++; # 配列の個数が加算される
}

多次元配列

awk では配列を ”,” (コンマ) で区切ることで擬似的な多次元配列を扱える:

arr[1, 2] = 123

実際には ”,” (コンマ) は 8 進数表記で “034” になる.

制御文

if 文

C 言語の if 文と基本的に同じ文法である:

# 奇数なら "Odd Number" と表示
if(num % 2 == 1){
  print "Odd Number"
}

if ~ else 文

C 言語の if ~ else 文と基本的に同じ文法である:

# 奇数なら "Odd Number" と表示, 偶数なら "Even Number" と表示
if(num % 2 == 1){
  print "Odd Number"
}else{
  print "Even Number"
}

FizzBuzz 問題は以下のように書くことができる:

if(num % 15 == 0){ # 3 の倍数かつ 5 の倍数
  print "FizzBuzz"
}else if(num % 5 == 0){ # 5 の倍数
  print "Buzz"
}else if(num % 3 == 0){ # 3 の倍数
  print "Fizz"
}else{
  print num;
}

while 文

C 言語の while 文と基本的に同じ文法である:

# 0 から 10 まで表示
while(i <= 10){
  print i++;
}

for 文

C 言語の for 文と基本的に同じ文法である:

# 0 から 10 まで表示
for(i = 0; i <= 10; i++){
  print i++
}

比較演算子

数値と文字列での扱いの違いはない:

==
!=
<=
<
>=
>
~ 正規表現マッチする
!~ 正規表現マッチしない

条件演算子

条件演算子は以下のように使う:

# 奇数なら "Odd Number" と表示, 偶数なら "Even Number" と表示
print(num % 2 == 1) ? "Odd Number" : "Even Number"

ユーザー定義関数

ユーザー定義関数を用いることができる:

# タンジェントを返す関数
function tan(num, val){
  val = sin(num) / cos(num)
  return val
}

上記引数 num は関数自体の引数で, 呼び出された時に使われていない引数 val は局所変数として振る舞う.

nawk では変数は全て大域変数である. function の中で使う場合のみ局所変数を定義することができる.

再帰で関数を呼び出すこともできる:

# fact(): 階乗を計算する
# in: 数値 (n)
# out: 入力された数値の階乗を返す
function fact(n){
  if(n >= 2){
    return n * fact(n-1) # 再帰的に呼び出されている
  }else{
    return 1
  }
}

ファイルの入出力

通常ファイルの入力はパターン + アクション で行い, 出力はリダイレクトを用いる:

% sequence 10 | gawk 'NR % 2 {print $0}' > odd_number.txt

プログラム中でもリダイレクトでき, “/dev/stdout” (標準出力), “/dev/stderr” (標準エラー出力) 等の特殊デバイスも扱える:

if(err_num > 0){
  # 標準エラー出力に "Error" と表示
  print "Error" > "/dev/stderr"
}else{
  # 標準出力に "Not Error" と表示
  print "Not Error" > "/dev/stdout"
}

コマンドライン引数

コマンドライン引数は配列 ARGV に格納される:

# コマンド引数を全て表示
BEGIN{
  for(i in ARGV){
    print i, ARGV[i]
  }
}

ARGV の 0 は一般には awk インタプリタ名が格納されるが, 明確に定義されているわけではない.

getline

getline は振る舞いを理解するのは難しいが, getline を使うことでプログラムの幅が広がる.

アクション中での getline

1 行, 読み進める:

# 偶数行を出力
{getline; print $0}

上記はあくまでサンプルで, 通常は以下のようにして偶数行を表示する:

# 偶数行を表示
NR % 2 == 0

コマンドからの受渡し:

# date コマンドの結果を変数 var に格納し表示
BEGIN{ "date" | getline var; print var}

ファイルを読み込む

アクションブロック以外で読み込む際によく使われる:

# foo.txt の中身を表示
BEGIN{
while(getline < “foo.txt” > 0){
print $0

} close(“foo.txt”)

}

Awk の書き方

K&R スタイルで記述する

タブは 4 文字分のスペース展開したものを用いる

変数名や関数名は小文字かつ単語のは “_” (アンダースコア) で繋ぐ