awk スクリプト メモ

awkスクリプトの備忘録メモです。

# 環境

* mac OS
* Bash
* awk
* iTerm2

# awkの動き

ターミナルからコマンドを入力して試してみます。

## echo

`$ echo -e "001 aaa\n002 bbb\n003 ccc"`
`-e`オプションで文字列中にエスケープシーケンスが使えるようになる。


## １行ごとに処理する
awkの基本の動作。
`{ action }` action部で標準入力から渡されたテキストを１行づつ処理します。


```
$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '{ print }'
001 aaa
002 bbb
003 ccc
```

## 条件に合った入力だけ受け入れる
`awk 'pattern { action }'`を使って、actionに渡す前に`pattern`部に条件を指定して条件に合ったテキストだけを`action`部に渡すことができます。

```
$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /"001"/ { print }'
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /001/ { print }'
001 aaa
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /(001)/ { print }'
001 aaa
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /(001|002)/ { print }'
001 aaa
002 bbb
```

例えば、空の行がある場合に、パターン部に正規表現を利用して空行以外を処理できます。

```
$ echo -e "001 aaa\n002 bbb\n\n003 ccc" | awk '{ print }'
001 aaa
002 bbb

003 ccc
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n\n003 ccc" | awk '/^$/ { print }'

Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n\n003 ccc" | awk '/[^$]/ { print }'
001 aaa
002 bbb
003 ccc
```

## BEGIN
BEGINパターンを利用して前処理ができる。

例えば、RS（入力レコードセパレーター）を標準の改行から空白に設定する。

```
$ echo -e "001,aaa 002,bbb  003,ccc" | awk 'BEGIN{ RS = " " }{ print }'
001,aaa
002,bbb

003,ccc
```


## 組み込み変数
awk言語にもシステムで定義された特別な変数があります。

### FS
フィールドセパレーター。入力行のフィールド区切り文字が設定されています。
デフォルトは空白。

### RS
レコードセパレーター。入力行のレコード区切り文字が設定されています。
デフォルトは改行文字。

### NR
1から始まるカレントレコードの番号が設定されています。

### NF
カレントレコードのフィールド数が設定されています。

```
$ echo -e "001,aaa 002,bbb  003,ccc" | awk 'BEGIN{ RS = " " }{ print NR, NF, $0 }'
1 1 001,aaa
2 1 002,bbb
3 0
4 1 003,ccc
```

```
$ echo -e "001,aaa 002,bbb  003,ccc" | awk 'BEGIN{ RS = " " }{ if(NF != 0){ print NR, NF, $0 } }'
1 1 001,aaa
2 1 002,bbb
4 1 003,ccc
```


---
# bashの中でawkを利用する
bashの中でawkを利用する場合の書き方です。


読み込みファイルはShift-JISで用意されている前提

``` t1-sjis.text
Id,IsDelete,ParentId,Name
"00001",0,"000001","サンプルファイル１.txt"
"00002",0,"000001","サンプルファイル２.txt"
"00003",0,"000002","サンプルファイル３.txt"
```

スクリプトは下記の感じ

``` awk-sample01.sh
#! /bin/sh

cat t1-sjis.csv | \
nkf -Sw | \
awk -F, \
'{
    if (NR != 1) {
        system("if test ! -d "$3"; then mkdir "$3"; fi")
        system("if test -d "$3"; then cp "$1" ./"$3"/"$4"; fi")
        print NR
        print $1,$3,$4
    }
}'
```

## 説明

* `nkf -Sw` nkfでShift-jisをutf-8に変換
* `NR != 1` 1行目のヘッダーは処理しない
* `system("if test ! -d "$3"; then mkdir "$3"; fi")` ディレクトリが無かったら作る

---
# 変数と配列

* awkの配列とハッシュには違いが無いので、文字列をキーにセットすれば配列はハッシュになる。
* ハッシュの取り出しは `for (value in array) array[value]` とする。


```awk-sample02.sh
#! /bin/sh

cat t1-sjis.csv | \
nkf -Sw | \
awk -F, \
'{
    array[$3] = $4
    print array[$3]
    print $1,$3,$4
    value01="Hello"
}
END {
    for ( item in array)
        print item, array[item]
    print value01
}
'
```


```console
$ ./awk-sample02.sh
Name
Id ParentId Name
"サンプルファイル１.txt"
"00001" "000001" "サンプルファイル１.txt"
"サンプルファイル２.txt"
"00002" "000001" "サンプルファイル２.txt"
"サンプルファイル３.txt"
"00003" "000002" "サンプルファイル３.txt"
ParentId Name
"000002" "サンプルファイル３.txt"
"000001" "サンプルファイル２.txt"
Hello
```

## シェルの変数をawkに渡す
$2はシェルのコマンドパラメータ
awk の -vオプションを使ってawkで利用する変数にシェルの変数をセットする 

```
awk -v attachments_dir="$2" -F, \
'
BEGIN {
    print attachments_dir
}
'
```

2つ以上の変数を渡すとき

```
awk -v file_name="$1" -v attachments_dir="$2" -F, \
'
BEGIN {
    print file_name attachments_dir
}
'
```


# ソート

## パイプでソートする

