auralbee
auralbee

Reputation: 8841

How to insert a string into a textfile

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

Answers (6)

Mike Slinn
Mike Slinn

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.

Command-line Usage

# 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

Ruby Program Usage

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

Program Code

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

Command-line Installation

$ gem install insert_after

Installation For Use In a Ruby Program

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

For More Information

See https://github.com/mslinn/insert_after

Upvotes: 0

Sv1
Sv1

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

kejadlen
kejadlen

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

ffoeg
ffoeg

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

Jonas Elfstr&#246;m
Jonas Elfstr&#246;m

Reputation: 31468

If it's a small file I think the easiest way would be to:

  • Read the complete file.
  • Insert the line.
  • Write the new file.

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

liwp
liwp

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

Related Questions