join コマンドは便利だけど、一度に2つのファイルしか join できないし、事前のソートも必要なのが少々めんどくさい...
たとえばこういうデータファイルがあったとします。(データの区切りはタブ区切り)
kinoue@kali:~/work$ cat list2.txt
key1 100
kinoue@kali:~/work$ cat list1.txt
key1 10
key2 5
kinoue@kali:~/work$ cat list3.txt
key3 20
key1 300
これをこんなふうにまとめたい。
list1.txt | list2.txt | list3.txt | |
---|---|---|---|
key1 | 10 | 100 | 300 |
key2 | 5 | ||
key3 | 20 |
でも、これを join コマンドでやるのは案外めんどくさいです。join コマンドは一度に2つのファイルしか処理できないし、そもそも join の仕様として事前にソートしておかねばならない。対象のファイル数が多かったりすると、意外に手間取ります。
というわけで、事前のソートが不要かつ複数のデータファイルを読み込んで処理できるスクリプトを awk で書いてみました。
動作確認は Kali Linux と Windows10 1607 Anniversary Update の Bash on Windows (14.04LTS) で行っています。
実行例
# ニコイチの例。これは join コマンドでもできる。
kinoue@kali:~/work$ ./multijoin.awk list1.txt list2.txt
list1.txt list2.txt
key1 10 100
key2 5
# 3コイチの例。これは join コマンドでは一発ではできない。
kinoue@kali:~/work$ ./multijoin.awk list1.txt list2.txt list3.txt
list1.txt list2.txt list3.txt
key1 10 100 300
key2 5
key3 20
使用上の注意
大した注意事項は無いのですけど、下記3点が注意事項です。
- データファイルはタブ区切りで key[tab]value のように格納されていることが必要です。
- データファイルは事前のソートは不要です。
- 出力されるデータもタブ区切りテキストになりますが、Excel等で処理したい場合は必要に応じて改行コードや文字コード変換を実施するなどの別の作業が生じます。これには unix2dos とか nkf などをご利用いただければよろしいかと。
実装
# !/usr/bin/awk -f
#
# ソートされていない複数のファイルのデータを join して matrix にするスクリプト
#
# usage:
# multijoin.awk [file1] [file2] ...
#
# 入力データ形式
# key[tab]value
BEGIN {
FS="\t"
num_files=0
current_filename=""
}
# 進捗の簡易表示
NR % 1000 == 0 {
printf "%d:%s \r", NR, FILENAME > "/dev/stderr"
}
{
# 参照中のファイルが次のファイルに変わった場合は新しいファイル名を保持しておく
if ( current_filename != FILENAME ) {
current_filename=FILENAME
filename_mapper[num_files]=current_filename
num_files++
}
# 2次元配列への代入
key=$1
value=$2
key_list[key]=1
datamatrix[key][current_filename]=value
}
END {
# 見出し行の出力
for ( i = 0 ; i < num_files ; i++ ) {
printf "\t%s",filename_mapper[i]
}
printf "\n"
# データの出力
for ( key in key_list ) {
printf "%s", key
for ( i = 0 ; i < num_files ; i++ ) {
printf "\t%s",datamatrix[key][filename_mapper[i]]
}
printf "\n"
}
}
処理時間
例えば、24個のデータファイル(それぞれ14万件以上のデータを含む)を処理するのに、自分の環境では20秒もかかりません。この例で用いたデータは、エンバカデロ・テクノロジーズの統合開発環境製品であるRAD Studio/Delphi/C++Builderの最新リリース10.2Tokyoのインストールオプションを変えてインストールを実施後にHDD上に存在するファイルおよびファイルサイズの一覧です。(ファイルの数がやたらと多いのは、これは c:¥Windows などのファイルも含んでいるためであり、RAD Studio/Delphi/C++Builderだけでこの数のファイルがインストールされるわけではありませんよ)
kinoue@kali:~/work$ wc -l *.txt
190059 cpp_all.txt
170835 cpp_Android.txt
175285 cpp_iOS.txt
168511 cpp_osx32.txt
164419 cpp_win32.txt
168592 cpp_win64.txt
178218 delphi_all.txt
158540 delphi_Android.txt
164147 delphi_iOS.txt
158138 delphi_Linux.txt
156896 delphi_osx32.txt
153207 delphi_win32.txt
157810 delphi_win64.txt
209082 dunitx.txt
206450 helpfile.txt
142957 ideonly.txt
209469 interbase_desktop.txt
209295 interbase_express.txt
207399 intraweb.txt
198426 languagepack-jpn.txt
140495 preinstall_Tokyo.txt
194796 radstudio_all.txt
207409 sample-sourcecode.txt
208212 teechart.txt
4298647 total
kinoue@kali:~/work$ time ./multijoin.awk *.txt > result
real 0m20.553s
user 0m18.740s
sys 0m0.972s
kinoue@kali:~/work$ wc -l result
211758 result
しかし、これらの入力ファイルを join で処理するとしたら、事前準備としてのソートだけで30秒近く掛かってしまいます。
kinoue@kali:~/work$ time for file in *.txt ; do echo $file ; sort $file > .tmp\$file ; done
cpp_all.txt
cpp_Android.txt
cpp_iOS.txt
cpp_osx32.txt
cpp_win32.txt
cpp_win64.txt
delphi_all.txt
delphi_Android.txt
delphi_iOS.txt
delphi_Linux.txt
delphi_osx32.txt
delphi_win32.txt
delphi_win64.txt
dunitx.txt
helpfile.txt
ideonly.txt
interbase_desktop.txt
interbase_express.txt
intraweb.txt
languagepack-jpn.txt
preinstall_Tokyo.txt
radstudio_all.txt
sample-sourcecode.txt
teechart.txt
real 0m30.497s
user 0m27.248s
sys 0m0.712s