awkでCSVファイル内の日付のシリアル値のみを変換する

## はじめに

とあるWeb APIを叩いた時、吐かれたCSVデータ内の日付情報がシリアル値(何故に？)だったので、
awkでCSVファイルの特定の列を変換するスクリプトを作ってみた。
実行環境は、Windows 10 ProのWSLでKaliを使いました。

## 作ったShellスクリプト

```bash:test.sh
# ～省略

cat ${output_dir}/tmp.csv | awk '{FS=",";OFS=","} NR>1{ $2=strftime("%Y-%m-%d", 
substr($2, 0, 10)) } 1' > ${output_dir}/result.csv

# 省略～
```

## 結果

とりあえずうまくいったみたい。

```windows:tmp.csv
No,Date,description,
1,1273645330000,TEST,
～
```

↓

```windows:result.csv
No,Date,description,
1,2010-05-12,TEST,
～
```

## awkのメモ

* 単一引用符で括った内容がawkに渡される。
* 今回は、BEGINブロック(1つ目の中括弧{})と、ENDブロック(2つ目の中括弧{})で作ったが、その間に処理を追加するMAINブロックも追加できるらしい。
* 「FS」はファイル内の区切り文字、「OFS」は出力のデミリタを指定できる。
* \$n(今回は\$2)は、FSで区切られたn列目の値を持つ変数。
* 「NR」は行番号を参照できる組込み変数。今回はBEGINブロックの頭に付与してCSVファイルの2行目以降を処理するよう指定している。
* ENDブロックの}と単一引用符の間の「1」は、真を意味していて、これがあるとawkデフォルト操作{print \$0}を実行するため、CSVファイルの全行を出力してくれるらしい(true/falseの結果が変わらないのは何故...？w)。

```Windows:WSL
$ echo 1 2 | awk '{print $1}'
1
$ echo 1 2 | awk '{print $1} 1'
1
1 2
$ echo 1 2 | awk '{print $1} 0'
1
$ echo 1 2 | awk '{print $0}'
1 2
$ echo 1 2 | awk '{print}'
1 2
$ echo 1 2 | awk '1'
1 2
$ echo 1 2 | awk '{print $1} true'
1
$ echo 1 2 | awk '{print $1} false'
1
```

### awk組込み関数strftime

タイムスタンプを人間が理解しやすい文字列に変換して返してくれる関数。
この関数は ANSI のC標準ライブラリと同じものらしい。

```c:strftime関数
strftime([format [, timestamp]])
```

| 変換指定文字 | 内容 |
| ---- | ---- |
| %Y | 世紀部分を含めた4桁の西暦年。 |
| %m | 月(10 進数表記)。01～12で指定する。 |
| %d | 月内通算日 (10 進数表記) 。01～31で指定する。 |

### awkの組込み関数substr

string中でstart番目の文字から始まる長さlengthの部分文字列を返してくれる関数。

```
substr(string, start [, length])
```

## その他

今回のスクリプトでは、元のCSVデータを新しいCSVファイルへリダイレクトしたが、
下記のように元のCSVファイルを上書きするように書くと上手くいかなかった。
awkで元のCSVファイルをロックしてるから上書き処理できないのかな？

```bash:test.sh
#1
awk '{FS=",";OFS=","} NR>1{ $2=strftime("%Y-%m-%d", 
substr($2, 0, 10)) }1' ${output_dir}/tmp.csv > ${output_dir}/tmp.csv

#2
$(awk '{FS=",";OFS=","} NR>1{ $2=strftime("%Y-%m-%d", 
substr($2, 0, 10)) }1' ${output_dir}/tmp.csv) > ${output_dir}/tmp.csv
```

**$()**は「コマンド置換([posix仕様](https://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03))」と呼ばれ、サブシェルを呼び出すものらしい。
バッククォート（ ``）で括ることでも同様の動きをするが、こっちはネストできるみたい。

## さいごに

結局、下記の疑問が残ってしまいましたが、CSV内のシリアル値を戻すという目的は達成できました。
やりたい処理は実装できたのですが、試合に勝って勝負に負けた感覚です笑
下記ご存じの方は、ご教示いただけると幸いですm(_ _)m

1. awkとリダイレクトの組合せではCSVファイル上書きできない問題
2. ENDブロックの}と単一引用符の間のtrue/falseが効かない問題
3. Web API が日付情報をシリアル値で返すのは、相手環境に依るバグを回避するため？

## 参考

* [The GNU Awk User’s Guide](https://www.gnu.org/software/gawk/manual/gawk.html)
* [stackoverflow：awk - Assigning system command's output to variable
](https://stackoverflow.com/questions/1960895/assigning-system-commands-output-to-variable)
* [stackoverflow：Why does “1” in awk print the current line?](https://stackoverflow.com/questions/20262869/why-does-1-in-awk-print-the-current-line)
* [Linux Programmer's Manual (3)：STRFTIME](https://linuxjm.osdn.jp/html/LDP_man-pages/man3/strftime.3.html)
* [UNIX & Linux コマンド・シェルスクリプト リファレンス：日付を取得する](https://shellscript.sunone.me/date.html#utc-%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E5%80%A4%E3%82%92%E4%B8%80%E8%88%AC%E7%9A%84%E3%81%AA%E6%97%A5%E4%BB%98%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B)
* [POSIX：2.6.3 Command Substitution](https://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03)