LinuxコマンドでたくさんのZIP圧縮されたファイルを解凍してUTF8にしてくっつける

以下のような一見すると面倒そうだなと思えるファイル群に出会ったんですが、思ったよりも簡単に全ファイルを解凍して、 UTF8 に変換して 1 つのファイルにまとめちゃうってことができたのでメモです。

- ZIP ファイルがたくさんある
- その ZIP ファイルの中には複数の CSV ファイルが入っている
- 解凍後のファイル名は SJIS になっている
- 解凍後のファイルの中身は SJIS で書かれている、改行コードは CRLF
- でも 1 つあたりの CSV ファイルサイズはそんなに大きくない
- レコード中に登録NO(UID)や登録日時、登録者IDといった項目があってレコードの重複はないハズなのに重複したレコードがある。 > システムからのダウンロードを手作業で行ってて、重複してダウンロードしてしまうとかは良くある話。

## 環境
- Lubuntu 16.04(64bit)
- メモリ 4G

## バックアップは絶対
間違って元の zip ファイルを削除してしまったり、上書きしてしまっても良いようにバックアップをとっておきます。大丈夫、大丈夫と思っても大丈夫じゃないことがありますので必ず行いましょう。

## その１（最初に思いついた方法）
### まずは解凍
まずはたくさんある ZIP ファイルをまとめて解凍します。これは `find` コマンドを使えば簡単にできます。 -j をつけることでディレクトリ構造を無視して解凍、その際にファイル名が重複しても上書きされないようにするために -B オプションをつけています。

`unzip` で `-O sjis` を指定することでファイル名を SJIS に変換できるみたいなんですが、うまく行かない場面に何度が遭遇したことがあるのでココでは使っていません。

```bash:ZIPファイルの解凍
mkdir work #作業用ディレクトリの作成
cd work
find ../ -name '*.zip' -exec unzip -j -B {} \;
```

`find` じゃなくて `ls` と `xargs` を使う方法もあります。

```bash:ZIPファイルの解凍（lsとxargsを使用）
mkdir work #作業用ディレクトリの作成
cd work
ls ../*.zip | xargs -I{} unzip -j -B {}
```

### ZIPファイルが入れ子になっている場合は？

ZIP ファイルをしたらその中にまた ZIP ファイルが入ってた。ってこともよくある話なんですが、その場合は以下のようにして再度解凍しちゃいます。解凍したらまた ZIP ファイルが出てきたら更に実行てな感じで不安がなくなるまで実行しちゃいます。実行すれば実行するほどファイルが増えて重複も増えちゃうんですが、あとで重複レコードは削除する予定なので、この時点では何回実行して重複ができてしまっても問題はないかなと思います。

*.zip みたいに指定しないのは ZIP 形式以外のファイルは解凍できずにエラーになるだけなので別に良いかなと思うのと、過去にファイル名が SJIS で文字化けしていたからか *.zip でヒットしないことがあったためです。

```bash:ZIPファイルの解凍（ZIPファイルが入れ子になっていた場合）
find ./ -type f -exec unzip -j -B {} \;
```

他にも `ls` と `xargs` を使う方法もあるんですが、短く書くというよりも自分が分かりやすい方法を選ぶのが良いんじゃないかなと思います。

```bash:ZIPファイルの解凍（lsとxargsを使用）
ls | xargs -I{} unzip -j -B {}
```

### 不要なZIPファイルを削除
完全に解凍してしまったら ZIP ファイルが残っていると厄介なので削除してしまいます。拡張子で判断できるなら `rm` でいけるんですが、今回はそうでないことを想定して中身が ZIP 形式であるファイルを探しだして削除したいと思います。

これもそんなに難しくはなくて `file` と `grep` を使えば案外と簡単にできてしまいます。削除する前に念のため対象となるファイルを確認しておきます。ファイルが多いとすごく時間がかかりますので、ファイル名で判断できる時は `rm *.zip` で削除した方が良いかなと思います。

