3
3

More than 5 years have passed since last update.

二つのディレクトリの差分を別ディレクトリにコピーするスクリプトを作ってみました

Posted at

ネットで調べ物をしていると、次のページを見つけました。Rubyの配列を上手く使っていて、非常に勉強になる内容でした。

Ruby標準パッケージでディレクトリ比較を作ってrsyncモドキをする - それマグで!
http://takuya-1st.hatenablog.jp/entry/20100921/1285054620

せっかくなので、私も似たようなRubyスクリプトを作成してみました。

二つのディレクトリを比較し、追加や変更のあったファイル/ディレクトリを、別のディレクトリにコピーします。あるファイル/ディレクトリ一式を、他の人に提供する際に、2回目以降は、差分のファイル/ディレクトリのみを提供したい場合などに、使えるスクリプトです。(とは書いて見たものの、利用シーンは非常に限られていますね。)

使い方

次のように引数を3つ渡して、実行します。

diffcopy.bat
ruby diffcopy.rb 変更前ディレクトリ 変更後ディレクトリ 出力ディレクトリ

たとえば、次のようなディレクトリに対して、スクリプト実行するとします。

変更前ディレクトリの中身

DiffFile.txt
Dir1OnlyFile.txt
SameFile.txt

変更後ディレクトリの中身

DiffFile.txt ※「変更前ディレクトリ」の同名ファイルと差異あり
Dir2OnlyFile.txt
SameFile.txt ※「変更前ディレクトリ」の同名ファイルと同じ

変更後ディレクトリにて追加、変更のあったファイルのみが選択されて、次のようなディレクトリが作成されます。この例では、ファイルのみを扱っていますが、サブディレクトリも同様に処理されます。

出力ディレクトリ

DiffFile.txt ※「変更後ディレクトリ」のファイル
Dir2OnlyFile.txt

スクリプト

diffcopy.rb
require 'fileutils'
require 'pathname'

# ------------------------------------------------------------
# DiffCopyコマンドクラス
# ------------------------------------------------------------
class DiffCopy
    def initialize(argv)
        @args = Arguments.new(argv)
    end

    def run
        @args.parse
        if !@args.valid?
            @args.print_usage
            return
        end

        patharr1 = PathArray.new(@args.indir1)
        patharr2 = PathArray.new(@args.indir2)

        diffdata = DiffData.new(patharr1, patharr2)

        filecopy = FileCopy.new(diffdata.update_path_array, @args.outdir)
        filecopy.run
    end
end

# ------------------------------------------------------------
# コマンドライン引数クラス
# ------------------------------------------------------------
class Arguments
    attr_reader :indir1, :indir2, :outdir

    def initialize(argv)
        @argv = argv
    end

    def parse
        @indir1 = @argv[0]
        @indir2 = @argv[1]
        @outdir = @argv[2]
    end

    def valid?
        @indir1 && @indir2 && @outdir
    end

    def print_usage
        puts "Usage: diffcopy 入力ディレクトリ1 入力ディレクトリ2 出力ディレクトリ"
    end
end

# ------------------------------------------------------------
# パス配列クラス
# ------------------------------------------------------------
class PathArray < Array
    attr_reader :dir

    def initialize(dir, array = nil)
        @dir = dir
        if array == nil
            # 与えられたディレクトリ配下の、ファイル/ディレクトリを再帰的に取得する。
            Dir.chdir(dir) {
                concat(Dir.glob("**/*"))
            }
        else
            concat(array)
        end
    end

    # 追加ファイル/フォルダを取得する。
    def add_path_array(other)
        PathArray.new(dir, self - other)
    end

    # 削除ファイル/フォルダを取得する。
    def del_path_array(other)
        PathArray.new(other.dir, other - self)
    end

    # 変更ファイルを取得する。
    # ※フォルダの中身は別途確認するため、フォルダは、追加/削除のみ確認する。
    def mod_path_array(other)
        PathArray.new(dir, (self & other).delete_if {|e|
            pathname = Pathname.new(File.expand_path(e, self.dir))
            if pathname.directory?
                true
            elsif pathname.file?
                FileUtils.cmp(File.expand_path(e, self.dir), File.expand_path(e, other.dir))
            else
                $stderr.puts("対象外パス:#{src}")
            end
        })
    end
end

# ------------------------------------------------------------
# 差分クラス
# ------------------------------------------------------------
class DiffData
    attr_reader :add_path_array, :del_path_array, :mod_path_array

    def initialize(path_array1, path_array2)
        @add_path_array = path_array2.add_path_array(path_array1)
        @del_path_array = path_array2.del_path_array(path_array1)
        @mod_path_array = path_array2.mod_path_array(path_array1)
    end

    def update_path_array
        PathArray.new(@add_path_array.dir, (@add_path_array + @mod_path_array).sort)
    end
end

# ------------------------------------------------------------
# ファイル/ディレクトリ差分コピークラス
# ------------------------------------------------------------
class FileCopy
    def initialize(path_array, out_dir)
        @path_array = path_array
        @out_dir = out_dir
    end

    def run
        @path_array.each {|e|
            src = Pathname.new(File.expand_path(e, @path_array.dir))
            dest = Pathname.new(File.expand_path(e, @out_dir))
            if src.directory?
                FileUtils.mkdir_p(dest, {:verbose => $VERBOSE})
            elsif src.file?
                FileUtils.mkdir_p(dest.parent, {:verbose => $VERBOSE})
                FileUtils.cp(src, dest, {:verbose => $VERBOSE})
            else
                $stderr.puts("対象外パス:#{src}")
            end
        }
    end
end

# ------------------------------------------------------------
# コマンド実行
# ------------------------------------------------------------
if __FILE__ == $0
    main = DiffCopy.new(ARGV)
    main.run
end

リポジトリ

上記のスクリプトは、次の場所で管理してあります。
https://github.com/kurukurupapa/TryRuby.git のdiffcopy.rb

動作確認環境

  • Windows 8.1 64bit
  • ruby 1.9.2p0 (2010-08-18 revision 29036) [x64-mswin64_80]

参考ページ

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3