awk, sortコマンドでCSVの囲み文字付き数値フィールドを並び替える

CSV で、数値フィールドがダブルクォートなどの囲み文字で囲まれていると、sort コマンドだけでは数値としての並び替えができない。 awk を使って数値フィールドの囲み文字を除去してから sort を実行し、その後数値フィールドを再び囲み文字で囲むことを考える。
※上記処理の対象を数値フィールドだけでなく全てのフィールドを対象にすることで期待結果が得られれば事は単純。 しかし、フィールドデータにカンマが含まれていると問題になりうるのでこの方法は採用しない。 データにカンマが含まれないことが担保できれば、その方法でも良い。

以下のファイルで試してみる。

1$ cat sample.txt
2"123","ab12c","9","a"
3"9875","w{z","8","b"
4"58","12zn9p","7","c"
5"45","@@","7","d"
6"741","i59:::","8","e"
7"1657","c\^-","9","f"

3列目を昇順、かつ、1列目を降順で並び替えてみた。

 1$ cat sample.txt | \
 2> awk -F, -v OFS=, '{gsub(/"/, "", $1); gsub(/"/, "", $3); print}' | \
 3> sort -t, -k 3,3n -k 1,1nr | \
 4> awk -F, -v OFS=, '{sub($1, "\"&\"", $1); sub($3, "\"&\"", $3); print}'
 5"58","12zn9p","7","c"
 6"45","@@","7","d"
 7"9875","w{z","8","b"
 8"741","i59:::","8","e"
 9"1657","c\^-","9","f"
10"123","ab12c","9","a"

◆解説
最初の awk コマンドについて。
-F, はセパレータがカンマであることを指定。
-v OFS=, は出力結果のセパレータにカンマを指定(これを指定しないとデフォルトのスペース区切りで出力されてしまう)。
並び替えキーの 1列目と 3列目を数値として扱うため、 gsub で一旦囲み文字のダブルクォートを削除している。
print で、囲み文字除去後のレコードを出力。
この awk の出力結果は以下となる。

1$ cat sample.txt | \
2> awk -F, -v OFS=, '{gsub(/"/, "", $1); gsub(/"/, "", $3); print}'
3123,"ab12c",9,"a"
49875,"w{z",8,"b"
558,"12zn9p",7,"c"
645,"@@",7,"d"
7741,"i59:::",8,"e"
81657,"c\^-",9,"f"

上の出力結果をパイプで sort コマンドに渡す。 sort コマンドについて。
-t, はセパレータがカンマであることを指定。
-k 3,3n は 3列目を数値と見做して昇順に並び替え。
-k 1,1nr は 1列目を数値と見做して降順に並び替え。
この sort の出力結果は以下となる。

1$ cat sample.txt | \
2> awk -F, -v OFS=, '{gsub(/"/, "", $1); gsub(/"/, "", $3); print}' | \
3> sort -t, -k 3,3n -k 1,1nr
458,"12zn9p",7,"c"
545,"@@",7,"d"
69875,"w{z",8,"b"
7741,"i59:::",8,"e"
81657,"c\^-",9,"f"
9123,"ab12c",9,"a"

最後に上の出力結果を awk コマンドに渡し、一旦除去していた囲み文字を付与する。
コマンド sub($1,"\"&\"",$1) で 1列目を囲み文字で囲む ( 左記コマンドの代わりに $1="\""$1"\"" でも良い ) 。 sed コマンドと同様に、 awk の sub コマンドでも & はパターンにマッチした文字列に置き換わる。 3列目も同様。
うっかり最後の引数を指定せず sub($3, "\"&\"") とすると、置換対象がレコード全体となり、3列目よりも前に3列目と同じ文字列があるとそこがヒットしてしまい3列目が置換されないので、要注意。
置換後に print でレコード出力。 出力結果を再度載せておく。

 1$ cat sample.txt | \
 2> awk -F, -v OFS=, '{gsub(/"/, "", $1); gsub(/"/, "", $3); print}' | \
 3> sort -t, -k 3,3n -k 1,1nr | \
 4> awk -F, -v OFS=, '{sub($1, "\"&\"", $1); sub($3, "\"&\"", $3); print}'
 5"58","12zn9p","7","c"
 6"45","@@","7","d"
 7"9875","w{z","8","b"
 8"741","i59:::","8","e"
 9"1657","c\^-","9","f"
10"123","ab12c","9","a"

◆参考
awkコマンド:http://itdoc.hitachi.co.jp/manuals/3021/3021313320/JPAS0315.HTM
sortコマンド:http://itdoc.hitachi.co.jp/manuals/3021/3021313320/JPAS0337.HTM

関連ページ