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
```

## 処理する行の指定

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

## ファイルへの出力

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

## if文

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

### 三項演算子

```awk
x ? y : z 
```

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

- 例

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

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

## ループ

### for

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

## 文字列の要素を抽出

- substr関数を使う。
- 変数（文字列）のm文字目からn文字目までを取り出したい場合

```awk
substr(変数,m,n)
```

```
awk '{ print substr($1,1,1) }' hoge.txt
```

## 配列

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

```awk
array1[3] = "component1"
```

### 実用例（平均値の分布）

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

以下は，[bashスクリプトの中に，awkスクリプトを組み込んでいる](https://qiita.com/hnishi/items/499552ff29e63ec2beaf#bash%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E4%B8%AD%E3%81%ABgnuplot%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E3%81%9F%E3%81%84)。

```bash
#!/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
```

- 参考： https://codezine.jp/article/detail/8156

## ランダム数生成

```awk: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 の分布に，ほぼ従っていることが確認できる

http://kdo.la.coocan.jp/awk2.html

## 平均値，標準偏差

```awk: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乗の平均を求めた方が安全である．

http://kdo.la.coocan.jp/awk2.html

## パターンマッチ

- 演算子 ~ を使える。
- grepみたいなことをしたい場合。

```awk
if( $1 ~ "abc" ) # $1がabcを含んでいれば真
```

- 裏は，演算子 !~ を使う。

```awk
if( $1 !~ "abc" ) # $1がabcを含んでいなければ真
```

## 正規表現

```awk
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
```

## ユーザー定義関数

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

http://www.kt.rim.or.jp/~kbk/gawk-30/gawk_14.html

## 次の行の表示

getline関数を使う。

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

https://www.gnu.org/software/gawk/manual/html_node/Getline.html

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

`int( $1 )`

例えば，

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

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

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

```awk
{
  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
}
```

## 参考

http://www.gi.ce.t.kyoto-u.ac.jp/user/susaki/perl/file_io.html