awk

アルバイトのMoriです。
今回は業務で触れる機会があり、プライベートでも使い始めた言語AWKについて書かせていただきました。

目次

なにを紹介するのか

awkを書いているとき「これってどう書くんだろう?」と手をとめて考えてしまう処理をピックアップしてまとめてみました

1. 最後からn番目の列を出力する

組み込み変数NFを用いれば良い。

awk '{ print $(NF-n+1) }'

最後から2番目の値を出力する場合

$ echo "a b c d" | awk '{ print $(NF-1) }'
 c

2. ファイルの2行目以降だけ扱いたい

組み込み変数FNRとパターンマッチングを用いれば良い

awk 'FNR > 1 { なにかしらの処理 }'

以下のようなCSVhuga.csvの1列目(1行目を除く)を取得する場合

name,age,gender
Tom,18,male
Alice,20,female
Watson,45,male
$ awk -F',' 'FNR > 1 { print $1 }' fuga.csv
Tom
Alice
Watson

3. コマンドラインから引数を与える

プログラム部分をfオプションを使ってファイルで与えた場合
実行した時に変数の値を指定したい場合などがあると思います
その時はvオプションを使って変数に直接代入する

$ awk -f hoge.awk -v '変数=値'

hoge.awkは1列目の値を定数倍するためのスクリプト

{ print $1 * k }

定数kをコマンドライン引数として与える

$ echo "1" | awk -f hoge.awk -v 'k=2'
2

4. 区切り文字を複数指定する

文字1,文字2,…., 文字nを入力区切り文字に設定したい時

awk -F '[文字1文字2...文字n]' 'program'

のようにFオプションに区切り文字にしたい文字を[]で囲った値を渡す.

空白とカンマの両方を区切り文字として扱う

$ printf "a,b c" | awk -F '[, ]' '{ print $1, $2, $3 }'
 a b c

のように、カンマと空白のどちらでも区切りられる.

5. パターンマッチに変数を使用する

match関数を使ってパターンマッチを行う。

awk -v '変数=値' 'match( $0, 変数 ) { なにかしらの処理 }'

以下のようなファイルhuga.txtから、変数に格納した値にマッチする行を取得する

a b c
d e f
$ awk -v 'str=e f' 'match( $0, str ){ print $0 }' huga.txt
d e f

6. CSV形式で出力する

hoge.awkを以下のようにする

BEGIN { OFS = "," }

{
  for (i=1; i<=NF; i++) {
    printf "\"%s\"", $i;
    if( i < NF ) printf "%s",OFS
  }
}

END { printf "\n" }

列番号を表す変数i1からNFまでfor文で回す。
for文内部では,printf関数でダブルクォーテーションを囲ってから列区切り文字OFSを出力。

プログラムファイルにhoge.awkを指定して実行

awk -f hoge.awk

文字列a b,c dを空白区切りしてCSV形式で出力

$ echo "a b,c d" | awk -f hoge.awk
 "a","b,c","d"

余談ですが、AWKはダブルクォーテーション内の改行を区別できないため、カラム内に改行コードがあるCSVは正確に読み込めないことに注意。

7. スクリプト内でファイルを開く

getlineを使う。 AWKの公式では無闇なgetlineの使用は推奨されておらず、組み込み変数の上書きによる想定外の動きをすることが多いため、使うとしてもこの書き方を強く推奨しておく。

BEGIN {
  while ( (getline var < ファイル名) > 0 ) {
    print var
  }
}

ファイルabc.txtの値を行毎に出力する

BEGIN {
  while ( (getline var < "abc.txt") > 0 ) {
    print var
  }
}
$ echo "abc" > abc.txt
$ echo "" | awk -f hoge.awk
abc

8. 合計・平均・分散・標準偏差を求める

1列目に対して合計・平均・分散・標準偏差を求めたい場合はhoge.awkに以下のように記述

{
  sum += $1
  square_sum += $1 * $1
}

END {
  if ( NR == 0 ) exit

  average  = sum / NR
  variance = square_sum / NR - average * average
  std_dev  = sqrt( variance )

  print " 合計 :", sum
  print " 平均 :", average
  print " 分散 :", variance
  print "標準偏差:", std_dev
}

プログラムファイルにhoge.awkを指定して実行

awk -f hoge.awk

以下のような内容のfuga.txtの合計・平均・分散・標準偏差を求める

50
60
70
70
100
$ awk -f hoge.awk fuga.txt 
 合計 : 350
 平均 : 70
 分散 : 280
標準偏差: 16.7332

9. 相関係数を求める

1列目と2列目の相関係数を求めたい場合はhoge.awkに以下のように記述

{
  sum_x   += $1
  sum_x2  += $1 * $1
  sum_y   += $2
  sum_y2  += $2 * $2
  sum_xy  += $1 * $2
}

END {
  if ( NR == 0 ) exit

  ave_x = sum_x / NR
  ave_y = sum_y / NR
  
  covariance = sum_xy / NR - ave_x * ave_y
  std_dev_x  = sqrt( sum_x2 / NR - ave_x * ave_x )
  std_dev_y  = sqrt( sum_y2 / NR - ave_y * ave_y )

  R = covariance / (std_dev_x * std_dev_y)

  print "相関係数:", R
}

プログラムファイルにhoge.awkを指定して実行

awk -f hoge.awk

以下のような内容のfuga.txtの相関係数を求める

50 50
50 70
80 60
70 90
90 100
$ awk -f hoge.awk fuga.txt 
相関係数: 0.633518

最後に

いかがでしたでしょうか?
今回は長すぎない記事を目指して書いたので初心者さん向けではない情報量になってしまいました。 AWKに興味がある方はぜひWeb上に大量の記事があるので検索してみてください。

参考になったページ