何をしたいか
awkで、一行全体の表示print $0
時に、出力ファイル区切り文字OFS
を指定しても機能しない現象を何とかしたい。
#状況
テーブルの区切り文字を変換して出力したい、処理結果をカンマ区切りで出力したい…等
例)こんなCSVを用意する。これをタブ区切りに変換して出力したいとする。
$ cat test.csv
a,b,c,d,e,f
1,32,6543,45,4,45
2,45,521,343,1,211
3,32,321,641,-2,377
4,45,56,939,-5,543
5,32,532,1237,-8,709
6,45,7,1535,-11,875
現象
$ gawk 'BEGIN {FS=","; OFS="\t";} {print $0}'
a,b,c,d,e,f
1,32,6543,45,4,45
2,45,521,343,1,211
3,32,321,641,-2,377
4,45,56,939,-5,543
5,32,532,1237,-8,709
6,45,7,1535,-11,875 # 区切り文字が変わっていない!
対処
$
つき変数に対して何らかの適当な代入処理を行う。$1=$1;
程度の簡単なものでよい。
$ gawk 'BEGIN {FS=","; OFS="\t";} {$1=$1; print $0}'
a b c d e f
1 32 6543 45 4 45
2 45 521 343 1 211
3 32 321 641 -2 377
4 45 56 939 -5 543
5 32 532 1237 -8 709
6 45 7 1535 -11 875 # タブ区切りが反映された
原因
ここまで書いて同一内容の先行記事を見つけた。
- [kakenmanの日記 - awkで$0出力するときにOFS指定が無視られる問題]
(https://kakenman.hatenadiary.org/entry/20110317/1300381670)
これでは差分がないので、少し原因を調べてみることにする。
調査結果
- フィールド
$0
の再構成タイミングはいつか、という問題に尽きる。
The GAWK Manual Edition 0.15 より
入力ファイルの読み込み > フィールド分割の指定 の節に、ほとんど回答になっている以下の記述がある。
先頭や末尾にある連続した空白をはぎとるという動作は、$0が再計算されたときには常におこなわれる。したがって、
echo ' a b c d' | awk '{ print; $2 = $2; print }'
このパイプラインは次のような結果となる。
a b c d
a b c d最初のprint文は読んだままのレコードを、行頭の空白を含んだ形で出力する。 $2に対する代入で$0 は$1 から$NFまでをOFSを 区切りとして連結した結果に再構成される。
なお、慣れていない人向けに書くと、単にprint
と書くのはprint $0
の省略形である。
つまり、(明確に記述されているわけではないが)単純にprint $0
を実行するだけでは$0
は再構成されないことが推察される。再構成されない限りOFS
は反映されず、元の文字列がそのまま出力されることになるというわけである。
フィールド代入の結果元のデータを変更せず、かつ$0
の再構成を誘発し、最も短く書ける文が$1=$1;
というわけだ。
ちなみに、$0=$0;
は再構成を発生させないので、今回の問題の解決には効果がない(試されたし)。
執筆理由
久しぶりにawkを使ってトラブった。基本的な内容だが、意外に日本語記事が少なかったため。
WindowsでもWSLが使えるようになって、シェルのツールがかえって便利になってきつつあるので、こういった温故知新ツールの出番が増えるかもしれませんね。