覚書:バッチ処理関連 Tips
大量ファイルを扱うバッチ処理の事前検証が必要になり、急遽やっつけ作業で 検証しました。まじめにシェルスクリプトを書くのがひさしぶりなので、忘れそうな ノウハウをまとめておきます。
大量ファイルの削除
大量のファイルを削除する際に、 rm * などとやると思います。しかし、 ファイルが数万を超えているとうまく消すことが出来ません。 Shellでは * の部分がファイル名に置換されて処理されますが、ファイル名が数万と続くので コマンドラインで処理しきれずうまくいきません。
こういう場合は、 find と組み合わせて xargs を利用しましょう。
"*.txt" の部分は削除したファイルに合わせてください。
大量ファイルのアーカイブ
rm の場合と同じように、ファイルをアーカイブする際に tar コマンドでも 対象ファイルが大量にあると同じことが起こります。一つ上位のフォルダから まとめてアーカイブすると手もありますが、ファイルだけをアーカイブしたい 場合もあるでしょう。
この場合も同じように、 find コマンドと組み合わせてファイルリストを 作成して処理します。
バッチをバックグランドで
数時間掛かるバッチ処理のテストなので、ずっと見ている訳には行きません。
ただ、普通にバックグランデで実行するだけでは、sshなどでセッションが 切れてログオフされたときにそこで起動したバッチも Killされてしまいます。
これは起動したセッションの子プロセスとしてバッチのプロセスが作られるため、 セッションがログオフされた時にその子プロセスにハングアップシグナル(HUP) が送信されてしまうためです。
それを防ぐため、nohup で起動するとこのシグナルを無視してくれます。
こんな感じで起動すると、batch-test.sh はログオフしてもバックグランドで 実行され続けます。
PDF生成
今回大量のPDFファイルをテストデータとして生成する必要があったので、 Rubyを使って作成しました。 Prawn と言うRuby向けのPDF Writerを使用しました。
まず、ネタのテキストファイルを作成します。青空文庫から適当に5000個 くらいのテキストを抜いてきて、 txtフォルダにxxxxx.txt というネーミングでファイルを作成しておきます。
以下のようなRubyプログラムで引数の数だけテキストベースのPDFファイルを 生成できます。
#!/usr/bin/env ruby require "prawn" def generate_pdf(num,seed) dir = "pdf/" seq = 0 random = Random.new size_seed = seed.size() num.times do seq += 1 f_name = "./pdf-in/" + File.basename(seq.to_s, ".rb")+".pdf" p f_name Prawn::Document.generate(f_name) { ## 日本語を書く stroke_axis font "/Users/hogehoge/Library/Fonts/Ricty-Regular.ttf" text "PDFファイル・バッチ処理検証用データ" text seed[random.rand(1..size_seed)] text seed[random.rand(1..size_seed)] text seed[random.rand(1..size_seed)] text seed[random.rand(1..size_seed)] text seed[random.rand(1..size_seed)] } end end # ネタとなるテキストのフォルダからファイル名を取得 filenames = Dir.open("txt").to_a # ファイル名から全てのファイルをオープンして # 内容のテキストは配列に格納 texts = [] for filename in filenames do if filename == "." || filename == ".." then next end file = open("txt/"+filename) text = file.read() texts.push(text) file.close() end count_text = texts.size() # コマンドライン引数から生成するファイル数を target_generate = ARGV[0].to_i generate_pdf(target_generate, texts)
Javaで高速ファイルコピー
今回Javaでファイルをコピーする処理をシミュレーションしましたが、Java自体 20年ぶりくらいでちょっと戸惑いました。
最初は、 DataInputSream で自分でバッファを作ってやっていたのですが、 何やら java.nio みたいなパッケージも出来ていて今どきのやり方がある みたいです。
それがこちら。
import java.io.*; import java.nio.file.*; import java.nio.channels.*; (中略) File source = new File("input.pdf"); File dist = new File("output.pdf"); try { FileChannel srcCh = new FileInputStream(source).getChannel(); FileChannel distCh = new FileInputStream(dist).getChannel(); srcCh.transferTo(0, srcCh.size(), distCh); srcCh.close(); distCh.close(); } catch(IOException e) { e.printStackTrace(); }
なんだか、今までStreamでループ組んでたのがばからしくなりました。 trasferTo() 一発 でコピーが出来るなんて。。。