Max*_*yak 187
这是一个快速的简短方法.
file_names = ['foo.txt', 'bar.txt']
file_names.each do |file_name|
text = File.read(file_name)
new_contents = text.gsub(/search_regexp/, "replacement string")
# To merely print the contents of the file, use:
puts new_contents
# To write changes to the file, use:
File.open(file_name, "w") {|file| file.puts new_contents }
end
Run Code Online (Sandbox Code Playgroud)
小智 100
实际上,Ruby确实具有就地编辑功能.和Perl一样,你可以说
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')" *.txt
Run Code Online (Sandbox Code Playgroud)
这将以双引号将代码应用于当前目录中名称以".txt"结尾的所有文件.编辑文件的备份副本将以".bak"扩展名创建(我认为"foobar.txt.bak").
注意:这似乎不适用于多行搜索.对于那些,你必须用另一个不那么漂亮的方式来做,使用正则表达式的包装脚本.
lam*_*ont 47
请记住,执行此操作时,文件系统可能空间不足,您可能会创建一个零长度文件.如果您正在执行类似于/ etc/passwd文件作为系统配置管理的一部分的操作,那么这将是灾难性的.
[编辑:请注意,就接受的答案中的就地文件编辑将始终截断文件并按顺序写出新文件.始终存在竞争条件,并发读者将看到截断或部分截断的文件,这可能是灾难性的.出于这个原因,我认为接受的答案很可能不是公认的答案.]
您需要使用以下算法:
读取旧文件并写入新文件.(你需要小心将整个文件篡改到内存中).
显式关闭新的临时文件,这是您可能抛出异常的地方,因为文件缓冲区因为没有空间而无法写入磁盘.(如果你愿意,可以抓住这个并清理临时文件,但是你需要重新抛出一些东西或者在这一点上相当困难.
修复新文件上的文件权限和模式.
重命名新文件并将其删除到位.
使用ext3文件系统,您可以保证将文件移动到位的元数据写入不会被文件系统重新排列并在写入新文件的数据缓冲区之前写入,因此这应该成功或失败.ext4文件系统也被修补以支持这种行为.如果您非常偏执,则应fdatasync()在将文件移动到位之前将系统调用作为步骤3.5.
无论语言如何,这都是最佳实践.在调用close()不抛出异常(Perl或C)的语言中,必须显式检查返回close()并在异常失败时抛出异常.
上面的建议只是简单地将文件篡改到内存中,操作它并将其写入文件将保证在完整的文件系统上生成零长度文件.您需要始终使用FileUtils.mv将完全写入的临时文件移动到位.
最后一个考虑因素是临时文件的放置.如果你在/ tmp中打开一个文件,那么你必须考虑一些问题:
可能更重要的是,当您尝试mv跨设备挂载的文件时,您将透明地转换为cp行为.将打开旧文件,旧文件inode将被保留并重新打开,文件内容将被复制.这很可能不是您想要的,如果您尝试编辑正在运行的文件的内容,则可能会遇到"文本文件繁忙"错误.这也违背了使用文件系统mv命令的目的,并且您可以仅使用部分写入的文件来运行目标文件系统.
这也与Ruby的实现无关.系统mv和cp命令的行为类似.
更可取的是在与旧文件相同的目录中打开Tempfile.这可确保不存在跨设备移动问题.在mv本身应该永远不会失败,你应该总是让一个完整的,未截断文件.在写入Tempfile时,应该遇到任何故障,例如设备空间不足,权限错误等.
在目标目录中创建Tempfile的方法的唯一缺点是:
这里有一些实现完整算法的代码(Windows代码未经测试和未完成):
#!/usr/bin/env ruby
require 'tempfile'
def file_edit(filename, regexp, replacement)
tempdir = File.dirname(filename)
tempprefix = File.basename(filename)
tempprefix.prepend('.') unless RUBY_PLATFORM =~ /mswin|mingw|windows/
tempfile =
begin
Tempfile.new(tempprefix, tempdir)
rescue
Tempfile.new(tempprefix)
end
File.open(filename).each do |line|
tempfile.puts line.gsub(regexp, replacement)
end
tempfile.fdatasync unless RUBY_PLATFORM =~ /mswin|mingw|windows/
tempfile.close
unless RUBY_PLATFORM =~ /mswin|mingw|windows/
stat = File.stat(filename)
FileUtils.chown stat.uid, stat.gid, tempfile.path
FileUtils.chmod stat.mode, tempfile.path
else
# FIXME: apply perms on windows
end
FileUtils.mv tempfile.path, filename
end
file_edit('/tmp/foo', /foo/, "baz")
Run Code Online (Sandbox Code Playgroud)
这里有一个稍微紧凑的版本,不用担心每个可能的边缘情况(如果你在Unix上并且不关心写入/ proc):
#!/usr/bin/env ruby
require 'tempfile'
def file_edit(filename, regexp, replacement)
Tempfile.open(".#{File.basename(filename)}", File.dirname(filename)) do |tempfile|
File.open(filename).each do |line|
tempfile.puts line.gsub(regexp, replacement)
end
tempfile.fdatasync
tempfile.close
stat = File.stat(filename)
FileUtils.chown stat.uid, stat.gid, tempfile.path
FileUtils.chmod stat.mode, tempfile.path
FileUtils.mv tempfile.path, filename
end
end
file_edit('/tmp/foo', /foo/, "baz")
Run Code Online (Sandbox Code Playgroud)
非常简单的用例,因为当您不关心文件系统权限时(要么您没有以root用户身份运行,要么以root身份运行并且该文件是root用户):
#!/usr/bin/env ruby
require 'tempfile'
def file_edit(filename, regexp, replacement)
Tempfile.open(".#{File.basename(filename)}", File.dirname(filename)) do |tempfile|
File.open(filename).each do |line|
tempfile.puts line.gsub(regexp, replacement)
end
tempfile.close
FileUtils.mv tempfile.path, filename
end
end
file_edit('/tmp/foo', /foo/, "baz")
Run Code Online (Sandbox Code Playgroud)
TL; DR:在所有情况下,应该至少使用它来代替接受的答案,以确保更新是原子的,并发读者不会看到截断的文件.如上所述,在此处创建与编辑文件相同的目录中的Tempfile非常重要,以避免在/ tmp安装在其他设备上时将跨设备mv操作转换为cp操作.调用fdatasync是一个增加的偏执层,但它会导致性能损失,所以我从这个例子中省略了它,因为它不常用.
sep*_*p2k 11
实际上没有一种方法可以就地编辑文件.当你可以逃脱它时通常做的事情(即文件不是太大)是,你将文件读入内存(File.read),对读取字符串(String#gsub)执行替换,然后将更改后的字符串写回文件(File.open,File#write).
如果文件是足够大的,要成为不可行的,你需要做的,是在读的块文件(如果你想更换不会跨越多行模式,然后一个块通常意味着一条线-您可以使用File.foreach来逐行读取文件,并为每个块执行替换并将其附加到临时文件.完成对源文件的迭代后,关闭它并使用FileUtils.mv临时文件覆盖它.
另一种方法是在Ruby内部使用inplace编辑(而不是从命令行):
#!/usr/bin/ruby
def inplace_edit(file, bak, &block)
old_stdout = $stdout
argf = ARGF.clone
argf.argv.replace [file]
argf.inplace_mode = bak
argf.each_line do |line|
yield line
end
argf.close
$stdout = old_stdout
end
inplace_edit 'test.txt', '.bak' do |line|
line = line.gsub(/search1/,"replace1")
line = line.gsub(/search2/,"replace2")
print line unless line.match(/something/)
end
Run Code Online (Sandbox Code Playgroud)
如果您不想创建备份,请将".bak"更改为"".
这对我有用:
filename = "foo"
text = File.read(filename)
content = text.gsub(/search_regexp/, "replacestring")
File.open(filename, "w") { |file| file << content }
Run Code Online (Sandbox Code Playgroud)
这是在给定目录的所有文件中查找/替换的解决方案.基本上我接受了sepp2k提供的答案并进行了扩展.
# First set the files to search/replace in
files = Dir.glob("/PATH/*")
# Then set the variables for find/replace
@original_string_or_regex = /REGEX/
@replacement_string = "STRING"
files.each do |file_name|
text = File.read(file_name)
replace = text.gsub!(@original_string_or_regex, @replacement_string)
File.open(file_name, "w") { |file| file.puts replace }
end
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
115803 次 |
| 最近记录: |