0

Say I have the following Ruby code which, given a hash of insert positions, reads a file and creates a new file with extra text inserted at those positions:

insertpos = {14=>25,16=>25}
File.open('file.old', 'r') do |oldfile|
    File.open('file.new', 'w') do |newfile|
        oldfile.each_with_index do |line,linenum|
            inserthere = insertpos[linenum]
            if(!inserthere.nil?)then
                line.insert(inserthere,"foo")
            end
            newfile.write(line)
        end
    end
end

Now, instead of creating that new file, I would like to modify this original (old) file. Can someone give me a hint on how to modify the code? Thanks!

oaklodge
  • 722
  • 6
  • 19
  • @SantoshSharma The OP does not want to append to a file, he/she wants to insert lines into the middle of a file. – mwp Sep 02 '16 at 05:59

3 Answers3

-1

At a very fundamental level, this is an extremely difficult thing to do, in any language, on any operating system. Envision a file as a contiguous series of bytes on disk (this is a very simplistic scenario, but it serves to illustrate the point). You want to insert some bytes in the middle of the file. Where do you put those bytes? There's no place to put them! You would have to basically "shift" the existing bytes after the insertion point "down" by the number of bytes you want to insert. If you're inserting multiple sections into an existing file, you would have to do this multiple times! It will be extremely slow, and you will run a high risk of corrupting your data if something goes awry.

You can, however, overwrite existing bytes, and/or append to the end of the file. Most Unix utilities give the appearance of modifying files by creating new files and swapping them with the old. Some more sophisticated schemes, such as those used by databases, allow inserts in the middle of files by 1. reserving space for such operations (when the data is first written), 2. allowing non-contiguous blocks of data within the file through indexing and other techniques, and/or 3. copy-on-write schemes where a new version of the data is written to the end of the file and the old version is invalidated by overwriting an indicator of some kind. You are most likely not wanting to go through all this trouble for your simple use case!

Anyway, you've already found the best way to do what you're trying to do. The only thing you're missing is a FileUtils.mv('file.new', 'file.old') at the very end to replace the old file with the new. Please let me know in the comments if I can help explain this any further.

(Of course, you can read the entire file into memory, make your changes, and overwrite the old file with the updated contents, but I don't believe that's what you're asking here.)

mwp
  • 8,217
  • 20
  • 26
-1

Here's something that hopefully solves your purpose:

# 'source' param is a string, the entire source text
# 'lines' param is an array, a list of line numbers to insert after
# 'new' param is a string, the text to add
def insert(source, lines, new)
        results = []
        source.split("\n").each_with_index do |line, idx|
                if lines.include?(idx)
                        results << (line + new)
                else
                        results << line
                end
        end
        results.join("\n")
end

File.open("foo", "w") do |f|
        10.times do |i|
                f.write("#{i}\n")
        end
end

puts "initial text: \n\n"
txt = File.read("foo")
puts txt

puts "\n\n after inserting at lines 1,3, and 5: \n\n"
result = insert(txt, [1,3,5], "\nfoo")
puts result

Running this shows:

initial text: 

0
1
2
3
4
5
6
7
8
9


 after inserting at lines 1,3, and 5: 

0
1
foo
2
3
foo
4
5
foo
6
7
8
max pleaner
  • 26,189
  • 9
  • 66
  • 118
-1

If its a relatively simple operation you can do it with a ruby one-liner, like this

ruby -i -lpe '$_.reverse!' thefile.txt

(found e.g. at https://gist.github.com/KL-7/1590797).

Felix
  • 4,510
  • 2
  • 31
  • 46