bashでCSVから別のフォーマットへ変換した話
バイト先で、タイトルみたいな感じのものを作ってと言われたので Bashとか全然分かりませんが、調べながら作ってみました。
例えば、こういうことがしたい
こういうCSVファイル「data.csv」があったとします。
tatibana,arisu,12
sagisawa,humika,19
これを、別のフォーマットに変換したいとします。 例えばこんな感じ。
name="tatibana arisu",age=12
name="sagisawa humika",age=19
バイト先で作るのは、もっと量が多くて、 値の変換などありますが、基本はこんな感じです。
変数を文字に置き換える
次のような内容のファイル「template.txt」を作ります。
name="${last_name} ${first_name}",age=${age}
${変数名}で囲まれた部分に対応する環境変数を設定します。
export last_name=tatibana export first_name=arisu export age=12
envsubst
というコマンドを使うと、文字列の部分を環境変数の値に置き換えてくれます。
envsubst < template.txt > output.txt
次は、ファイルからデータを取得しましょう。
CSVから特定の列の値を抜き出す
awk
で特定の列のデータを抜き出します。
# カンマ区切りの文字列から、フィールド2の値を抜き出す awk -F ',' '{print $2}' < data.csv
これらを組み合わせて、一行ずつファイルに追記していきましょう。
最終的にこうなった
#!/bin/bash touch output.txt while read f; do export last_name=`awk -F ',' '{print $1}' <<< $f` export first_name=`awk -F ',' '{print $2}' <<< $f` export age=`awk -F ',' '{print $3}' <<< $f` envsubst < template.txt >> output.txt done < data.csv
もうちょっと、上手い書き方があるような気もします。 ツッコミどころがあったらごめんなさい。
awkおまけ
作った途中の副産物。 そのうち整理したいです。
改行なしで出力する
# printは勝手に改行される awk '{printf $1; printf $2; printf $3}' <<< '1 2 3' # ORS(アウトプットレコードセパレータ)を空文字にすると改行されない awk '{ORS=""; print $1; print $2; print $3}' <<< '1 2 3' # printfは"\n"を付けないと改行されない awk '{printf $1; printf $2; printf $3}' <<< '1 2 3'
シェル変数でawkのフィールドを指定する
# シェル変数で2番目のフィールドを指定 hoge=2 awk -v a=$hoge '{print $a}' <<< 'hoge piyo fuga'
for文を使って、n~m番目までのフィールドを出力する
# 3~6番目 awk '{for (i = 3; i <= 6; ++i) print $i}' <<< '1 2 3 4 5 6 7 8' # ついでに奇数 awk '{for (i = 1; i <= NF; i+=2) print $i}' <<< '1 2 3 4 5 6 7 8'
範囲指定した値をカンマ区切りで出力する
# OFS(アウトプットフィールドセパレータ)で # フィールドの区切り文字にカンマを設定する awk '{OFS=","; print $1,$2,$3}' <<< '1 2 3' # $0の場合、区切り文字が出力されない…? # ひとつひとつ書きたくない場合、for文を使う…? awk '{ printf $3; for (i = 4; i <= 6; ++i) printf(",%d",$i); printf("\n") }' <<< '1 2 3 4 5 6 7 8'
値を外部のコマンドで加工する
# 2進数の値を16進数に変換して出力 awk '{ for (i=1; i<=NF; ++i) { cmd = "bc <<< \"obase=16; ibase=2; "$i"\"" cmd | getline var # awk内でコマンドを使った場合はcloseしないといけないらしい。 # 要検証 close(cmd) print var } }' <<< '1010 1011 1100'