| Class | RsyncBackup |
| In: |
rsync_backup.rb
|
| Parent: | Object |
Rsync によるバックアップを行うためのクラス。
| dstdir | [R] | 転送先のディレクトリ. RsyncBackup.new で指定 (必須) |
| dsthost | [R] | 転送先のホスト. RsyncBackup.new で指定 (指定しない場合, ローカルとなる) |
| exclude_list | [R] | rsync の —exclude オプションの対象となるリスト. RsyncBackup.excludeopt で指定できる. |
| logfile_output | [R] | |
| noexec | [R] | |
| rsync_opt_check | [R] | rsync の "-n" オプションをつけるためのフラグ. RsyncBackup.checkopt メソッドで指定できる. |
| rsync_opt_delete | [R] | |
| rsync_opt_rsh | [R] | |
| src_dst_hash | [R] | |
| src_exclude_hash | [R] | |
| srcdir | [R] | 転送元のディレクトリ. RsyncBackup.new で指定 (必須) |
| srchost | [R] | 転送元のホスト. RsyncBackup.new で指定 (指定しない場合, ローカルとなる) |
| user | [R] | ユーザ名. デフォルトは nil で, その場合には rsync コマンドに ユーザを指定しない. |
srcdir と srchost に転送元のディレクトリとホストを指定する。 dstdir と dsthost に転送先のディレクトリとホストを指定する。 srchost, dsthost は、指定されない場合はローカルだと仮定する。 srcdir と dstdir を指定しない場合にはエラーを発生させる。
# File rsync_backup.rb, line 139
139: def initialize(srcdir=nil, srchost=nil, dstdir=nil, dsthost=nil)
140: @srcdir = srcdir
141: @srchost = srchost
142: @dstdir = dstdir
143: @dsthost = dsthost
144:
145: # @srcdir, @dstdir が文字変数でない or 空白の場合はエラー
146: if !str_and_notspace?(@srcdir) then
147: raise ArgumentError, "\"srcdir\" is not specified.\n"
148: end
149: if !str_and_notspace?(@dstdir) then
150: raise ArgumentError, "\"dstdir\" is not specified.\n"
151: end
152:
153: # @srchost と @dsthost の両方が有効な場合にはエラー
154: if str_and_notspace?(@srchost) && str_and_notspace?(@dsthost) then
155: raise ArgumentError, "rsync is not supported to transfer from remote src to remote destination.\n"
156: end
157:
158: debug(@srcdir, @srchost, @dstdir, @dsthost)
159:
160: self.noexec(nil)
161: self.logfile(nil, true)
162: self.rsh
163: self.checkopt(nil)
164: self.deleteopt(nil)
165: self.excludeopt(nil, true)
166: self.username(nil)
167: self.partly
168: end
rsync に "-n" オプションをつけて動作させるためのメソッド。 これにより、転送するファイルやディレクトリの一覧を 表示するだけで、実際にはデータの転送を行わないようにする。
check に nil を与えると "-n" オプションをはずす。
# File rsync_backup.rb, line 247
247: def checkopt(check=true)
248: @rsync_opt_check = (check) ? "-n" : " "
249:
250: debug(@rsync_opt_check)
251: end
rsync に "—delete" オプションをつけて動作させるためのメソッド。 このオプションにより、転送元に無くて転送先に存在するファイルや ディレクトリを消去するように動作する。
delete に nil を与えると "—delete" オプションをはずす。
# File rsync_backup.rb, line 261
261: def deleteopt(delete=true)
262: @rsync_opt_delete = (delete) ? "--delete" : " "
263:
264: debug(@rsync_opt_delete)
265: end
rsync に "—exclude" オプションをつけて動作させるためのメソッド。 このオプションにより、一部のファイルを転送対象からはずすことができる。 複数回呼ぶことが可能である。第1引数には転送対象からはずしたい ファイル名を書く。第2引数に true を与えると今まで与えたファイル名 がリストからクリアされる。
ただし、有効にするには、このメソッドを呼んだ後、必ず RsyncBackup.partly メソッドを呼ぶ必要がある (引数は nil でも良い) ので注意すること。
# File rsync_backup.rb, line 282
282: def excludeopt(exclude=false, clear=false)
283: if clear then
284: @exclude_list = Array.new
285: end
286:
287: if str_and_notspace?(exclude) then
288: @exclude_list.push(exclude)
289: end
290:
291: debug(@exclude_list)
292: end
ログをファイルに書き出すためのメソッド、引数 logfile に nil を渡すと ログは標準出力に書き出される。文字列を与えると、そのファイルに対して ログを書き出すようになる。文字列ではなく、且つ true を渡すと、 自動的に /tmp/以下に実行ファイルとプロセス ID を組み合わせた ファイルに書き出す。
デフォルトではログは以前のファイルを上書きするが、overwrite に nil を 与えると、以前のものに追記するようになる。
# File rsync_backup.rb, line 201
201: def logfile(logfile=true, overwrite=true)
202: redirect = (overwrite) ? ">" : ">>"
203:
204: if logfile.instance_of?(String) && /\w+/ =~ logfile.chomp then
205: @logfile_output = redirect.strip + " " + logfile.chomp.strip
206: elsif logfile then
207: @logfile_output = redirect.strip + " "
208: @logfile_output << "/tmp/" + File.basename($0.to_s) + "-" + $$.to_s
209: else
210: @logfile_output = " "
211: end
212:
213: debug(@logfile_output)
214: end
コマンドとして渡す文字列を出力するに留め、実際には rsync を動作させなく するためのメソッド。引数に nil を渡せば、rsync を動作するようにできる。
# File rsync_backup.rb, line 184
184: def noexec(noexec=true)
185: @noexec = noexec
186:
187: debug(@noexec)
188: end
一部だけを転送するためのメソッド。
dirs にディレクトリ名を渡すと、RsyncBackup.new で srcdir に指定した ディレクトリをそのまま転送するのではなく、その一部分のみを 転送するようにする。重複するものは無視される。 srcdir 以下のファイルやディレクトリの数が多くなってきた場合に、 dirs にディレクトリを指定して個別にバックアップを行うことを想定 している。 現在の仕様では、複数回 partly を用いた場合、最後の一回のみが 有効になるようになっている。
# File rsync_backup.rb, line 325
325: def partly(*dirs)
326: dirs.flatten! # 配列の平滑化 (1次元配列化)
327: dirs.delete_if{|dir| !dir.instance_of?(String)} # 文字列でないものは削除
328: dirs.collect!{|dir| dir = dir.strip} # 前後の空白を除く
329: dirs.collect!{|dir| dir = File.expand_path(dir)} # フルパスに変更
330: dirs.uniq! # 重複を無くす
331:
332: @src_dst_hash = Hash.new
333: @src_exclude_hash = Hash.new
334: if (dirs.size < 1) then
335: src = merge_dir_host_user(@srcdir, @srchost, @user)
336: dst = merge_dir_host_user(@dstdir, @dsthost, @user)
337: @src_dst_hash.store(src, dst)
338:
339: base_dir = (!str_and_notspace?(@srchost)) ? @srcdir : @dstdir
340: rsync_opt_exclude = gen_rsync_opt_exclude(base_dir, @exclude_list)
341: @src_exclude_hash.store(src, rsync_opt_exclude)
342:
343: debug(@src_dst_hash)
344: debug(@src_exclude_hash)
345: else
346: dirs.each{|directory|
347: debug(directory)
348: # directory が @srcdir と同じであれば上記設定をして next
349: if File.expand_path(@srcdir) == File.expand_path(directory) then
350: src = merge_dir_host_user(@srcdir, @srchost, @user)
351: dst = merge_dir_host_user(@dstdir, @dsthost, @user)
352: @src_dst_hash.store(src, dst)
353:
354: base_dir = (!str_and_notspace?(@srchost)) ? @srcdir : @dstdir
355: rsync_opt_exclude = gen_rsync_opt_exclude(base_dir, @exclude_list)
356: @src_exclude_hash.store(src, rsync_opt_exclude)
357:
358: debug(@src_dst_hash)
359: debug(@src_exclude_hash)
360: next
361: end
362:
363: # directory が @srcdir を異なる際の動作
364: splited = split_base_dir(@srcdir, directory)
365: unless splited then
366: print "\"#{directory}\" is not included \"#{@srcdir}\".\n"
367: next
368: end
369:
370: src = merge_dir_host_user(File.join(@srcdir, splited), @srchost, @user)
371: dst = merge_dir_host_user(File.join(@dstdir, splited), @dsthost, @user)
372:
373: @src_dst_hash.store(src, dst)
374:
375: rsync_opt_exclude = gen_rsync_opt_exclude(directory, @exclude_list)
376: @src_exclude_hash.store(src, rsync_opt_exclude)
377: }
378: debug(@src_dst_hash)
379: debug(@src_exclude_hash)
380: end
381: end
rsync の転送方法を指定するためのメソッド。何も指定しない場合は "-e ssh" が用いられる。もしも RsyncBackup.new にて設定した @srchost と @dsthost が両方とも無効 (文字列で無い、または 空文字) である場合には効果を発揮しない。
# File rsync_backup.rb, line 223
223: def rsh(rsync_rsh=nil)
224: if rsync_rsh then
225: @rsync_opt_rsh = rsync_rsh
226: elsif !@srchost && !@dsthost
227: @rsync_opt_rsh = " "
228: else
229: @rsync_opt_rsh = "-e ssh"
230: end
231:
232: debug(@rsync_opt_rsh)
233: end
@src_dst_hash に対して rsync コマンドを実行するメソッド。 最終的に呼ばれるべきメソッドである。
# File rsync_backup.rb, line 387
387: def run(cmd=nil)
388: cmdstr_base = (cmd) ? cmd : "/usr/bin/env rsync "
389: cmdstr_base << "#{@rsync_opt_check} "
390: cmdstr_base << "#{@rsync_opt_delete} "
391: cmdstr_base << "-av "
392: cmdstr_base << "#{@rsync_opt_rsh} "
393:
394: @src_dst_hash.each{|src, dst|
395: cmdstr = "" # for ruby1.6
396: # cmdstr = String.new # for ruby1.8
397: cmdstr << cmdstr_base
398: cmdstr << " #{@src_exclude_hash[src]}"
399: cmdstr << " #{src} #{dst}"
400: cmdstr << " #{logfile_output}"
401:
402: debug(cmdstr)
403:
404: print "#{cmdstr}\n"
405: system "#{cmdstr}" unless @noexec
406: }
407: end
rsync にユーザ名を指定するためのメソッド. user に文字型で且つ空白 のみでない値を与えた場合のみ, 有効になる.
# File rsync_backup.rb, line 303
303: def username(user=false)
304: if str_and_notspace?(user) then
305: @user = user.chomp.strip
306: else
307: @user = nil
308: end
309: debug(@user)
310: end
代入された変数が、配列で、且つゼロ配列ではないことを 調べるメソッド
# File rsync_backup.rb, line 545
545: def array_and_notzero?(obj)
546: debug(obj)
547:
548: if obj.instance_of?(Array) && obj.size > 0 then
549: return true
550: else
551: return false
552: end
553:
554: end
デバッグ出力用メソッド。組み込み関数 $DEBUG が真の場合 (つまり、 プログラムを $ ruby -d ./xxxxxx.rb として呼び出した場合) に debug メソッドに代入された変数を出力する。
# File rsync_backup.rb, line 174
174: def debug(*args)
175: p [caller.first, *args] if $DEBUG
176: end
2つ目の引数 (Array オブジェクト) の各パスを1つ目の引数 (String オブジェクト) に合わせた文字列に整形し、最終的に rsync のオプションとして渡す文字列を整形する。
# File rsync_backup.rb, line 490
490: def gen_rsync_opt_exclude(srcdir=nil, exclude_list=nil)
491: debug("srcdir=#{srcdir}, exclude_list=", exclude_list)
492:
493: rsync_opt_exclude = ""
494:
495: # exclude_list が配列で無かったり、サイズが 0 だったら空白文字を返す
496: if !array_and_notzero?(exclude_list) then
497: return rsync_opt_exclude
498: end
499:
500: if !str_and_notspace?(srcdir) then
501: # srcdir が無効な場合の処理
502: exclude_list.each{|exclude|
503: rsync_opt_exclude << " --exclude #{exclude}"
504: }
505: else
506: # srcdir が有効な場合
507: exclude_list.each{|exclude|
508: formed_exclude_path = Pathname.new(File.expand_path(exclude))
509: expand_srcdir = Pathname.new(File.expand_path(srcdir))
510:
511: formed_exclude =
512: formed_exclude_path.relative_path_from(expand_srcdir).to_str
513:
514: rsync_opt_exclude << " --exclude #{formed_exclude}"
515: }
516: end
517:
518: debug(rsync_opt_exclude)
519: return rsync_opt_exclude
520:
521: end
ディレクトリ名 dir, ホスト名 host, ユーザ名 user を "user@host:dir/" という rsync で用いる形式にマージする。 ディレクトリ名が無ければ nil を返すが、host や user が 無い場合にはそれぞれの部分が無いパスを返す。 なお、ディレクトリの最後にスラッシュ "/" を付け加えていることに注意 されたし。これは、rsync の src に指定する際に、スラッシュの有無が 動作を大きく変えるためである。詳しくは rsync(1) を参照せよ。
ファイル名やディレクトリ名に空白が入っている場合、 そのディレクトリ名を rsync が正しく解釈できるよう整形する。
# File rsync_backup.rb, line 421
421: def merge_dir_host_user(dir=nil, host=nil, user=nil)
422: debug(dir, host, user)
423: return nil unless dir.instance_of?(String)
424:
425: unless host.instance_of?(String) then
426: if /\w+\s+\w+/ =~ dir.strip then
427: formed_dir = "\"" + File.expand_path(dir) + "/\""
428: else
429: formed_dir = dir.strip + "/"
430: end
431:
432: debug(formed_dir)
433: return formed_dir
434: end
435:
436: if host.instance_of?(String) then
437: if /\w+\s+\w+/ =~ dir.strip then
438: formed_dir = "\"" + dir.strip.gsub(/\s/, '\ ') + "/\""
439: else
440: formed_dir = dir.strip + "/"
441: end
442: end
443:
444: url = host.strip + ":" + formed_dir
445: debug(url)
446: return url unless user
447: url = user.strip + "@" + url
448: debug(url)
449: return url
450: end
dir から base 部分を除いた部分を返す。dir がカレントパスで 記述されていたり、"~" 記法を用いていても自動的に展開する。 もしも dir が base 以下に存在しないディレクトリの場合は nil を返す。
# File rsync_backup.rb, line 458
458: def split_base_dir(base=nil, dir=nil)
459: debug("base=#{base}, dir=#{dir}", dir)
460:
461: return nil unless dir
462: fulldir = File.expand_path(dir)
463: debug("fulldir=#{fulldir}")
464:
465: return fulldir unless base
466: fullbase = File.expand_path(base)
467: debug("fullbase=#{fullbase}")
468:
469: if /^#{fullbase}\/(.*)$/ =~ fulldir
470: splited = $1
471: debug("splited=#{splited}")
472: return splited
473:
474: elsif /^#{fullbase}$/ =~ fulldir
475: debug("splited=")
476: return ""
477:
478: else
479: debug("splited=", nil)
480: return nil
481: end
482: end