开发者

Increment part of a string in Ruby

I have a method in a Ruby script that is attempting to rename files before they are saved. It looks like this:

def increment (path)
    if path[-3,2] == "_#"
        print "    Incremented file with that name already exists, renaming\n"
        count = path[-1].chr.to_i + 1
        return path.chop! << count.to_s
    else
        print "    A file with that name already exists, renaming\n"
        return path << "_#1"
    end
end

Say you have 3 files with the same name being saved to a directory, we'll say the file is called example.mp3. The idea is that the first will be saved as example.mp3 (since it won't be caught by if File.exists?("#{file_path}.mp3") elsewhere in the script), the second will be saved as example_#1.mp3 (since it is caught by the else part of the above method) and the third as example_#2.mp3 (since it is caught by the if part of the above method).

The problem I have is twofold.

1) if path[-3,2] == "_#" won't work for files with an integer of more than one digit (example_#11.mp3 for example) since the character placement will be wrong (you'd need it to be path[-4,2] but then that doesn't cope with 3 digit numbers etc).

2) I'm never reaching problem 1) since the method doesn't reliably catch file names. At the moment it will rename the first to example_#1.mp3 but the second gets renamed to the same thing (causing it to overwrite the previously saved file).

This is possibly too vague for Stack Overflow but I can't find anything that addresses the issue of incrementing a certain part of a string.

Thanks in advance!

Edit/update:

Wayne's method below seems to work on it's own but not when included as part of the whole script - it can increment a file once (from example.mp3 to example_#1.mp3) but doesn't cope with taking example_#1.mp3 and incrementing it to example_#2.mp3. To provide a little more context - currently when the script finds a file to save it is passing the name to Wayne's method like this:

file_name = increment(image_name)
File.open("images/#{file_name}.jpeg", 'w') do |output|
    open(image_url) do |input|
        output << input.read
    end
end    

I've edited Wayne's script a little so now it looks like this:

def increment (name)
    name = name.gsub(/\s{2,}|(http:\/\/)|(www.)/i, '')
    if File.exists?("images/#{name}.jpeg")
        _, filename, count, extension = *name.match(/(\A.*?)(?:_#(\d+))?(\.[^.]*)?\Z/)
        count = (count || '0').to_i + 1
        "#{name}_##{count}#{extension}"
    els开发者_如何学Goe
        return name
    end
end

Where am I going wrong? Again, thanks in advance.


A regular expression will git 'er done:

#!/usr/bin/ruby1.8

def increment(path)
  _, filename, count, extension = *path.match(/(\A.*?)(?:_#(\d+))?(\.[^.]*)?\Z/)
  count = (count || '0').to_i + 1
  "#{filename}_##{count}#{extension}"
end

p increment('example')        # => "example_#1"
p increment('example.')       # => "example_#1."
p increment('example.mp3')    # => "example_#1.mp3"
p increment('example_#1.mp3') # => "example_#2.mp3"
p increment('example_#2.mp3') # => "example_#3.mp3"

This probably doesn't matter for the code you're writing, but if you ever may have multiple threads or processes using this algorithm on the same files, there's a race condition when checking for existence before saving: Two writers can both find the same filename unused and write to it. If that matters to you, then open the file in a mode that fails if it exists, rescuing the exception. When the exception occurs, pick a different name. Roughly:

loop do
  begin
    File.open(filename, File::CREAT | File::EXCL | File::WRONLY) do |file|
      file.puts "Your content goes here"
    end
    break
  rescue Errno::EEXIST
    filename = increment(filename)
    redo
  end
end


Here's a variation that doesn't accept a file name with an existing count:

def non_colliding_filename( filename )
  if File.exists?(filename)
    base,ext = /\A(.+?)(\.[^.]+)?\Z/.match( filename ).to_a[1..-1]
    i = 1
    i += 1 while File.exists?( filename="#{base}_##{i}#{ext}" )
  end
  filename
end

Proof:

%w[ foo bar.mp3 jim.bob.mp3 ].each do |desired|
  3.times{
    file = non_colliding_filename( desired )
    p file
    File.open( file, 'w' ){ |f| f << "tmp" }
  }
end
#=> "foo"
#=> "foo_#1"
#=> "foo_#2"
#=> "bar.mp3"
#=> "bar_#1.mp3"
#=> "bar_#2.mp3"
#=> "jim.bob.mp3"
#=> "jim.bob_#1.mp3"
#=> "jim.bob_#2.mp3"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