182
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

awkの使いかた

基本的にワンライナーで使うことを想定してメモ。(長くなる場合はスクリプトの形で記載する。)

awk の良いところ

  • Linuxでテキストデータをちゃちゃっと処理できる。
  • ほとんどのLinux OSで標準で使える(プレインストールされているので,自分でインストールする必要がない)。
  • WindowsのMingwや,Macでも標準で装備されているので互換性や移植性が高い(スクリプトだけコピーして持っていれば,だいたいどこでも使える)。
  • 書き方は,Cと似ているので覚えやすい。
  • コンパイルが不要。
  • codingが簡単なので,作業時間が短く済む。

基本

awkはテキストファイルを,行ごとに処理を行う。
それぞれの要素(列)に対しての処理をcodingすることが基本となる。
それぞれの要素に関しては,以下で紹介するような組み込み変数で指定する。

例えば,テキストファイル内の1列目の全数値データに対して,2を足して出力したいときは,以下のようにする。

awk '{print $1 + 2}' hoge.txt

$1が1列目の要素を表す。
hoge.txtが処理の対象となるテキストファイル。

組み込み変数

  • $1, $2, ..., $n, ... : 第n列目のデータ
  • NF: フィールド(列)の数(1からスタート)
  • NR: 行番号(1からスタート)

よく使う処理

行全部をそのまま表示したい

(ただファイルの内容を表示するだけなので,catと同じ)。

awk '{print $0}' hoge.txt

1列目のデータを百倍して表示したい

awk '{$1=$1*100;print $0}' hoge.txt

テキストファイルの第2列目だけ表示したい

awk '{print $2}' hoge.txt

N行毎に出力したい(行のスキップ・間引き)

10行毎に出力したい場合
以下のようにすると1行目,11行目,21行目...が出力される。(10で割ったときに1余る数)

awk 'NR%10==1' angle_dist.dat > angle_dist_skip10.dat 

同様に2行毎の場合はNR%2==1とする。

第1列目のデータの合計(平均)をとりたい

awk '{sum+=$1} END {print sum,sum/NR}' hoge.txt
  • END {} の中に,最後に行いたい処理を書く。
  • 勝手に変数sumを初期化してくれている。 明示的に初期化したい場合は,BEGINを使う。
awk 'BEGIN {sum=0} {sum+=$1} END {print sum, sum/NR}' hoge.txt

処理する行の指定

NR==1{
  print "hogehoge"
}
NR>1{
  print $0
}

ファイルへの出力

{
aa="out.txt"
print "hogehoge" > aa
}

if文

awk '{if( $1 == 0 ){ print $0 }}' hoge.txt

三項演算子

x ? y : z 

x が真なら y,偽なら z の値

print ($1 < $2 ? $1 : $2)

2つの値を比較し,小さい値を出力。

ループ

for

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

文字列の要素を抽出

  • substr関数を使う。
  • 変数(文字列)のm文字目からn文字目までを取り出したい場合
substr(変数,m,n)
awk '{ print substr($1,1,1) }' hoge.txt

配列

全ての配列は連想配列(他の言語のハッシュ,辞書に相当)として扱われる。

array1[3] = "component1"

実用例(平均値の分布)

2列のテキストデータをインプットに,1列目の値をbinで区切って,
それぞれのbinの中で2列目のデータの平均値を求める。

以下は,bashスクリプトの中に,awkスクリプトを組み込んでいる

#!/bin/bash -ue

inputfile=angle_velo.dat 
outfile=out_do4.txt

width_win=10.0
interval_win=10.0
min_x=-180.0
max_x=0.0

################################
# AWK scripts                  #
################################
read -d '' scriptVariable << 'EOF'
BEGIN {
  for (i=v_min_x;i+v_width_win<=v_max_x;i+=v_interval_win) {
    num_sum[i+v_interval_win/2] = 0; #initialization
  }
}
{
  for (i=v_min_x;i+v_width_win<=v_max_x;i+=v_interval_win) {
    if (i<=$1 && $1<i+v_interval_win) {
      sum[i+v_interval_win/2] += $2; 
      num_sum[i+v_interval_win/2] ++; 
    }
  }
}
END {
  for (i=v_min_x;i+v_width_win<=v_max_x;i+=v_interval_win) {
    if (num_sum[i+v_interval_win/2] != 0){
      print i+v_interval_win/2,sum[i+v_interval_win/2]/num_sum[i+v_interval_win/2], num_sum[i+v_interval_win/2]
    }
    else {
      print i+v_interval_win/2,sum[i+v_interval_win/2] 
    }
  }
}
EOF
################################
# End of AWK Scripts           #
################################

