shell script: 2つのcsvファイルの差分を，配列を利用した集合演算を用いて取り出す．

# はじめに
 集合演算を用いて2つのファイルの差分を取り出す方法は複数ある．例えば2つのファイルを並べて集合演算行う方法がある[[1]]．他に，ファイルの内容を配列に格納し，配列に対して集合操作を行う方法がある[[2]]．ここでは，後者の配列を用いた方法を述べる．

# 環境
- MacOS: 10.15.3
- zsh: 5.7.1

# 準備
## 2つのcsvファイルを準備する．
- `file_1.csv`，`file_2.csv`を準備する．
- 練習なので要素が一部重なる様にしてある．
- 個人利用を想定し，csvファイルの書き方として自分なりのルールを決めておく[[7]]．

```zsh:file_1.csvの内容をcatで表示する．
%cat file_1.csv
url_1,title_1
url_2,title_2
url_3,title_3
url_4,title_4
url_5,title_5
```
```zsh:file_2.csvの内容をcatで表示する．
%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`の変更なしで下記の操作をするとスペースで区切られたりしてうまくいかない．

```zsh
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[@]"`との間にスペースが必要．

```zsh
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]]．

```zsh
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]]．

```zsh
printf "%s\n" "$comArr[@]" | sort | uniq
```

# 積集合（intersection, A AND B）

```zsh
printf "%s\n" "$comArr[@]" | sort | uniq -d
```

# 排他的論理和（exclusive union, A XOR B）

```zsh
printf "%s\n" "$comArr[@]" | sort | uniq -u
```

# 差集合（difference set, A - B）
- 見通しをよくするため途中で改行`\`している．
- 和集合を求める（1行目）．
- 和集合とarr_2を並べる（2行目）．
- それを並べ替え`sort`，重ならない箇所だけとりだす`uniq -u`．

```zsh
(printf "%s\n" "$comArr[@]" | sort | uniq\
; printf "%s\n" "$arr_2[@]")\
| sort | uniq -u
```

# 差集合（difference set, B - A）

```zsh
(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


[1]:https://kunst1080.hatenablog.com/entry/2015/01/25/011158
[2]:https://qiita.com/a_atsushi/items/72f4039038d67950f0f7
[3]:https://qiita.com/highfrontier/items/610cd285f0c0de480ac9
[4]:https://qiita.com/saitamasaitama/items/3ffc15bd0de154a1fb30
[5]:https://matsu7874.hatenablog.com/entry/2018/09/16/230822
[6]:https://blog.sgnet.co.jp/2018/08/bash-set.html
[7]:https://qiita.com/BlackCat_617/items/8a18d4da3acdca60e4b7