技研のまつけんです。
前回の記事では、複数のアーカイブを展開してファイルやフォルダの名前の傾向を調べる過程を紹介しました。今回は、その逆の「多数のファイルを分活してアーカイブする」ためのシェルスクリプトを紹介したいと思います (前回の「まとめ」では違う予告をしましたが、その前に今回の作業をする機会があったので、先に記事にしました)。
自分がデータクレンジングしたものを誰かに渡すことがあります。その際、メールで送るにしてもサーバに置くにしても、再びアーカイブ (+圧縮) すると便利なのですが、数ギガバイトのzipやtgzだと色々と不便です。そこで、分割してアーカイブしたくなります。それを実現するのに、真っ先に思いつく方法は、a、b、cという3つのフォルダがあるのであれば (そして、3つのフォルダ内のデータ量に偏りが無いのであれば)、
tar cfz a.tgz data/a* |
tar cfz b.tgz data/b* |
tar cfz c.tgz data/c* |
のように
- aで始まるものは、a.tgz
- bで始まるものは、b.tgz
- cで始まるものは、c.tgz
としてしまうことでしょう。
しかしながら、いつもこのように綺麗に分かれているとは限りません。そうすると、どのように分ければ良いか検討しなければなりません。これを手作業でするとなると意外に面倒です。そこで、今回は、最終的なアーカイブファイルの個数の上限を指定すると、分け方を考えてくれるスクリプトを作りました。こちらです:
TGT="$1" |
LIM=10 |
SKIP=1 |
find "$TGT" -type d | grep -v ^"$TGT"$ > list |
find "$TGT" -not -type d | grep -v ^"$TGT"$ > list_nd |
LEN=`cat list | sed -e 's#.#x#g' | uniq | sort | uniq | tail -n 1 | wc -c` |
declare NS |
for N in `seq $(($LEN - 1))` |
do |
M=`cat list | cut -c -$N | uniq | sort | uniq | wc -l` |
NS[$M]=$N |
if [ $LIM -lt $M ] |
then |
break |
fi |
NCH=$N |
NFL=$M |
done |
for M in "${!NS[@]}" |
do |
echo -n "${NS[$M]} chars: $M files" |
if [ ${NS[$M]} -eq $NCH ] |
then |
echo " <<< Selected <<<" |
else |
echo "" |
fi |
done |
while read PREFIX |
do |
ITEMS=`cat list_nd | grep ^"$PREFIX" | wc -l | sed -e 's#...$#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#,\+#,#g' -e 's#^,##'` |
echo "$PREFIX* -> "`basename "$PREFIX"`.tgz" ($ITEMS items)" |
done < <( cat list | cut -c -$NCH | uniq | sort | uniq ) |
echo -n "$(tput cuu $NFL)" |
while read PREFIX |
do |
ARC=`basename "$PREFIX"`.tgz |
ITEMS=`cat list_nd | grep ^"$PREFIX" | wc -l` |
ITEMS2=`echo $ITEMS | sed -e 's#...$#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#,\+#,#g' -e 's#^,##'` |
echo -n "$PREFIX* -> $ARC ($ITEMS2 items) " |
echo -n "$(tput sc)" |
if [ $SKIP -ne 0 -a -f "$ARC" ] |
then |
echo -n "<<< Comparing # of items <<<" |
ITEMS_ALL=`cat list list_nd | grep ^"$PREFIX" | wc -l` |
ITEMS_ARC=`(tar tfz $ARC | wc -l) 2> /dev/null` |
else |
ITEMS_ALL=-1 |
ITEMS_ARC=-2 |
fi |
echo -n "$(tput rc)" |
echo -n "$(tput el)" |
if [ $SKIP -ne 0 -a $ITEMS_ALL -eq $ITEMS_ARC ] |
then |
echo -n "Skipped " |
else |
echo -n "<<< Processing <<<" |
tar cfz "$ARC" "$PREFIX"* |
echo -n "$(tput rc)" |
echo -n "$(tput el)" |
echo -n "Done " |
fi |
SIZE=`wc -c < "$ARC" | sed -e 's#...$#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#...,#,\0#' -e 's#,\+#,#g' -e 's#^,##'` |
echo "($SIZE bytes)" |
done < <( cat list | cut -c -$NCH | uniq | sort | uniq ) |
rm -f list list_nd |
今回のような処理を自動で行おうと考えた場合、もっとも簡単な方法は「先頭から何文字までを共通として扱えば目的を達成できるか」を調べることでしょう。例えば、前回の記事で扱った例とよく似たフォルダ構成:
data_000/20101121-090928 |
data_000/20110301-011826 |
data_000/20110502-195236 |
data_000/20110707-085504 |
data_000/20120407-180406 |
data_000/20120509-133753 |
data_000/20120513-204704 |
data_000/20120725-104143 |
data_000/20120824-024826 |
data_000/20121116-160047 |
のような場合、「data_000/20*」は上記の全ての項目を指しますが、先頭の12文字「data_000/201」までを共通として扱い、13文字目の違いを利用して
tar cf 2010.tar data_000/2010* |
tar cf 2011.tar data_000/2011* |
tar cf 2012.tar data_000/2012* |
とすれば、3つのファイルにわけてアーカイブすることが出来ます。13文字目までを共通として扱い、14文字目の違いを利用して
tar cf 20101.tar data_000/20101* |
tar cf 20110.tar data_000/20110* |
tar cf 20120.tar data_000/20120* |
tar cf 20121.tar data_000/20121* |
とすれば、4つに分かれます。
こちらに対して実行した結果です。そのときの実行中の画面表示がこちらです:
12 chars: 1 files |
13 chars: 3 files |
14 chars: 4 files |
15 chars: 9 files |
25 chars: 10 files <<< Selected <<< |
26 chars: 28 files |
data_000/20101121-090928_* -> 20101121-090928_.tgz ( 7,091 items) Skipped (69,986,411 bytes) |
data_000/20110301-011826_* -> 20110301-011826_.tgz (10,856 items) Skipped (155,913,511 bytes) |
data_000/20110502-195236_* -> 20110502-195236_.tgz (12,824 items) Skipped (189,698,173 bytes) |
data_000/20110707-085504_* -> 20110707-085504_.tgz (12,912 items) Done (183,731,059 bytes) |
data_000/20120407-180406_* -> 20120407-180406_.tgz (12,160 items) Done (176,292,719 bytes) |
data_000/20120509-133753_* -> 20120509-133753_.tgz ( 1,672 items) Done (25,096,677 bytes) |
data_000/20120513-204704_* -> 20120513-204704_.tgz (10,080 items) Done (146,139,693 bytes) |
data_000/20120725-104143_* -> 20120725-104143_.tgz (10,160 items) <<< Processing <<< |
data_000/20120824-024826_* -> 20120824-024826_.tgz ( 3,896 items) |
data_000/20121116-160047_* -> 20121116-160047_.tgz (14,962 items) |
最初の部分は、何文字目まで共通にすると幾つのファイルが出来るか、のリストです。冒頭で、LIM=10としているので、10ファイル以下におさまる最大の値として、「25文字目まで共通」が選ばれています。実行中のステータスの表示などについては、カーソルを移動 [1][2] して上書きすることで実現しています。途中でctrl-cなどで中断すると、その位置にプロンプトが出てしまうので、trap [3] を使って、後始末をするようにしても良いかと思いますが、今回は省略しています。また、プログレスバー [4] を表示するのも良さそうです。
上に述べたように分割したファイルを受け渡す場合は、受け取った側がこちらと同じフォルダ・ファイル構成を再現できたのか、確認する手段が欲しいところです。そこで、サマリー情報を送る方法を紹介します。
以下の通り、アイテムのリストと、それぞれのファイルのMD5を記録することが出来ます:
find result | sort | gzip > items.gz |
find result -type f | sort | xargs md5sum | gzip > file-sums.gz |
受け側は、分割されたアーカイブを受け取って展開したあと、
diff <( find result | sort ) <( zcat items.gz ) |
find result -type f | sort | xargs md5sum | gzip > file-sums.gz |
のようにすることで、全てのフォルダ・ファイルを正しく受け取ることが出来たか、確認することができます。
如何でしたか? 今回は、大量のファイルを分割して送る方法、また、正しく受け渡せたのか確認する方法について紹介しました。よろしければ、活用してみてください。
さて、実は、今回のスクリプトにはバグがあります。実は、
./data/data_000/20101121-090928 |
./data/data_000/20110301-011826 |
./data/data_000/20110502-195236 |
./data/data_000/20110707-085504 |
./data/data_000/20120407-180406 |
./data/data_001/20120509-133753 |
./data/data_001/20120513-204704 |
./data/data_001/20120725-104143 |
./data/data_002/20120824-024826 |
./data/data_002/20121116-160047 |
のようなディレクトリ構成の場合、
12 chars: 1 files |
13 chars: 3 files |
17 chars: 6 files |
18 chars: 8 files |
19 chars: 9 files <<< Selected <<< |
20 chars: 12 files |
data/data_000* -> data_000.tgz |
data/data_000/20101* -> 20101.tgz |
data/data_000/20110* -> 20110.tgz |
data/data_000/20120* -> 20120.tgz |
data/data_001* -> data_001.tgz |
data/data_001/20120* -> 20120.tgz |
data/data_002* -> data_002.tgz |
data/data_002/20120* -> 20120.tgz |
data/data_002/20121* -> 20121.tgz |
という動きをしてしまうのです。これでは、同じファイルが複数のアーカイブに入ってしまいます。これについては原因はわかっている (そして、それほど困っていない) ので、いずれ直したいと思いますが、読者の皆様もパズル感覚で修正案を考えていただければ、と思います。