同一ファイル名を上書きせず、(1)などをファイル名に付加する(Ruby)

※注記
以前はまったくバカみたいなことをやっているので、書き直しました。クラスメソッド File.check_fname_overlapping(fname) です。fname = "hoge"(ファイル名でもディレクトリ名でもいいです)が存在していなければそれをそのまま返します。既に存在していれば、その最後に(1) を付加した String path/to/hoge(1) を返します。拡張子があれば path/to/hoge(1).rb のように拡張子の前に付きます。既にそのディレクトリに hoge(1).rb と hoge(3).rb があれば、path/to/hoge(2).rb が返ります。(2017/9/14)

class File
  def self.check_fname_overlapping(fname)
    #String{dir, base, result, suffix, r}, m, m1
    
    return fname unless File.exist?(fname)
    dir  = File.dirname(fname)
    base = File.basename(fname)
    m = /(.+)(\..+)$/.match(base)
    result, suffix = m ? [m[1], m[2]] : [base, ""]
    
    begin
      result = if (m1 = /(.+)\((\d+)\)$/.match(result))
        m1[1] + "(#{m1[2].to_i + 1})"
      else
        result + "(1)"
      end
    end while File.exist?(r = File.join(dir, result + suffix))
    r
  end
end

if __FILE__ == $0
  p File.check_fname_overlapping("nyao.jpg")
  p File.check_fname_overlapping("./fuga/hoge.rb")
end

 
自家製の Gem 'kaki-utils' に入れてあります。
kaki-utils | RubyGems.org | your community gem host
 



 
ダウンロードの時などに同じファイル名のファイルがあると、ファイルの末尾に「(1)」などを付けて上書きしないようになっていますが、それを Ruby で実装してみました。えらく大袈裟になってしまいましたが…。fname_check! メソッドで、レシーバーはファイル名の入った String クラスです。カレント・ディレクトリに同一ファイル名がなければ何もせず、あれば「(1)」などを自動的に付加します(破壊的メソッドです)。img.jpgimg(1).jpgimg(3).jpg があるところにに "img.jpg".fname_check! とすれば、"img(2).jpg" に変わります。

def getdir  #カレント・ディレクトリのファイル名・フォルダ名を配列に入れて返す
  dir = Dir.open('.')
  a = []
  while name = dir.read
    next if name == "." or name ==".."
    a << name
  end
  dir.close
  a
end

def num_exist(fname)   #存在する"(数字)"の数字部分を、配列に入れて返す
  num = [0]
  a = fname.split(/\./)
  return false if a.length != 2
  getdir.each do |fname1|
    next unless /\./ =~ fname1
    a1 = fname1.split(/\./)
    next if a1.length != 2
    next unless /(.+)\((\d+)\)$/ =~ a1[0]
    if a1[1] == a[1] and $1 == a[0]
      num << $2.to_i
    end  
  end
  return false if num == []
  num.sort
end

def num_exist1(fname)
  num = [0]
  getdir.each do |fname1|
    next unless /(.+)\((\d+)\)$/ =~ fname1
    if $1 == fname
      num << $2.to_i
    end
  end
  return false if num == []
  num.sort
end

def filename_exist(fname)   #与えられたファイル名と一致するファイル名が、カレント・ディレクトリにあるか
  getdir.each do |fname1|
    if fname1 == fname
      return true
    end
  end
  false
end

def num_max(num)  #初めて現れる空白番号を返す
  l = num.length
  return l if num[-1] + 1 == l
  num.eachnum do |i|
    return i if num[i] != i
  end
end

class Array
  def eachnum  #配列による繰り返し
    for j in 0..(self.length - 1)
      yield(j)
    end
  end
end

class String
  def fname_check!
    return unless filename_exist(self)
    if /\./ =~ self
      num = num_exist(self)
      return unless num
      a = self.split(/\./)
      self.replace(a[0] + '(' + num_max(num).to_s + ').' + a[1])
    else
      num = num_exist1(self)
      return unless num
      self.replace(self + '(' + num_max(num).to_s + ')')
    end
  end
end

Dir.chdir("D:")
i = "text.txt"
i.fname_check!
fo = open(i, "w+"); fo.puts "a"; fo.close


改良版

やっていることは殆ど変っていないです。(2015/6/21)

def num_exist(fname)   #存在する"(数字)"の数字部分を、配列に入れて返す
  num = [0]
  a = fname.split(/\./)
  return false unless a.length == 2
  Dir.glob("*").each do |fname1|
    next unless /\./.match(fname1)
    a1 = fname1.split(/\./)
    next unless a1.length == 2
    next unless m = /(.+)\((\d+)\)$/.match(a1[0])
    num << m[2].to_i if a1[1] == a[1] and m[1] == a[0]
  end
  num.uniq.sort
end

def num_exist1(fname)
  num = [0]
  Dir.glob("*").each do |fname1|
    next unless m = /(.+)\((\d+)\)$/.match(fname1)
    num << m[2].to_i if m[1] == fname
  end
  num.uniq.sort
end

def filename_exist(fname)   #与えられたファイル名と一致するファイル名が、カレント・ディレクトリにあるか
  Dir.glob("*").each {|fname1| return true if fname1 == fname}
  false
end

class Array
  def num_max       #初めて現れる空白番号を(文字列で)返す
    num = self
    ln = num.length
    return ln.to_s if num[-1] + 1 == ln
    num.each_with_index {|n, i| return i.to_s unless n == i}
  end
end

class String
  def fname_check!
    s = self
    return unless filename_exist(s)
    if /\./.match(s)
      a = s.split(/\./)
      self.replace(a[0] + '(' + num_exist(s).num_max + ').' + a[1])
    else
      self.replace(s + '(' + num_exist1(s).num_max + ')')
    end
  end
end


Dir.chdir("D:")
i = "text.txt"
i.fname_check!
fo = open(i, "w+"); fo.puts "a"; fo.close