灰汁の教典 #9

恐怖の図嚢改革

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'