以下のような一見すると面倒そうだなと思えるファイル群に出会ったんですが、思ったよりも簡単に全ファイルを解凍して、 UTF8 に変換して 1 つのファイルにまとめちゃうってことができたのでメモです。
- ZIP ファイルがたくさんある
- その ZIP ファイルの中には複数の CSV ファイルが入っている
- 解凍後のファイル名は SJIS になっている
- 解凍後のファイルの中身は SJIS で書かれている、改行コードは CRLF
- でも 1 つあたりの CSV ファイルサイズはそんなに大きくない
- レコード中に登録NO(UID)や登録日時、登録者IDといった項目があってレコードの重複はないハズなのに重複したレコードがある。 > システムからのダウンロードを手作業で行ってて、重複してダウンロードしてしまうとかは良くある話。
環境
- Lubuntu 16.04(64bit)
- メモリ 4G
バックアップは絶対
間違って元の zip ファイルを削除してしまったり、上書きしてしまっても良いようにバックアップをとっておきます。大丈夫、大丈夫と思っても大丈夫じゃないことがありますので必ず行いましょう。
その1(最初に思いついた方法)
まずは解凍
まずはたくさんある ZIP ファイルをまとめて解凍します。これは find
コマンドを使えば簡単にできます。 -j をつけることでディレクトリ構造を無視して解凍、その際にファイル名が重複しても上書きされないようにするために -B オプションをつけています。
unzip
で -O sjis
を指定することでファイル名を SJIS に変換できるみたいなんですが、うまく行かない場面に何度が遭遇したことがあるのでココでは使っていません。
mkdir work #作業用ディレクトリの作成
cd work
find ../ -name '*.zip' -exec unzip -j -B {} \;
find
じゃなくて ls
と xargs
を使う方法もあります。
mkdir work #作業用ディレクトリの作成
cd work
ls ../*.zip | xargs -I{} unzip -j -B {}
ZIPファイルが入れ子になっている場合は?
ZIP ファイルをしたらその中にまた ZIP ファイルが入ってた。ってこともよくある話なんですが、その場合は以下のようにして再度解凍しちゃいます。解凍したらまた ZIP ファイルが出てきたら更に実行てな感じで不安がなくなるまで実行しちゃいます。実行すれば実行するほどファイルが増えて重複も増えちゃうんですが、あとで重複レコードは削除する予定なので、この時点では何回実行して重複ができてしまっても問題はないかなと思います。
*.zip みたいに指定しないのは ZIP 形式以外のファイルは解凍できずにエラーになるだけなので別に良いかなと思うのと、過去にファイル名が SJIS で文字化けしていたからか *.zip でヒットしないことがあったためです。
find ./ -type f -exec unzip -j -B {} \;
他にも ls
と xargs
を使う方法もあるんですが、短く書くというよりも自分が分かりやすい方法を選ぶのが良いんじゃないかなと思います。
ls | xargs -I{} unzip -j -B {}
不要なZIPファイルを削除
完全に解凍してしまったら ZIP ファイルが残っていると厄介なので削除してしまいます。拡張子で判断できるなら rm
でいけるんですが、今回はそうでないことを想定して中身が ZIP 形式であるファイルを探しだして削除したいと思います。
これもそんなに難しくはなくて file
と grep
を使えば案外と簡単にできてしまいます。削除する前に念のため対象となるファイルを確認しておきます。ファイルが多いとすごく時間がかかりますので、ファイル名で判断できる時は rm *.zip
で削除した方が良いかなと思います。
file * | grep 'Zip archive'
file * | grep 'Zip archive' | sed 's/: *Zip archive.*//' | xargs -I{} rm {}
その2
何回か解凍を繰り返して、削除して、…もうちょっと簡単にできそうかなと思ったのが以下の方法です。以下は解凍&削除を 6 回繰り返しています。
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 つにしてしまうため実はファイル名は何でもよかったりします。うまく変換できなかったとしてもそこにこだわる必要はないですし、文字化けのままでもほぼほぼ大丈夫だったりします。
convmv -f sjis -t utf8 --notest *
ファイルの中身を UTF8 に変更して、改行コードを LF に変更
私は使い慣れている nkf
を使っています。インストールされていない場合は sudo apt install nkf
でインストールします。 nkf
は文字コード変換と改行コード変更が同時にできちゃうので便利です。ファイル数が多いと「 Too many open files 」になっちゃうため、 find
を使っていますがファイルが少なかったら nkf 〜 *
のように書けると思います。
find ./ -type f -exec nkf -Lu -w --overwrite {} \;
nkf -Lu -w --overwrite *
ls | xargs -I{} nkf -Lu -w --overwrite {}
全ファイルをくっつけて重複を削除してひとつのファイルに圧縮して保存
「全ての CSV ファイルの先頭行に項目名が入ってるので cat
で単純にくっつけられない」ってことが分かっていればそれほど難しくなかったりします。 1 行目のデーター(ヘッダー)と、各ファイルから 1 行目のヘッダーを削除したデーターをくっつけるっていう感じです。
(cat * | head -1; ls | xargs -I{} sed '1d' {} | sort | uniq) | gzip > all.csv.gz
ちなみに「 NYSOL 」の「Mコマンド」が使えると以下のように書くことができます。 NYSOL の Mコマンドは CSV ファイルが扱えるだけでなく、少ないメモリーでも大きなファイルが扱えたりするので、使ってみるとなかなか便利だったりします。
mcat i=* | muniq k='*' | mfldname -q | gzip > all.csv.gz
gzip 圧縮していますが展開後のサイズを確認したい時は以下のような感じでしょうか。
zcat all.csv.gz | wc -l -c
$ 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 圧縮されてても使いにくいということはないかなって。