我想使用 Ruby 在文件顶部添加一行,如下所示:
# initial file contents
something
else
# file contents after prepending "hello" on its own line
hello
something
else
以下代码只是替换整个文件的内容:
f = File.new('myfile', 'w')
f.write "test string"
这是一个非常常见的任务:
original_file = './original_file'
new_file = original_file + '.new'
设置测试:
File.open(original_file, 'w') do |fo|
%w[something else].each { |w| fo.puts w }
end
这是实际的代码:
File.open(new_file, 'w') do |fo|
fo.puts 'hello'
File.foreach(original_file) do |li|
fo.puts li
end
end
将旧文件重命名为安全的名称:
File.rename(original_file, original_file + '.old')
File.rename(new_file, original_file)
证明它有效:
puts `cat #{original_file}`
puts '---'
puts `cat #{original_file}.old`
哪个输出:
hello
something
else
---
something
else
您不想尝试将文件完全加载到内存中。这将一直有效,直到您获得的文件大于您的 RAM 分配,并且机器变得缓慢,或更糟糕的是崩溃。
相反,逐行阅读。读取单行仍然非常快,并且可扩展。您的驱动器上必须有足够的空间来存储原始文件和临时文件。
fwiw 这似乎有效:
#!usr/bin/ruby
f = File.open("myfile", "r+")
lines = f.readlines
f.close
lines = ["something\n"] + lines
output = File.new("myfile", "w")
lines.each { |line| output.write line }
output.close
我想出了这样的方法,它比我见过的其他解决方案更具描述性,也更不那么神秘:
def file_prepend(file, str)
new_contents = ""
File.open(file, 'r') do |fd|
contents = fd.read
new_contents = str << contents
end
# Overwrite file but now with prepended string on it
File.open(file, 'w') do |fd|
fd.write(new_contents)
end
end
你可以这样使用它:
file_prepend("target_file.txt", "hello world!\n")
正如有些人所说,可能不会将其用于较大的文件,但这是一个简单的开始。
rd = IO.read 'myfile'
IO.write 'myfile', "hello\n" + rd
不存在任何机制可以轻松地做你想做的事情。
相反,您需要打开文件,删除文件,使用旧文件名打开一个新文件进行写入,写入内容,然后从旧文件的内容写入新文件。这听起来相当复杂,但代码很简单:
$ cat prepend.rb
#!/usr/bin/ruby
File.open("passwd", "r") do |orig|
File.unlink("passwd")
File.open("passwd", "w") do |new|
new.write "test string"
new.write(orig.read())
end
end
请注意,我使用的机制不会检查错误 - 您可能应该处理每个
File.open()
请求和 File.unlink()
请求上的错误 - 并且它假设文件的全部内容都适合内存。一个简短的例子:
$ rm passwd
$ cp /etc/passwd .
$ ./prepend.rb
$ head passwd
test stringroot:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
$
如果你想处理可能不完全适合内存的文件,你应该编写一个类似这样的循环(未经测试 - 最好将其视为伪代码):
while (data=orig.read(4096)) {
new.write(data)
}
作为替代方案,您可以写入临时文件,如果写入过程成功,然后删除该文件并就地重命名临时文件。无论哪种方法对您来说最有意义。
你可以试试这个:
File.copy_stream(myfile,tempfile)
f = File.open(myfile,'w')
f.write("Hello\n#{File.open(tempfile,'r').read}")
f.close
File.delete(tempfile)
从命令行,您可以执行以下操作:
ruby -i -pe '$_= "prepended text\n"+$_ if $. == 1' myfile
或更高效
ruby -i -pe 'BEGIN { gets; print "prepended text\n" + $_ }; ' myfile
可悲的是,事实证明,
-i
(就地)选项并不是真正就地(就此事而言,sed
也不是就地选项)——我的文件将有一个不同的操作之后的索引节点。
这让我很伤心,因为实际上,如果我没有足够的磁盘空间来容纳它的两个副本,我就无法过滤(前置)一个大文件。
不过,就地执行此操作并不困难(一般情况下进行过滤,而不仅仅是第一行)。您需要做的就是:
1)确保您有单独的读取和写入指针(或用于读取和写入的单独 File 对象) 2)确保你已经缓冲了你要重写的未读部分 3) 如果您的过滤操作最终得到的文件较短,则在末尾截断为文件
我在 https://github.org/pjump/i_rewriter 编写了包装类。
有了它,你就能做到
Rewriter.new("myfile") do |line, index|
index == 0 ? "prepended text\n" + line : line
end
或使用可执行文件:
$ i_rewriter 'index == 0 ? "prepended text\n" + line : line' myfile
请小心使用(如果中断,可能会损坏您的文件)。
纯粹,但有效。尽量减少与文件相关的操作。
`#!/bin/ruby
inv_file = File.open("./1.txt","r+") { |f|
#Read file string by-string
until f.eof?
#Searching for some
if f.readline[/condition here/]
#remember position
offset = f.pos
#Find it? - stop reading
break
end
end
#Remember what contains rest of file
tail = f.readlines
#Again to offset
f.pos = offset
#insert what necessary
f.puts "INSERTED"
#reconstruct tail
f.write tail.join
}`
虽然其他答案试图深思熟虑和优雅地做到这一点,但让我们使用 Ruby 提供的工具来完成工作:
# Option 1: backtick shell commands to the rescue
`sed -i '1ihello' myfile`
# Option 2: Read the file as a string and write it back
File.write 'myfile', "hello\n" + File.read 'myfile'