さて、昨日のlsに続いて、今日はtarの話から、コマンドラインとコマンドオプションの微妙な関係について。
tarコマンドとの不毛な闘い
これまた実話から始まります。日次で動いているログファイルのバックアップ処理が異常終了した。どうにもtarコマンドが動いていない、と。原因はトランザクションが増えたことでログファイル数が増大し、tarコマンド引数の上限を超えたという事象でした。
担当はそのとき、「対象ファイル数を減らすため、日次処理を時間毎にする」という対応をしていたようですが、それってまた増えたら同じこと起きるわけで・・・というわけで、調べたのです。
「引数の上限」。これが今日の本題なんですが、この「引数の上限」は誰の上限か。
# tar zcvf /tmp/test.tgz 2015* -bash: /bin/tar: 引数リストが長すぎます
はい、シェルの制限なんですね。シェルがコマンドに引数として渡せる数は一般には1万くらいが上限です。これを設定で変えるって話は見たことがないのでなんとも言えませんが、通常、1万ファイル超えると、これが出ると思って良いです。
解法として一般に出ているのは、rmコマンドのケースでechoとxargsを使う方法ですが、tarの場合はまとめてアーカイブしたいのでこれが使えません。ということで、1万個以上のファイルをtarに渡すにはどうすれば良いか。
その1。別ディレクトリに移動してアーカイブする。結構良くやる方法です。
# mkdir 20151220 # for file in 201512*.txt; do mv $a 20151220/ ; done # tar zcvf 20151220.tgz 20151220 # rm -rf 20151220
デメリットはディレクトリツリーが一階層増えることくらいで、安定性も高く、常套句と言えば常套句です。ちなみに、forやechoのようなシェルbuild-inファンクションは、1万個制限がありません。
その2。変態技として、tarの場合、「ファイルの追加」ができるので、前述のechoとxargsと同じことが出来ます。
# echo * | xargs tar rf /tmp/test.tar
追記を行うためには、圧縮アーカイブ(zオプション)が使えませんが・・・まあ、tarコマンドのtarコマンドらしい使い方といえば、言えなくもない。個人的にはちょっと微妙。
その3。で、お勧めは、ファイルリストを使う方法です。
# /bin/ls -U > /tmp/list # tar zcf /tmp/test.tar.gz --files-from=/tmp/list
一度ファイルリストを作る。常套句中の常套句ですが、これでファイルの数がいくらあっても怖くない。また、ファイルの選択も、tarコマンドのexcludeオプションではなく、grepでリストを編集するなどできますので便利です。かつ、圧縮もできます。
さらに調べると、tarには、「アーカイブしたファイルを削除する」という–remove-filesオプションもあり、コマンド1行でバックアップに係るすべてのオペレーションが完結できてしまいます。ただし、このとき、処理が途中で止まったら、アーカイブと元ファイルがどういう状態になるかはちょっと不安ですがね。なので、とりあえず捨てても良いけどとにかくどっか行けーってファイルを扱う場合とか、どうしても今すぐ容量を空けたい、って時は、–remove-filesもアリかな。
一般には、リスト取得、編集、アーカイビング、削除という一連で、終了ステータスを確認しながら処理しますか。
#!/bin/sh cd /logdir /bin/ls -U | grep `date --date yesterday +%Y%m%d` > /tmp/list.$$ if [ `wc -l /tmp/list.$$ |awk -F" " '{print $1}'` == 0 ] ; then exit; fi tar zcf `date --date yesterday +%Y%m%d`.tgz --files-from=/tmp/list.$$ if [ $? != 0 ]; then exit 1; fi for file in `cat /tmp/list.$$` ; do /bin/rm -f $file ; done rm -f /tmp/list.$$ exit 0
これならファイル数がいくつあろうが、処理できます。
この、コマンドラインオプションがコマンドにどう渡るかって、意外にトラブりがちなので注意が必要というのが持論です。良くあるのが、「コマンドラインで与えた正規表現・ワイルドカードをシェルに解釈されてしまい、コマンドが想定通り動かない」ってやつ。上記tarコマンドでも、アーカイブ対象のファイル名パターンをコマンドラインで与えられますが、シェルに解釈されると空になったり、下手すると別の環境変数等で置き換えられたりするので、要注意ってやつ。これを避けるため、–files-fromなど、一時ファイルでコマンドラインには見えないようにしたいんですよね(上の例でも、awkの辺り、どう解釈されるかを考えると若干不安になる感じが)。
というわけで私は、ファイル処理する場合、なるべく引数にパターンを書くのではなく、grep等でリストをfixしてから、処理をする、という段取りを心がけています。
大切なことは、「引数リストが長すぎます」って言われた瞬間に、「tar使えねー」とか「対象減らさないとダメ?」と思うのではなく、そのコマンドの本来の能力や機能を生かして、シンプルかつ安全な使い方をして行きましょうよ、ということです。振り返って見ると、今年は結構このパターンの解析や改善が多かった気がします・・・。
コメント
–files-from=で指定するファイル名にも’-‘を使うと標準入力になるようです。
よって、以下の様にすればリストファイルを実際に作成せずできました。
(for i in *; do echo $i; done) | tar zcvf files.tar.gz –files-from=-
※CentOS 6.5 で確認