awk -v v_min_x="${min_x}" \
    -v v_width_win="${width_win}" \
    -v v_interval_win="${interval_win}" \
    -v v_max_x="${max_x}" \
"$scriptVariable" ${inputfile} > $outfile

ランダム数生成

gen_rand.awk
BEGIN{
  pi = atan2(0, -1)

  while(i < n){
    x1 = rand()
    x2 = rand()

    if(x1 != 0){
      print sqrt(-2 * log(x1)) * cos(2 * pi * x2)
      i++

      if(i >= n) break

      print sqrt(-2 * log(x1)) * sin(2 * pi * x2)
      i++
    }
  }
}

このスクリプトを実行するときは, -vオプションで,乱数の個数 n を指定する必要がある. 今回は,100万個の乱数列 nrand.txt を作る.

$ awk -f normal_random_numbers.awk -v n=1000000 > nrand.txt

nrand.txt が,平均 0, 標準偏差 1 の分布に,ほぼ従っていることが確認できる

平均値,標準偏差

stddev.awk
{
  x[NR] = $1
}
END{
  if(NR == 0) exit

  for(i in x){
    sum_x += x[i]
  }

  m_x = sum_x / NR

  for(i in x){
    sum_dx2 += (x[i] - m_x) ^ 2
  }

  print "average ",m_x
  print "variance",sum_dx2 / NR
  print "standard_deviation ",sqrt(sum_dx2 / NR)
}

$ awk -f stddev.awk nrand.txt

計算誤差が蓄積してしまうため,

めんどうでも,一度平均値を計算してから,差の 2乗の平均を求めた方が安全である.

パターンマッチ

  • 演算子 ~ を使える。
  • grepみたいなことをしたい場合。
if( $1 ~ "abc" ) # $1がabcを含んでいれば真
  • 裏は,演算子 !~ を使う。
if( $1 !~ "abc" ) # $1がabcを含んでいなければ真

正規表現

if( $1 ~ /[#@]/ ) # $1が#または@であれば真

.xvgファイルのコメント行を除いて出力

awk '{if(substr($1,1,1) !~ /[#@]/)print}' hogehoge.xvg

セパレータの指定

  • ,でデータを区切りたい場合
awk -F "," '{print $2}' hoge.txt 

応用例

graceプロットデータ(hoge.xvg)のデータ行だけ抽出したい。

  • コメント行やラベル行は,@や#から始まるので,それだけ除いて出力する。
awk '{if(substr($1,1,1) !~ /[#@]/)print $0 }' hoge.xvg > out.txt

行の要素全てを足し合わせたい。

awk '{sum=0;for(i=1;i<=NF;i++){sum+=$i}print sum}' hoge.txt

ユーザー定義関数

function myprint(num)
{
     printf "%6.3g\n", num
}

次の行の表示

getline関数を使う。

awk '{if( "r_2_3_&_P" == $2 ){getline;  print} }' index_axrst.ndx

文字列や実数を整数へ変換する

int( $1 )

例えば,

print int ("38DG") は,38 と出力される。

処理中に別のファイルを開く

getline 関数を使う。
http://tounderlinedk.blogspot.jp/2010/07/getline-1-awk.html

{
  time = $1
  atmid1 = $2
  atmid2 = $3
  while (( getline < "eq2_dna.gro" ) > 0) {
    if( $3 == atmid1 ){
      #print $1,$2,$3
      resid1 = int( $1 )
      if ( resid1 > 38 ) resid1 = 77 - resid1
    }
    if( $3 == atmid2 ){
      resid2 = int( $1 )
      if ( resid2 > 114 ) resid2 = 229 - resid2
    }
  }
  close("eq2_dna.gro")
  print time, resid1 , resid2
}

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
182
Help us understand the problem. What are the problem?