はじめに
集合演算を用いて2つのファイルの差分を取り出す方法は複数ある.例えば2つのファイルを並べて集合演算行う方法がある[1].他に,ファイルの内容を配列に格納し,配列に対して集合操作を行う方法がある[2].ここでは,後者の配列を用いた方法を述べる.
環境
- MacOS: 10.15.3
- zsh: 5.7.1
準備
2つのcsvファイルを準備する.
-
file_1.csv
,file_2.csv
を準備する. - 練習なので要素が一部重なる様にしてある.
- 個人利用を想定し,csvファイルの書き方として自分なりのルールを決めておく[7].
%cat file_1.csv
url_1,title_1
url_2,title_2
url_3,title_3
url_4,title_4
url_5,title_5
%cat file_1.csv
url_1,title_4
url_2,title_5
url_3,title_6
url_4,title_7
url_5,title_8
url_5,title_9
url_5,title_10
url_5,title_11
url_5,title_12
url_5,title_13
ファイルを1行ずつ要素として配列に格納する.
- file_1.csvの1行目は配列1の要素1,2行目は要素2・・・に格納する.
-
file_1.csv
は配列arr_1
,file_2.csv
はarr_2
に格納する.
IFS
を利用する.
- 行中のスペース
$'\n'
を区切り文字IFS
にしてからcat
で標準出力して配列に格納する[4] - 環境変数
IFS
の元の値はoriginalIFS
に代入し(1行目),IFS
に行末改行$'\n'
を代入する.すると1行を一塊として認識してくれる. - もし,
IFS
の変更なしで下記の操作をするとスペースで区切られたりしてうまくいかない.
originalIFS=$IFS
IFS=$'\n'
arr_1=($(cat file_1.csv))
arr_2=($(cat file_2.csv))
IFS=$originalIFS
2つの配列を横に並べた配列をつくる.
- これは和集合ではなく,並べただけの配列である.
-
comArr
とする(combineの意). - 全要素を並べて
()
で囲む."$arr_1[@]"
と"$arr_2[@]"
との間にスペースが必要.
comArr=("$arr_1[@]" "$arr_2[@]")
配列の要素を1つずつ取り出し,縦に1列に並べる.
- 2ファイルで集合演算を行った[7]のと同様,縦に1列に並べる必要がある.
-
xargs
を用いた方法[4]など複数方法がある(ダブルクオートやシングルクオートが含まれるとうまくいかない). -
printf
を用いた方法[6].これは汎用性が高い(特殊文字や改行,スペースなどが要素に含まれていてもうまくいく). -
printf
の箇所をecho
では代用できない.1行ずつ読みたい,1つずつ読みたいなどのときにprintf
ではうまくいき,echo
ではうまくいかないことが多い.この点でprintf
とecho
は挙動が全然異なる. -
echo
を使う場合はfor
ループを用いて1つずつ取り出す作業となる[5].
printf "%s\n" "$comArr[@]"
配列を用いた集合演算の手順概要
手順1_1: 区切り文字を行末改行にする(後で元の値に戻す)IFS=$'\n'
手順1_2: 1行ずつ配列の要素として格納する($(cat file_1.csv))
.
手順2: 2つの配列を合わせた配列をつくる("$arr_1[@]" "$arr_2[@]")
.
手順3: 全ての要素を縦1列に並べる.printf "%s\n" "$comArr[@]"
手順4: sort
とuniq
を用いて集合演算を行う.
和集合(union, A OR B)
- 以下の作業は2つのファイルを用いた方法と同じである[7].
printf "%s\n" "$comArr[@]" | sort | uniq
積集合(intersection, A AND B)
printf "%s\n" "$comArr[@]" | sort | uniq -d
排他的論理和(exclusive union, A XOR B)
printf "%s\n" "$comArr[@]" | sort | uniq -u
差集合(difference set, A - B)
- 見通しをよくするため途中で改行
\
している. - 和集合を求める(1行目).
- 和集合とarr_2を並べる(2行目).
- それを並べ替え
sort
,重ならない箇所だけとりだすuniq -u
.
(printf "%s\n" "$comArr[@]" | sort | uniq\
; printf "%s\n" "$arr_2[@]")\
| sort | uniq -u
差集合(difference set, B - A)
(printf "%s\n" "$comArr[@]" | sort | uniq\
; printf "%s\n" "$arr_1[@]")\
| sort | uniq -u
考察
- 2つのファイルを並べて集合操作を行って差分をとる方法は簡便なのが利点であるが,ファイルを2つ準備する必要があるところが難点である.
- 今回紹介した配列操作を利用する方法は手順が増えるのが難点だが,標準出力上で扱える利点がある.
- 1行ずつ要素として配列にいれる方法には,上記の方法以外では
IFS
とwhile read
を組み合わせる方法がある. - 配列を1列に並べる方法は
echo
とfor
ループを組み合わせる方法がある[5].もし要素に'\n'のなどあるときはecho -E
にしてエスケープを利かないようにする. -
printf
とecho
では挙動が全く異なる.1行ずつ取り出す,1個ずつ取り出すなどのときはprintf
が使いやすい. - 差集合の結果をリダイレクト
>>
を用いて元のファイルに追加できる.
まとめ
配列に対する集合演算を用いて,2つのcsvファイルの差分を取り出す方法を述べた.
参考
[1]https://kunst1080.hatenablog.com/entry/2015/01/25/011158
uniqコマンドを使って、論理和・論理積・排他的論理和・差集合を得る方法 - くんすとの備忘録
[2]https://qiita.com/a_atsushi/items/72f4039038d67950f0f7
Bash で配列の集合演算をする - Qiita
[3]https://qiita.com/highfrontier/items/610cd285f0c0de480ac9
コマンドラインで集合演算 - Qiita
[4]https://qiita.com/saitamasaitama/items/3ffc15bd0de154a1fb30
bashでテキストファイルを簡単に配列にする方法 - Qiita
[5]https://matsu7874.hatenablog.com/entry/2018/09/16/230822
Bashで配列の積集合・差集合を求める - matsu7874のブログ
[6]https://blog.sgnet.co.jp/2018/08/bash-set.html
SGソフトウェア開発ブログ: [bash] 配列をset(集合)として扱いたい
[7]https://qiita.com/BlackCat_617/items/8a18d4da3acdca60e4b7
shell script: 2つのcsvファイルを縦に並べ,集合演算を用いて差分を取り出す. - Qiita