如何用 ruby 中的正则表达式和 .sub 覆盖 txt 文件中的部分行
how to overwrite part of a line in a txt file with regex and .sub in ruby
我在 txt 文件中有以下布局。
[item] label1: comment1 | label2: foo
我有下面的代码。目标是修改文本中现有行的一部分
def replace_info(item, bar)
return "please create a file first" unless File.exist?('site_info.txt')
IO.foreach('site_info.txt','a+') do |line|
if line.include?(item)
#regex should find the data from the whitespace after the colon all the way to the end.
#this should be equivalent to foo
foo_string = line.scan(/[^"label2: "]*\z/)
line.sub(foo_string, bar)
end
end
end
请指教。也许我的 regrex
是关闭的,但是 .sub
是正确的,但是我无法覆盖 line
.
小问题:你的正则表达式不符合你的想法。 /[^"label2: "]*\z/
表示:行尾不属于 a
、b
、e
、l
、"
、[ 的任意数量的字符=43=]、冒号或 2
(参见 Character classes)。 scan
returns 一个数组,sub
不能使用。但这并不重要,因为...
小问题:line.sub(foo_string, bar)
什么都不做。它 returns 是一个更改过的字符串,但是您没有将它分配给任何东西,它就被丢弃了。 line.sub!(foo_string, bar)
会改变 line
本身,但这会导致我们...
大问题:您不能只更改读取行并期望它在文件本身中发生更改。这就像读一本书,认为你可以写出更好的台词,并期待它能改变这本书。在文本文件中更改一行的方法是从一个文件中读取并将读取的内容复制到另一个文件。如果你在读和写之间换了一行,新写的副本就会不同。最后,您可以将新文件重命名为旧文件(这将删除旧文件并自动替换为新文件)。
编辑:这是一些代码。首先,我不喜欢 IO.foreach
,因为我喜欢自己控制迭代(在我看来,IO.foreach
不像 IO#each_line
那样可读)。在正则表达式中,我使用 lookbehind 来查找标签而不将其包含在匹配中,因此我可以只替换值;出于类似的原因,我更改为 \Z
以从匹配中排除换行符。您不应该从函数返回错误消息,这就是异常的用途。我将简单的 include?
更改为 #start_with?
,因为当我们不想触发更改时,您的 item
可能会在行的其他地方找到。
class FileNotFoundException < RuntimeError; end
def replace_info(item, bar)
# check if file exists
raise FileNotFoundException unless File.exist?('site_info.txt')
# rewrite the file
File.open('site_info.txt.bak', 'wt') do |w|
File.open('site_info.txt', 'rt') do |r|
r.each_line do |line|
if line.start_with?("[#{item}]")
line.sub!(/(?<=label2: ).*?\Z/, bar)
end
w.write(line)
end
end
end
# replace the old file
File.rename('site_info.txt.bak', 'site_info.txt')
end
replace_info("item", "bar")
我在 txt 文件中有以下布局。
[item] label1: comment1 | label2: foo
我有下面的代码。目标是修改文本中现有行的一部分
def replace_info(item, bar)
return "please create a file first" unless File.exist?('site_info.txt')
IO.foreach('site_info.txt','a+') do |line|
if line.include?(item)
#regex should find the data from the whitespace after the colon all the way to the end.
#this should be equivalent to foo
foo_string = line.scan(/[^"label2: "]*\z/)
line.sub(foo_string, bar)
end
end
end
请指教。也许我的 regrex
是关闭的,但是 .sub
是正确的,但是我无法覆盖 line
.
小问题:你的正则表达式不符合你的想法。 /[^"label2: "]*\z/
表示:行尾不属于 a
、b
、e
、l
、"
、[ 的任意数量的字符=43=]、冒号或 2
(参见 Character classes)。 scan
returns 一个数组,sub
不能使用。但这并不重要,因为...
小问题:line.sub(foo_string, bar)
什么都不做。它 returns 是一个更改过的字符串,但是您没有将它分配给任何东西,它就被丢弃了。 line.sub!(foo_string, bar)
会改变 line
本身,但这会导致我们...
大问题:您不能只更改读取行并期望它在文件本身中发生更改。这就像读一本书,认为你可以写出更好的台词,并期待它能改变这本书。在文本文件中更改一行的方法是从一个文件中读取并将读取的内容复制到另一个文件。如果你在读和写之间换了一行,新写的副本就会不同。最后,您可以将新文件重命名为旧文件(这将删除旧文件并自动替换为新文件)。
编辑:这是一些代码。首先,我不喜欢 IO.foreach
,因为我喜欢自己控制迭代(在我看来,IO.foreach
不像 IO#each_line
那样可读)。在正则表达式中,我使用 lookbehind 来查找标签而不将其包含在匹配中,因此我可以只替换值;出于类似的原因,我更改为 \Z
以从匹配中排除换行符。您不应该从函数返回错误消息,这就是异常的用途。我将简单的 include?
更改为 #start_with?
,因为当我们不想触发更改时,您的 item
可能会在行的其他地方找到。
class FileNotFoundException < RuntimeError; end
def replace_info(item, bar)
# check if file exists
raise FileNotFoundException unless File.exist?('site_info.txt')
# rewrite the file
File.open('site_info.txt.bak', 'wt') do |w|
File.open('site_info.txt', 'rt') do |r|
r.each_line do |line|
if line.start_with?("[#{item}]")
line.sub!(/(?<=label2: ).*?\Z/, bar)
end
w.write(line)
end
end
end
# replace the old file
File.rename('site_info.txt.bak', 'site_info.txt')
end
replace_info("item", "bar")