AWK で表を作るとき printf などを毎回ググっているのでとりあえず自分がよく使うぶんをまとめておく記事。
はじめに
まずはテストデータを用意する。基本的な成績を持つ野球選手の CSV だ。このデータは LLM に作らせた。
チーム,選手名,守備位置,打率,防御率,OPS,エラー率,打席数,安打数,ホームラン数
東京サンダーズ,佐藤悠斗,外野手,0.312,-,0.890,0.015,512,160,28
東京サンダーズ,高橋蓮,内野手,0.275,-,0.745,0.022,478,131,12
東京サンダーズ,山本海斗,投手,0.120,2.85,0.310,0.005,75,9,0
東京サンダーズ,中村翔,捕手,0.248,-,0.690,0.018,420,104,8
大阪ブレイバーズ,井上大輝,外野手,0.298,-,0.860,0.012,530,158,25
大阪ブレイバーズ,木村直樹,内野手,0.265,-,0.720,0.028,490,130,10
大阪ブレイバーズ,小林誠,投手,0.105,3.40,0.280,0.004,68,7,0
大阪ブレイバーズ,藤田亮,捕手,0.255,-,0.705,0.020,410,105,9
名古屋フェニックス,松田悠真,外野手,0.321,-,0.910,0.010,545,175,30
名古屋フェニックス,岡田健太,内野手,0.260,-,0.700,0.030,460,120,11
名古屋フェニックス,石井涼,投手,0.140,2.60,0.350,0.006,80,11,1
名古屋フェニックス,森優斗,捕手,0.238,-,0.680,0.019,395,94,7
このデータを検算するスクリプトを書いてみよう。 LLM に作らせたものだから数値の正確性に疑いがあるのはわかりきっている。
# check_avg.awk
# 打席数・安打数・打率の整合性をチェックする簡易的スクリプト
# AVG_DIGIT:
# 打率比較の小数桁数(-v AVG_DIGIT=3 のように指定)
BEGIN {
# 許容誤差(epsilon)
digits = (AVG_DIGIT == "" ? 3 : AVG_DIGIT) + 0
# 許容誤差を桁数から計算(丸め誤差を考慮して 0.5 を掛ける)
eps = 0.5 / (10 ** digits)
# エラーフラグを明示的に初期化
bad = 0
}
NR == 1 {
for (i = 1; i <= NF; i++) header[$i] = i
next
}
{
pa = $(header["打席数"]) + 0
hit = $(header["安打数"]) + 0
avg = $(header["打率"]) + 0
if (pa <= 0) next
calc = hit / pa
diff = calc - avg
if (diff < 0) diff = -diff
if (diff <= eps) next
print $(header["チーム"]), $(header["選手名"]),
"打率不一致", "入力=" avg, "計算=" calc, "差=" diff
bad = 1
}
END {
# 明示的に 0 / 1 を返す
exit (bad ? 1 : 0)
}
結果はこうなる。
$ gawk --csv -v AVG_DIGIT=3 -f ./check_avg.awk ./data.csv
東京サンダーズ 佐藤悠斗 打率不一致 入力=0.312 計算=0.3125 差=0.0005
東京サンダーズ 高橋蓮 打率不一致 入力=0.275 計算=0.274059 差=0.000941423
大阪ブレイバーズ 小林誠 打率不一致 入力=0.105 計算=0.102941 差=0.00205882
大阪ブレイバーズ 藤田亮 打率不一致 入力=0.255 計算=0.256098 差=0.00109756
名古屋フェニックス 岡田健太 打率不一致 入力=0.26 計算=0.26087 差=0.000869565
名古屋フェニックス 石井涼 打率不一致 入力=0.14 計算=0.1375 差=0.0025
まあ、ポン出しだとこんなものかもしれない。
あと
東京サンダーズ 佐藤悠斗 打率不一致 入力=0.312 計算=0.3125 差=0.0005
はおかしくないか?と思われたのなら鋭い。
これは丸めの問題で
printf "%s %s 打率不一致 入力=%.20f 計算=%.20f 差=%.20f\n",
$(header["チーム"]),
$(header["選手名"]),
avg, calc, diff > "/dev/stderr"
とでもしてみれば面白いことがわかるだろう。
printf
先程の出力を整形する。
ただし、日本語のフォントは環境でサイズが変わるのでローマ字化する。
Tokyo Sato AVG_MISMATCH 0.312 0.3125 0.0005
Tokyo Takahashi AVG_MISMATCH 0.275 0.274059 0.000941423
Osaka Kobayashi AVG_MISMATCH 0.105 0.102941 0.00205882
Osaka Fujita AVG_MISMATCH 0.255 0.256098 0.00109756
Nagoya Okada AVG_MISMATCH 0.260 0.26087 0.000869565
Nagoya Ishii AVG_MISMATCH 0.140 0.1375 0.0025
縦が揃っていないので見づらいなというふうに思うだろう。
次の基準で整形する。
- 文字列:左寄せ
-
AVG_MISMATCHが一番長いのでこれに合わせる
-
- 数値:右寄せ
- 小数:桁を揃える
- 入力値はわざわざ精度を上げない
- 計算値は精度を上げる
printf の書式指定
printf はフォーマット文字列に従って値を整形する。
基本はこう。
%[フラグ][幅][.精度]型
今回使っているものだけに絞る。
AWK の printf は C のそれに似せて作られている。
例えば、記事では書いていないが整数を出力するなら C のフォーマット指定子と同じ d が指定子になる
$ awk 'BEGIN {printf "%d\n", 1.00}'
1
$ awk 'BEGIN {printf "%3d\n", 1.001}'
1
文字列
%s : 文字列
%Ns : N文字幅で右寄せ
%-Ns : N文字幅で左寄せ
数値(浮動小数)
%f : 小数
%N.Mf : 最小表示幅が N、小数点以下が M 桁
数値が N を超えたら、幅は自動で広がる
例
{
printf "%-12s %-12s %-12s %8.3f %10.6f %10.6f\n",
$1, $2, $3, $4, $5, $6
}
これで
Tokyo Sato AVG_MISMATCH 0.312 0.312500 0.000500
Tokyo Takahashi AVG_MISMATCH 0.275 0.274059 0.000941
Osaka Kobayashi AVG_MISMATCH 0.105 0.102941 0.002059
Osaka Fujita AVG_MISMATCH 0.255 0.256098 0.001098
Nagoya Okada AVG_MISMATCH 0.260 0.260870 0.000870
Nagoya Ishii AVG_MISMATCH 0.140 0.137500 0.002500
こうなる
ポイント
- 幅を指定すると列が揃う
-
-を付けると左寄せになる -
.Mを付けると小数桁が固定される
書式の動的生成
AWK では列幅に応じた printf の書式文字列を動的に組み立てられる。
{
data[NR,1] = $1
data[NR,2] = $2
data[NR,3] = $3
data[NR,4] = $4
data[NR,5] = $5
data[NR,6] = $6
if (length($1) > w1) w1 = length($1)
if (length($2) > w2) w2 = length($2)
if (length($3) > w3) w3 = length($3)
}
END {
for (i = 1; i <= NR; i++) {
printf "%-" w1 "s %-" w2 "s %-" w3 "s %8.3f %10.6f %10.6f\n",
data[i,1], data[i,2], data[i,3],
data[i,4], data[i,5], data[i,6]
}
}
ポイント
-
printf "%-" w1 "s ..."は、文字列連結で書式を作っている- つまり、w1 の値に応じて
% -14sのような書式が実行時にできる - また変数に詰めて
printf fmt ...のような形式も可
- つまり、w1 の値に応じて
- length() は文字数を数えるだけなので、全角文字の表示幅までは見ない。今回はローマ字化しているので問題を避けている。
おわりに
AWK は grep や cut の延長として使われがちだが、整形して見やすくするところまでできる。
CSV をそのまま眺めるよりも、 一度 AWK を通して何かしらの読みやすい形式に整形してしまったほうが早い場面は多い。
-
printfで列を揃える - 列幅をデータから決める
- 書式を動的に組み立てる
このあたりを押さえると、単なるログ出力を読みやすい表に変えられる。