```bash:対象となるZIPファイルの確認
file * | grep 'Zip archive' 
```

```bash:ZIPファイルの削除を実行
file * | grep 'Zip archive' | sed 's/: *Zip archive.*//' | xargs -I{} rm {}
```

## その２
何回か解凍を繰り返して、削除して、…もうちょっと簡単にできそうかなと思ったのが以下の方法です。以下は解凍＆削除を 6 回繰り返しています。

```bash:その２
mkdir work
cd work
cp ../*.zip ./
for i in {1..6}; do
	ls | xargs -I{} sh -c "unzip -j -B '{}' && rm '{}'"
done
```


## ファイル名を UTF8 に変更
解凍後のファイル名が SJIS だと文字化けしてしていると思うので全ファイル名を UTF8 に変更します。 `convmv` コマンド使ったら簡単にできます。 `convmv` がない場合は `sudo apt install convmv` でインストールします。

ちなみに今回の場合は、あとでファイルをまとめて 1 つにしてしまうため実はファイル名は何でもよかったりします。うまく変換できなかったとしてもそこにこだわる必要はないですし、文字化けのままでもほぼほぼ大丈夫だったりします。

```bash:ファイル名をUTF8に変換
convmv -f sjis -t utf8 --notest *
```

## ファイルの中身を UTF8 に変更して、改行コードを LF に変更
私は使い慣れている `nkf` を使っています。インストールされていない場合は `sudo apt install nkf` でインストールします。 `nkf` は文字コード変換と改行コード変更が同時にできちゃうので便利です。ファイル数が多いと「 Too many open files 」になっちゃうため、 `find` を使っていますがファイルが少なかったら `nkf 〜 *` のように書けると思います。

```bash:ファイル内容ををUTF8に変換（その1,findを使用）
find ./ -type f -exec nkf -Lu -w --overwrite {} \;
```

```bash:ファイル内容ををUTF8に変換（その2,ファイルが少ない場合）
nkf -Lu -w --overwrite *
```

```bash:ファイル内容ををUTF8に変換（その3,lsとxargsを使用）
ls | xargs -I{} nkf -Lu -w --overwrite {}
```


## 全ファイルをくっつけて重複を削除してひとつのファイルに圧縮して保存
「全ての CSV ファイルの先頭行に項目名が入ってるので `cat` で単純にくっつけられない」ってことが分かっていればそれほど難しくなかったりします。 1 行目のデーター（ヘッダー）と、各ファイルから 1 行目のヘッダーを削除したデーターをくっつけるっていう感じです。

```bash:全ファイルをくっつけて重複を削除してgzip圧縮
(cat * | head -1; ls | xargs -I{} sed '1d' {} | sort | uniq) | gzip > all.csv.gz
```

ちなみに「 [NYSOL](https://www.nysol.jp/) 」の「Mコマンド」が使えると以下のように書くことができます。 NYSOL の Mコマンドは CSV ファイルが扱えるだけでなく、少ないメモリーでも大きなファイルが扱えたりするので、使ってみるとなかなか便利だったりします。

```bash:Mコマンドで全ファイルをくっつけて重複を削除してgzip圧縮
mcat i=* | muniq k='*'  | mfldname -q | gzip > all.csv.gz
```

gzip 圧縮していますが展開後のサイズを確認したい時は以下のような感じでしょうか。

```r:展開後サイズの確認
zcat all.csv.gz | wc -l -c
```

```r:展開後サイズの確認（実行結果）
$ zcat all.csv.gz | wc -l -c
 748654 229449752
```

gzip 圧縮されたファイルを解凍するとレコード数は 74 万行くらい、ファイルサイズは 200M くらいであることが分かります。

## なんで圧縮するの？
ファイルは小さい方が扱いが楽だからってなところでしょうか。ファイル内容にもよりますがサイズは 1/8 程度にはなると思いますし、 R の data.table::fread だと data.table::fread("zcat all.csv.gz") みたいに書けるので gzip 圧縮されてても使いにくいということはないかなって。