Ruby 中字符串的一部分递增

发布于 2024-10-09 08:00:22 字数 1969 浏览 2 评论 0原文

我在 Ruby 脚本中有一个方法,它尝试在保存文件之前重命名文件。它看起来像这样:

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

假设您有 3 个同名文件保存到一个目录中,我们会说该文件名为 example.mp3。这个想法是,第一个将保存为 example.mp3 (因为它不会被 if File.exists?("#{file_path}.mp3") 捕获> 脚本中的其他位置),第二个将保存为 example_#1.mp3 (因为它被上述方法的 else 部分捕获),第三个将保存为example_#2.mp3(因为它被上述方法的 if 部分捕获)。

我遇到的问题是双重的。

1) if path[-3,2] == "_#" 不适用于整数超过一位的文件 (example_#11.mp3例如),因为字符位置将是错误的(您需要它是 path[-4,2] 但这样就不能处理 3 位数字等)。

2)我永远不会遇到问题1),因为该方法不能可靠地捕获文件名。目前,它将第一个重命名为 example_#1.mp3,但第二个将重命名为相同的内容(导致它覆盖以前保存的文件)。

这对于 Stack Overflow 来说可能太模糊了,但我找不到任何可以解决递增字符串特定部分问题的内容。

提前致谢!

编辑/更新:

韦恩的下面的方法似乎可以单独工作,但当作为整个脚本的一部分包含时则不行 - 它可以将文件增加一次(从 example.mp3example_#1.mp3),但无法处理获取 example_#1.mp3 并将其递增到 example_#2.mp3。为了提供更多上下文 - 当前,当脚本找到要保存的文件时,会将名称传递给 Wayne 的方法,如下所示:

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

我对 Wayne 的脚本进行了一些编辑,所以现在看起来像这样:

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}"
    else
        return name
    end
end

我哪里出错了?再次提前致谢。

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}"
    else
        return name
    end
end

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我很OK 2024-10-16 08:00:22

正则表达式将完成:

#!/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"

这对于您正在编写的代码来说可能并不重要,但是如果您可能有多个线程或进程在同一文件上使用此算法,则在检查之前是否存在时会出现竞争条件保存:两个写入者都可以找到未使用的相同文件名并写入它。如果这对您很重要,请以某种模式打开该文件(如果存在),该模式会失败,从而挽救异常。当异常发生时,选择不同的名称。大致:

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

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
神魇的王 2024-10-16 08:00:22

这是不接受具有现有计数的文件名的变体:

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

证明:

%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"

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"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文