・基本要件

  • 指定したディレクトリ以下にあるファイルを(ディレクトリを再帰的に辿って)コピーする
  • コピー先は1つのディレクトリ
  • コピーする際にファイル名を変更する(ディレクトリ名を連結してファイル名の前に付加)

ソースコード。


#!/usr/bin/env ruby
 
require 'fileutils'
 
# コピー先のファイルパスを求める
# ==== Args
# path :: [String] コピー元のファイルパス
# base_dir :: [String] コピー元の基準絶対パス
# dst_dir :: [String] コピー先のディレクトリパス
# separator :: [String] ディレクトリ区切り文字を置換する文字列
# ==== Return
# [String] コピー先のファイルパス
def get_new_file_path(path, base_dir, dst_dir, separator='_')
  path = File.expand_path(path)
  base_dir_len = (base_dir + '/').size()
  dirs = path[base_dir_len, path.size() - base_dir_len]
  new_file_name = dirs.gsub(/\//) { separator }
  new_file_path = File.join(dst_dir, new_file_name)
  if FileTest.exist?(new_file_path)
    # すでにファイルが存在している場合に
    # 別のファイル名を提案するならこのへんを修正
    return nil
  else
    return new_file_path
  end
end
 
# ディレクトリ内のファイルをコピーする
# ==== Args
# src_dir :: [String] コピー元のディレクトリパス
# dst_dir :: [String] コピー先のディレクトリパス
# base_dir :: [String] コピー元の基準絶対パス
def flat_copy(src_dir, dst_dir, base_dir=nil)
  if !base_dir
    base_dir = File.expand_path(src_dir)
  end
  files = Dir.entries(src_dir)
  files.each{|file|
    if file != '.' && file != '..' # . とか .. とかいらない
      path = File.join(src_dir, file)
      if FileTest.directory?(path)
        # 再帰的に処理
        flat_copy(path, dst_dir, base_dir)
      elsif FileTest.file?(path)
        new_file_path = get_new_file_path(path, base_dir, dst_dir)
        if new_file_path != nil
          # ファイルコピー
          # 更新時刻と、可能なら所有ユーザ・所有グループもコピー
          FileUtils.copy(
            path, new_file_path,
            {:preserve => true, :verbose => true})
          #puts "COPY: #{path} => #{new_file_path}"
        else
          # 新しいファイル名が生成できなかった
          puts "Could't copy file: #{path}"
        end
      end
    end
  }
end
 
# 引数が足りないときは usage を表示してプログラムを終了
if ARGV.size < 2
  puts "Usage: ruby #{File.basename(__FILE__)} <src_dir> <dst_dir>"
  exit()
end
 
# コピー元ディレクトリパスとコピー先ディレクトリパス
src_dir = ARGV[0]
dst_dir = ARGV[1]
 
# コピー先ディレクトリが無いときは作る
if FileTest.exist?(dst_dir) == false
  FileUtils.mkdir_p(dst_dir)
end
 
# メインのファイルコピー処理
flat_copy(src_dir, dst_dir)

コピーのテスト。
コピー元(src)のディレクトリ・ファイル構成。


$ tree
.
├── flatcopy.rb
└── src
    ├── a
    │   └── 1.txt
    └── b
        ├── 2.txt
        ├── c
        │   └── 3.txt
        ├── c_3.txt
        └── d
            ├── 4.txt
            └── e
                └── 5.txt
 
6 directories, 7 files

実行結果。
コピー先でファイル名が衝突した場合は「Could't copy file」と表示してコピーしない。


$ ruby flatcopy.rb ./src ./dst
cp -p ./src/.DS_Store ./dst/.DS_Store
cp -p ./src/a/1.txt ./dst/a_1.txt
cp -p ./src/b/2.txt ./dst/b_2.txt
cp -p ./src/b/c/3.txt ./dst/b_c_3.txt
Could't copy file: ./src/b/c_3.txt
cp -p ./src/b/d/4.txt ./dst/b_d_4.txt
cp -p ./src/b/d/e/5.txt ./dst/b_d_e_5.txt

.DS_Store ファイルもコピーされちゃってる。。。

コピー元(src)・コピー先(dst)のディレクトリ・ファイル構成。
コピー元ファイルの更新時刻がちゃんと引き継がれている。


$ tree -D --timefmt="%H:%M:%S"
.
├── [18:27:02]  dst
│   ├── [18:23:14]  a_1.txt
│   ├── [18:23:18]  b_2.txt
│   ├── [18:23:22]  b_c_3.txt
│   ├── [18:23:26]  b_d_4.txt
│   └── [18:23:30]  b_d_e_5.txt
├── [17:28:29]  flatcopy.rb
└── [18:24:39]  src
    ├── [18:24:04]  a
    │   └── [18:23:14]  1.txt
    └── [18:26:43]  b
        ├── [18:23:18]  2.txt
        ├── [18:24:22]  c
        │   └── [18:23:22]  3.txt
        ├── [18:23:36]  c_3.txt
        └── [18:24:31]  d
            ├── [18:23:26]  4.txt
            └── [18:24:39]  e
                └── [18:23:30]  5.txt
 
7 directories, 12 files

何年か前にC言語で同じプログラムを書いて使っていたんだけど、C言語だと修正とか面倒なので、今回Rubyで書きなおしてみた。(っていま探してみたらC言語版のソースコードみつからなかった・・・)

コピー先のファイルパスを求める部分を修正すればファイル名もカスタマイズできるはず。

ちなみに今回の動作環境は Mac OS X Lion & Ruby 1.8系。


$ uname -mrsv
Darwin 11.3.0 Darwin Kernel Version 11.3.0: Thu Jan 12 18:47:41 PST 2012; root:xnu-1699.24.23~1/RELEASE_X86_64 x86_64
 
$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [universal-darwin11.0]

tags: ruby

Posted by NI-Lab. (@nilab)