【问题标题】:Increment part of a string in Ruby在 Ruby 中增加字符串的一部分
【发布时间】:2011-05-31 06:14:07
【问题描述】:

我在 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 来说可能太模糊了,但我找不到任何可以解决递增字符串特定部分的问题。

提前致谢!

编辑/更新:

Wayne 下面的方法似乎可以单独工作,但在作为整个脚本的一部分包含时却不能 - 它可以增加一个文件一次(从 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    

我稍微编辑了韦恩的脚本,现在看起来像这样:

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

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

【问题讨论】:

  • 我们需要知道 file_name 的内容才能确定出了什么问题,但是我的函数和@Phrog 都在递增的文件名中包含扩展名(例如'.jpeg')。尝试将"images/#{file_name}.jpeg" 更改为"images/#{file_name}"。如果 file_name 也包含路径,您还可以删除 images/ 留下 File.open(filename, ...)
  • file_name 不包含路径或扩展名。我想我已经发现了问题:当增量运行if File.exists?("images/#{name}.jpeg") 时,它只检查非增量文件(例如example.jpeg)。当超过 2 个具有相同 file_name 的文件在第 2 个之后出现时,被写入 example_#1.jpeg。这有意义吗?
  • 我认为文件名需要从一开始就包含路径和扩展名。那应该可以解决它。除了变量名,它可能应该是 path 而不是 filename 以便代码说的是实话。
  • @Wayne,完美地修复了它。谢谢!

标签: ruby string increment


【解决方案1】:

一个正则表达式会 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"

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

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

【讨论】:

  • 这似乎在单独运行时有效,但在作为整个脚本的一部分包含时无效。我已经扩展了我上面的原始帖子来解释。此外,还有线程示例 :) 谢谢!
【解决方案2】:

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

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"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-27
    • 1970-01-01
    • 2012-03-18
    • 1970-01-01
    • 2012-02-17
    • 2021-01-13
    • 2021-06-24
    • 1970-01-01
    相关资源
    最近更新 更多