CSV FILEの重複レコードをシェルスクリプトで除去する

AWK(gawk)コマンドの連想配列の活用

公開日: 2022-08-06
更新日: 2022-08-29

OS ubuntu 20.04 LTS Focal Fossa
Requirement gawkインストール済み

Table of Contents

Dependency: gawkのインストール

gawkコマンドをインストールし, PATHがgawkに対して設定されているか確認します. link currently points to /usr/bin/gawk という文字列が確認できれば問題ないです.

1
2
3
4
5
6
7
8
9
% sudo apt install gawk
% sudo update-alternatives --display awk
awk - auto mode
  link best version is /usr/bin/gawk
  link currently points to /usr/bin/gawk
  link awk is /usr/bin/awk
  slave awk.1.gz is /usr/share/man/man1/awk.1.gz
  slave nawk is /usr/bin/nawk
  slave nawk.1.gz is /usr/share/man/man1/nawk.1.gz

今回やりたいこと:CSV FILEの重複レコードを除去

以下のようにソートされずにレコードが格納されてているCSV FILE(test.csv)が与えられたときに, レコードの出現順番を維持しながら,重複レコードを除去したいとします.

postal_code,PREF_NAME,CITY_NAME,S_NAME
7793501,徳島県,吉野川市,美郷
7793505,徳島県,吉野川市,美郷
7793502,徳島県,吉野川市,美郷
7793403,徳島県,吉野川市,山川町
7793402,徳島県,吉野川市,山川町
7793407,徳島県,吉野川市,山川町
7793405,徳島県,吉野川市,山川町
7793502,徳島県,,
7793404,徳島県,吉野川市,山川町
7793410,徳島県,吉野川市,山川町
7793410,徳島県,吉野川市,美郷
7793501,徳島県,吉野川市,美郷
7793505,徳島県,吉野川市,美郷
7793502,徳島県,吉野川市,美郷
7793502,徳島県,,
7793403,徳島県,吉野川市,山川町
7793402,徳島県,吉野川市,山川町
7793402,徳島県,,
,,,,
7793407,徳島県,吉野川市,山川町
7793405,徳島県,吉野川市,山川町
7793404,徳島県,吉野川市,山川町
7793410,徳島県,吉野川市,山川町
7793410,徳島県,吉野川市,美郷
,,,,

ゴールとしての出力結果は以下です:

postal_code,PREF_NAME,CITY_NAME,S_NAME
7793501,徳島県,吉野川市,美郷
7793505,徳島県,吉野川市,美郷
7793502,徳島県,吉野川市,美郷
7793403,徳島県,吉野川市,山川町
7793402,徳島県,吉野川市,山川町
7793407,徳島県,吉野川市,山川町
7793405,徳島県,吉野川市,山川町
7793502,徳島県,,
7793404,徳島県,吉野川市,山川町
7793410,徳島県,吉野川市,山川町
7793410,徳島県,吉野川市,美郷
7793402,徳島県,,
,,,,

シェルスクリプト解説

実行コマンド

1
2
% awk -F',' '!seen[$0]++' ./test.csv
% awk -F',' '!seen[$0]++' ./test.csv > result.csv #result.csvに出力結果を保存したい場合

上記コマンドの実行結果のレコード数を確認してみると

1
2
3
4
% cat test.csv | wc -l
25
% cat result.csv | wc -l
14

解説

awk -F',' '!seen[$0]++' ./test.csvは3つの構成要素に分解できます:

-F',' FIELD SEPARATOR, 今回はCSV FILEなので , を指定しています
'!seen[$0]++' seenという実行者が任意に定義した連想配列を用いて重複チェック
./test.csv awkコマンドが動作する対象ファイル

連想配列(associative array)って?

まず配列と連想配列の違いですが,

  • 配列 : インデックスが数字であるもの
  • 連想配列 : インデックスが文字列であるもの, ハッシュや辞書も連想配列

そもそもAWKには変数の型がないので, 実質的には配列と連想配列はAWK界隈では一緒になります.

連想配列を用いてpostal_code毎の出現回数を出力してみる

./test.csvCSV FILEの構造として, 先頭レコードがカラム名で, ときおりpostal_codeが入力されていないレコードが存在することがわかっているので, カラム名とpostal_codeがNULLのレコードを除去した上でpostal_code毎の出現回数を出力したいとします.

  • 先頭レコードはそもそも除去
  • 連想配列を定義し, valueとして出現回数を入力
  • 連想配列のkeyが空文字の場合は出力しない

とすればできるはずで, その実行例が以下です:

1
2
3
4
5
6
7
8
9
10
% awk -F',' '{NR!=1 && pcode[$1]++}END{for (var in pcode) if(var != "") print var, "", pcode[var]+1, " times"}' ./test.csv
7793402  3  times
7793403  2  times
7793404  2  times
7793405  2  times
7793407  2  times
7793410  4  times
7793501  2  times
7793502  4  times
7793505  2  times

上記の例ではpcodeという連想配列を定義していますが, このことから重複レコード排除におけるseenは 実行者が任意に名前付けした連想配列であることがわかります.

連想配列seen[$0]のkey/valueの定義について

seen[$0]++という形で重複排除シェルスクリプトは連想配列を定義しています. seen部分は連想配列オブジェクトで, keyが$0とされていますが,これはレコード全体を意味しています.

$0 レコード全体
$1 1列目
$2 2列目
$N N列目

++部分は単なるインクリメント演算子で, keyと合致するレコードが出現するたびに+1されます. 操作は変数アクセスされた後に行われます(一回しか出現しないレコードについては0).

!seen[$0]++と否定演算子が用いられていますが, AWKでは0以外の数値または空でない文字列値はtrue とされるためです. この処理を噛ますことで, 二回目以降の重複レコードはFalseと評価され, 標準出力から除外されます.

別解:cat <FILE NAME> | sort | uniq

1
2
3
% cat test.csv | sort | uniq
% cat test.csv | (sed -u 1q; sort) | uniq #headerの位置をキープしたい場合
% cat test.csv | (sed -u 1q ;sort) | uniq -c #出現回数をカウントした結果も出力したい場合

でも重複レコード排除の結果を返すことはできます. 1qは, 最初の行(ヘッダ)を表示して終了する(残りの入力をソートするために残す)オプションです. -uはメモリ節約のためのオプションと認識していただけたらです.

上記コマンドでは, 出力結果がsortされてしまうので個人的にはgawkコマンドを用いた処理の方が好ましいと考えています.

References

関連ポスト

StackExchange



Share Buttons
Share on:

Feature Tags
Leave a Comment
(注意:GitHub Accountが必要となります)