Reputation: 8841
I have a configuration file to which I want to add a string, that looks e.g. like that:
line1
line2
line3
line4
The new string should not be appended but written somewhere into the middle of the file. Therefore I am looking for a specific position (or string) in the file and when it has been found, I insert my new string:
file = File.open(path,"r+")
while (!file.eof?)
line = file.readline
if (line.downcase.starts_with?("line1"))
file.write("Some nice little sentence")
end
end
The problem is that Ruby overwrites the line in that position with the new text, so the result is the following:
line1
Some nice little sentence
line3
line4
What I want is a "real" insertion:
line1
Some nice little sentence
line2
line3
line4
How can this be achieved?
Upvotes: 15
Views: 15229
Reputation: 8417
Inspired by the answer provided by @Jonas Elfström, I wrote a Ruby gem called insert_after. You can either use it from the command line or a Ruby program.
# Inserts 'Inserted 1' after the first line containing 'line' into demo/my_file.txt:
$ insert_after line 'Inserted 1' demo/my_file.txt
# Inserts 'Inserted 1' after every line containing 'line' into demo/my_file.txt:
$ insert_after -a line 'Inserted 1' demo/my_file.txt
# Inserts an empty line after the first line containing 'line 1' into demo/my_file.txt:
$ insert_after 'line 1' '' demo/my_file.txt
# Inserts an empty line after every line containing 'line 1' into demo/my_file.txt:
$ insert_after -a 'line 1' '' demo/my_file.txt
# Inserts 'Inserted 2' after the first line starting with 'line 2' into demo/my_file.txt:
$ insert_after '^line 2' 'Inserted 2' demo/my_file.txt
# Inserts 'Inserted 2' after every line starting with 'line 2' into demo/my_file.txt:
$ insert_after -a '^line 2' 'Inserted 2' demo/my_file.txt
# Inserts 'Inserted 3' after the first line containing an 'e' followed by a '2' into demo/my_file.txt:
$ insert_after 'e.*2' 'Inserted 3' demo/my_file.txt
# Inserts 'Inserted 3' after every line containing an 'e' followed by a '2' into demo/my_file.txt:
$ insert_after -a 'e.*2' 'Inserted 3' demo/my_file.txt
require 'insert_after'
InsertAfer.insert_after 'line 2', 'New line', 'my_file.txt'
InsertAfter.insert_after 'line', 'Another line from Ruby', 'my_file.txt', all: true
This is the principal method:
def self.insert_after(regex, new_line, filename, all: false)
InsertAfter.help "#{filename} does not exist" unless File.exist? filename
original_file = File.new(filename)
temporary_file = Tempfile.new(filename)
inserted = false
begin
original_file.each do |line|
temporary_file << line
if line.downcase.match?(/#{regex}/)
temporary_file << "#{new_line}\n" unless inserted
inserted = true unless all
end
end
original_file.close
temporary_file.close
FileUtils.mv(temporary_file.path, filename)
ensure
original_file.close unless original_file.closed?
temporary_file.close unless temporary_file.closed?
temporary_file.unlink if File.exist? temporary_file.path
end
end
$ gem install insert_after
Either add this line to your application’s Gemfile
:
gem 'insert_after'
... or add the following to your application’s .gemspec
:
spec.add_dependency 'insert_after'
And then execute:
$ bundle
See https://github.com/mslinn/insert_after
Upvotes: 0
Reputation: 2424
The tty-file gem has an inject_into_file
method for this purpose.
Install the gem:
gem install tty-file
Then:
require 'tty-file'
TTY::File.inject_into_file("filename.rb", "Some nice little sentence", after: "line1\n")
Upvotes: 0
Reputation: 1599
A couple other options:
file = File.read(path).sub(/line2\n/, 'Some nice little sentence\n\1')
File.write(path, file)
file = File.readlines(path)
index = file.index("line2")
file.insert(index, "Some nice little sentence")
File.write(path, file)
Upvotes: 7
Reputation: 2336
def replace(filepath, regexp, *args, &block)
content = File.read(filepath).gsub(regexp, *args, &block)
File.open(filepath, 'wb') { |file| file.write(content) }
end
replace(my_file, /^line2/mi) { |match| "Some nice little sentence"}
line1
Some nice little sentence
line2
line3
line4
and if you want to append to the existing...
replace(my_file, /^line2/mi) do |match|
"#{match} Some nice little sentence"
end
line1
line2 Some nice little sentence
line2
line3
line4
Upvotes: 8
Reputation: 31468
If it's a small file I think the easiest way would be to:
That is the way Rails does it behind the scenes.
Or you can copy it line by line and then overwrite the original with mv
like this:
require 'fileutils'
tempfile=File.open("file.tmp", 'w')
f=File.new("file.txt")
f.each do |line|
tempfile<<line
if line.downcase=~/^line2/
tempfile << "Some nice little sentence\n"
end
end
f.close
tempfile.close
FileUtils.mv("file.tmp", "file.txt")
Upvotes: 12
Reputation: 6926
The easiest way is to read the whole file in memory, then write the first part back to the file, write your inserted line to the file, and write the remaining part back to the file. This should be relatively simple to do when you read the file as an array of lines, but you might run into problems if your file is very large since you have to read the entire file into memory with this approach.
Alternatively you could find the spot that you want to insert the line, read the lines after that point into memory, seek back to that point in the file, write your new line to the file, and finally write the remaining lines to the file. Again you'll run into problems if the remainder of the file is very large since you have to read it all into memory.
A third approach is to write the first part into a new file, write the inserted line into a new file, write the remainder of the original file into the new file, and finally replace the old file with the new file on the file system. This approach allows you to deal with one line at a time so you can handle files that do not fit into memory.
The reason why file writing works like this is because the file is like a fixed size array of bytes: when you write a byte to the file you will overwrite an existing byte (I'm ignoring the case where you append to a file here). So the only way to insert anything to a file is to first move the old content to a new location by reading it from the old location and writing it to the new location. After that you can 'insert' data into the now vacant area.
Upvotes: 1