パイプでsortコマンドに渡すことでソート処理を行う

```
    for ( item in array)
        print item, array[item] | "sort"
```

```
"000001" "サンプルファイル２.txt"
"000002" "サンプルファイル３.txt"
ParentId Name
```

# ループ

## whileループ


```
    while ("ls ./"attachments_dir";" | getline) {
        ls_out[$0] = ++i
    }
```


## forループ
※ls_outは連想配列

```
    for (i in ls_out) {
        print i, ls_out[i]
    }
```

# ファイル

## ファイルオープン・クローズ
print文をリダイレクトするとファイルが作成される。
">>"を使うと既存ファイルに追記できる。

```awk-sample001.sh
#!/bin/bash

awk \
'
BEGIN {
    # file open
    print > "sample1.csv"
    close("sample1.csv")
}
'
```

## ファイル出力

```awk-sample002.sh
#!/bin/bash

awk \
'
BEGIN {
    # file open
    print "Hello awk" > "sample1.txt"
    # file close
    close("sample1.txt")
}
'
```

## ファイル入力

```awk-sample003.sh
#!/bin/bash

awk \
'
{
    print $0
}
'
```

```
$ cat sample.csv | ./awk-sample003.sh
```

## コマンドラインパラメータの利用
※sample.csv は読み込み対象のファイル
test は `if [ ! -f "$1" ]; then` でも同じ


```awk-sample003.sh
#!/bin/bash

if test ! -f "$1"; then
    echo "$1"'ファイルが存在しません'
    exit 1
fi
cat "$1" | \
awk \
'
{
    print $0
}
'
exit 0
```

```
$ ./awk-sample003.sh
ファイルが存在しません
$ ./awk-sample003.sh sample.csv
```

# 文字列処理

## 文字列を分割する

### split()
split("文字列", "配列", "分割文字")
戻り値：配列の要素数
配列の添字は1から始まるので注意。

```awk-sample005.sh
#!/bin/bash

# if test ! -f "$1"; then
if [ ! -f "$1" ]; then
    echo "$1"'ファイルが存在しません'
    exit 1
fi
cat "$1" | \
nkf -Sw | \
awk -F, \
'
{
    split($2, name, " ")
    print $1 "," $2 "," name[1] "\"" "," "\""name[2]
}
'
exit 0
```

※sample01.csvの中身は、cybozu kintoneのアプリストア「従業員名簿アプリ」のサンプルデータをお借りしております。

```
$ cat sample01.csv | nkf -Sw | head -5
"従業員番号(必須)","氏名","雇用形態","入社日","生年月日","TEL","メールアドレス"
"001","富田 美波","正社員","2014/04/01","1992/01/14","0801234****","minami-toda@sample.sample"
"002","山本 健太","正社員","2015/05/29","1985/05/25","0908989****","yamamoto123@sample.sample"
"003","佐藤 昇","正社員","2017/05/29","1986/05/16","0001234****","sato@sample.sample"
"004","太田 隆","正社員","2018/05/29","1977/11/01","0705678****","ohta-takashi@sample.sample"

$ ./awk-sample005.sh sample01.csv | head -5
"従業員番号(必須)","氏名","氏名"","
"001","富田 美波","富田","美波"
"002","山本 健太","山本","健太"
"003","佐藤 昇","佐藤","昇"
"004","太田 隆","太田","隆"
```

## 文字列中の位置を知りたい

### index()
index("文字列", "部分文字列")

```
$ awk 'BEGIN{print index("2021-03-26 00:00:00", " ")}'
11
```

## 文字列中の位置から部分文字列を取り出す

### substr()
substr("文字列", 位置)
文字列から指定位置以降を取り出す

```
$ awk 'BEGIN{print substr("080-123-9999", 5)}'
123-9999
```

## 正規表現にマッチした位置を知りたい

### match()
match("文字列", /正規表現/)
取り出しは RSTART, RLENGTH を利用する。
RSTART→マッチした文字の位置
RLENGTH→マッチした文字数

```
$ awk 'BEGIN{match("2021-03-26 00:00:00", / /); \
print RSTART, RLENGTH }'
11 1
```

```
$ echo "2021-03-26 00:00:00" | awk '{match($0, / /); \
print RSTART, RLENGTH; \
print substr($0, RSTART+1) \
}'
11 1
00:00:00
```

## 文字列置換

### sedコマンドを使う

```
$ echo "2021-03-26 00:00:00" | sed 's/ /T/'
2021-03-26T00:00:00
```

```
$ echo "2021-03-26 00:00:00" | sed 's/\([1-2][0-9]\{3\}[-]\)\(.*\)/\2/'
03-26 00:00:00
```

```
$ echo "2021-03-26 00:00:00" | sed 's/\([1-2][0-9]\{3\}[-][01][1-9][-][0-3][0-9]\)\(.*\)/\2/'
 00:00:00
```

```
$ echo "2021-03-26 00:00:00" | sed 's/\([12][0-9]\{3\}[-][01][1-9][-][0-3][0-9]\)\( \)\([0-2][0-9]:[0-5][0-9]\)\(.*\)/\1T\3Z/'
2021-03-26T00:00Z
```