Prepend a single line to file with Ruby

扶醉桌前 提交于 2019-11-28 21:27:18

This is a pretty common task:

original_file = './original_file'
new_file = original_file + '.new'

Set up the test:

File.open(original_file, 'w') do |fo|
  %w[something else].each { |w| fo.puts w }
end

This is the actual code:

File.open(new_file, 'w') do |fo|
  fo.puts 'hello'
  File.foreach(original_file) do |li|
    fo.puts li
  end
end

Rename the old file to something safe:

File.rename(original_file, original_file + '.old')
File.rename(new_file, original_file)

Show that it works:

puts `cat #{original_file}`
puts '---'
puts `cat #{original_file}.old`

Which outputs:

hello
something
else
---
something
else

You don't want to try to load the file completely into memory. That'll work until you get a file that is bigger than your RAM allocation, and the machine goes to a crawl, or worse, crashes.

Instead, read it line by line. Reading individual lines is still extremely fast, and is scalable. You'll have to have enough room on your drive to store the original and the temporary file.

fwiw this seems to work:

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

I have came up with something like this, it is a little bit more descriptive and less cryptic than other solutions I've seen:

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

And you can use it like this:

file_prepend("target_file.txt", "hello world!\n")

As some have said, probably don’t use this for larger files, but it is a simple start.

rd = IO.read 'myfile'
IO.write 'myfile', "hello\n" + rd

No mechanism exists to do what you want to do easily.

Instead, you will need to open the file, delete the file, open a new file by the old name for writing, write your content, and then write the new file from the old file's contents. That's pretty convoluted sounding but is straightforward in code:

$ 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

Note that the mechanism I've used doesn't bother checking errors -- you should probably handle errors on each File.open() request and the File.unlink() request -- and it assumes that the entire contents of the file will fit in memory. A short example:

$ 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
$ 

If you want to handle files that might not fit entirely in memory, you should code a loop sort of like this (untested -- better consider it pseudo-code):

while (data=orig.read(4096)) {
    new.write(data)
}

As an alternative, you could write to a temporary file, and if the writing process succeeds, then delete the file and rename the temporary file in place. Whichever approach makes the most sense to you.

You can try this:

File.copy_stream(myfile,tempfile)
f = File.open(myfile,'w')
f.write("Hello\n#{File.open(tempfile,'r').read}")
f.close
File.delete(tempfile)

From the command line, you can do:

ruby -i -pe '$_= "prepended text\n"+$_ if $. == 1' myfile

or more efficiently

ruby -i -pe 'BEGIN { gets; print "prepended text\n" + $_ }; ' myfile

Sadly, it turns out, the -i (in-place) options isn't truly in-place, though (and nor is seds in-place option for that matter) -- my file will have a different inode after the op.

That made me sad, because effectively, I can't filter through (prepend to) a huge file if I don't have enough diskspace for two copies of it.

It's not really difficult to do it in-place, though (filtering in general, not just for the first line). All you need to do is:

1) make sure, you have separate read and write pointers (or separate File objects for reading and writing) 2) make sure you have buffered the unread parts that you are going to rewrite 3) truncate to file at the end if your filtering operation should end up with a shorter file

I wrote wrapper class for that at https://github.org/pjump/i_rewriter .

With it, you can do

Rewriter.new("myfile") do |line, index|
  index == 0 ? "prepended text\n" + line : line
end

or with the executable:

$  i_rewriter 'index == 0 ? "prepended text\n" + line : line' myfile

Use carefully (it can corrupt your file if interrupted).

Pure, but works. Minimize file-related operations.

`#!/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
    }`
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!