Ruby:FileUtilsで指定したファイルだけをコピーする

/
追記:216/10/11

スクリプトをオブジェクト志向のプログラムに書き換えてみました。
Rubyのプログラムをオブジェクト志向で書いてみる

導入

Rubyでディレクトリを丸ごとコピーしたい場合はFileUtilsを使えば簡単に実行出来るのですが、ディレクトリ内の特定のファイルをコピーの対象から取り除きたかったのでスクリプトを組んでみました。

実行環境

  • ubuntu16.04LTS
  • Ruby 2.3.0

Script

# coding:utf-8
require 'fileutils'

#コピー元のディレクトリのパス
src_path = "src/"
#コピー先のディレクトリのパス
dist_path ="dist/"

#コピー先のディレクトリを空にする
FileUtils.rm_r(Dir.glob("#{dist_path}*"))

#コピー先のディレクトリがなければ作成
FileUtils.mkdir(dist_path) unless FileTest.exist?(dist_path)

#コピーしないファイル&ディレクトリを指定
CP_EXC = ['.','..']

#指定したファイル&ディレクトリをコピーの候補から除外
#src_pathの中身をsrc_distにコピー
Dir::entries(src_path).each do |f|
  unless CP_EXC.include?(f)
    src = src_path + f
    FileUtils.cp_r src, dist_path
  end
end
puts "It has copied files from #{src_path} to #{dist_path}."

コピー先ディレクトリのclean

FileUtils.rm_r(Dir.glob("#{dist_path}*"))

コピーの実行の前にコピー先のディレクトリを一旦空にしておきたかったので、Dir.glob()でコピー先のディレクトリ下にあるファイルの配列を取得して消去しています。

FileUtils

FileUtilesはRubyで基本的なファイル操作を行うためのmoduleです。ディレクトリやファイルの作成・削除を行う事が出来ます。
module FileUtils(Ruby 2.3.0)

rm_r(list, options = {})

ファイルまたはディレクトリを再帰的に消去します。

mkdir(dir, options = {})

ディレクトリ dir を作成します。

cp_r(src, dest, options = {})

src を dest に再帰的にコピーします。

Dir

Rubyでディレクトリ操作を行うためのクラス。

glob(pattern, flags = 0)

ワイルドカードのパターンにマッチするファイル名を文字列の配列として返します。

entries(path)

ディレクトリ path に含まれるファイルエントリ名の 配列を返します。

失敗

上記のコードを適当なディレクトリでテストし上手くいったので実際に実行したかったディレクトリで試したところパスの指定を間違えた上に、ディレクトリ内のファイルの置き方も悪かったのでコピーどころかコピー元のファイルとスクリプトごとまとめて消去されてしまいました…。
幸いBitbucketのリモートリポジトリにファイルを保存していたので最悪のケースは免れましたが一瞬ぞっとしたのでディレクトリ内の消去はコマンドで指定した時だけ実行するようにして消去の前にパスの確認も出来るように変更、ついでに削除したくないファイルを対象から外せるようにしました。

# coding:utf-8
require 'fileutils'

#コピー元のディレクトリのパス
src_path = "src/"
#コピー先のディレクトリのパス
dist_path ="dist/"

DEFAULT_EXC = ['.','..']
#削除しないファイル&ディレクトリを指定
DEL_EXC = DEFAULT_EXC + []
#コピーしないファイル&ディレクトリを指定
CP_EXC = DEFAULT_EXC + []

#コピー先のディレクトリがなければ作成
FileUtils.mkdir(dist_path) unless FileTest.exist?(dist_path)

input = ARGV[0]
if input == "delete"
  #コマンドラインからpathの確認
  puts "Do you really want to delete the #{dist_path}? [Y|n]:"
  response = STDIN.gets
  case response.chomp
  when "Y","Yes","yes"
    Dir::entries(dist_path).each do |f|
      unless DEL_EXC.include?(f)
     src = dist_path + f
        FileUtils.rm_r(src)
      end
    end
    puts "#{dist_path} has been cleared."
  else
    puts "It had been canceled"
  end
else
  #src_pathの中身をdist_pathにコピー
  Dir::entries(src_path).each do |f|
    unless CP_EXC.include?(f)
      src = src_path + f
      FileUtils.cp_r src, dist_path
    end
  end
  puts "It has copied files from #{src_path} to #{dist_path}."
end

ARGV[0]によってスクリプトの実行の際に引数を受け付けるようにしたので”delete”と入力した場合のみコピー先のディレクトリ内の消去が実行されます。またその際ターミナル上で消去対象のパスが表示され、実行の確認を聞かれるので確認が出来た場合のみファイルが消去されます。

$ ruby copyFile.rb delete
Do you really want to delete the dist/? [Y|n]:
$ Y
dist/ has been cleared.

引数に何も指定しなければCP_EXCに指定したファイルを覗いてコピーが実行されます。

$ ruby copyFile.rb
It has copied files from src/ to dist/.

あとがき

とりあえず目的の動作をさせる事は出来ました。
コードをオブジェクト化するとかコピーや削除の対象から外すファイルの指定を外部ファイルから読み込むとか色々追加したい事もありますが、とりあえず動くものを作るのが大事なので今日はこれで良しとします。
元のファイルを消してしまった時はちょっとあせりました。そんな事もあるので個人でやってるプロジェクトでもバックアップやバージョン管理はやはり大事ですね。