How to insert a string into a textfile - ruby-on-rails

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?

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")

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

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)

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.

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")

Related

lua table string concat not correct

I have a simple function to read lines from .txt file:
function loadData(file_name, root_path)
-- here, file_name is './list.txt', path is '../data/my/'
for line in io.lines(file_name) do
local data = {}
base_path = root_path .. line
-- so, base_path is something like ../data/my/001
data.file = base_path .. '_color.png'
print(data)
end
end
I expect the data should be {file: "../data/my/001_color.png"}, but I got {_color.png" ../data/my/001}
Can anyone help me? Thanks!
Check your ./list.txt file content for EOL (end of line) as it may be produced on windows (EOL=CR LF) an interpreted on linux (EOL=LF). io.lines takes CR character into line string on linux!
Your programm makes everything correct, but your data is not.
Let assume your first line in ./list.txt is ../data/my/001\r\n
line variable is ../data/my/001\r (print(#line) gives 15 instead of 14 ).
Carriage return (CR) in print moves cursor to start line position witout changing line.
Your print output in this case is something simmilar to {file: "../data/my/001\r_color.png"} (as it depends on print implementation) and you get output:
{file: "../data/my/001
_color.png"} <-- on the same line
Let's combine it:
_color.png"}ata/my/001
To correct this:
provide file without CR (works correctly on all systems)
add in loop on first row: line = line:gsub('[\r\n]','') to remove CR LF

writing into a .txt file, without renaming the first line

How I can write in a file, without replacing the first line?
I tried
file = io.open('test123.txt', 'r+')
file:write('test')
file:close()
but instead of creating a new line, it rewrites the first line
Try this:
file = io.open('test123.txt', 'r+')
file:read('L')
file:flush()
file:write('test')
file:close()
That keeps the first line as-is, and then starts overwriting after that. If it gets to the end, then it will start to grow the file.

Read next line during file IO in Ruby

I am trying to import a file using ruby and parse it. Is there a way to read the next line once inside the file import? Basically I want to see if a specific line is within x lines of another important line. Like does "x phrase" Come within 10 lines of "y phrase". I don't see a way to do this -- I know its simple with Java.
Thanks!
You can also try:
web_contents = "c:\\path\\to\\your\\file.txt"
File.open(web_contents).each_with_index do |line, i|
line.chomp!
puts "line #{line}, i #{i}" # Do whatever you want to here
end
The .each_with_index method gives you an index, i, which you can use to keep track of where on what line in your file you are. Simple maths can then yield the offset as required.
To read lines of a file
lines_array = IO.readlines('testfile')
lines_array.each { |l| #Do your stuff with your line }
VoilĂ 
Ruby Docs on IO

What does file.new("temp.out", "w") line represent?

I'm learning the Ruby language and I'm having a lot of fun.
I am currently working on the Temperature converter with file output exercise.
The solution is provided below
print "Hello. Please enter a Celsius value: "
celsius = gets.to_i
fahrenheit = (celsius * 9 / 5) + 32
puts "saving result to output file 'temp.out'"
fh = File.new("temp.out", "w")
fh.puts fahrenheit
fh.close
The highlighted part confuses me.
We are calling File.new to create a file named "temp.out" and "w" write whatever inputs until we fh.close. Am I correct?
Thank you!
By default, puts() will send its output to what's called stdout, which is connected to your screen. File.new() creates a new file which is assigned to the variable fh. Because you created the file in write mode, you can use fh to write stuff to the file. fh.puts() sends output to the file assigned to the variable fh. In other words, a bare puts() statement sends output to your screen, but when you precede puts() with a file, the output goes to the file.
You can also write those statements like this:
File.open("temp.out", "w") do |f|
f.puts fahrenheit
end
The neat thing about writing it like that is: when the end statement executes, Ruby will automatically close the file for you.

reading large csv files in a rails app takes up a lot of memory - Strategy to reduce memory consumption?

I have a rails app which allows users to upload csv files and schedule the reading of multiple csv files with help of delayed_job gem. The problem is the app reads each file in its entirity into memory and then writes to the database. If its just 1 file being read its fine, but when multiple files are read the RAM on the server gets full and causes the app to hang.
I am trying to find a solution for this problem.
One solution I researched is to break the csv file into smaller parts and save them on the server, and read the smaller files. see this link
example: split -b 40k myfile segment
Not my preferred solution. Are there any other approaches to solve this where I dont have to break the file. Solutions must be ruby code.
Thanks,
You can make use of CSV.foreach to read just chunks of your CSV file:
path = Rails.root.join('data/uploads/.../upload.csv') # or, whatever
CSV.foreach(path) do |row|
# process row[i] here
end
If it's run in a background job, you could additionally call GC.start every n rows.
How it works
CSV.foreach operates on an IO stream, as you can see here:
def IO.foreach(path, options = Hash.new, &block)
# ...
open(path, options) do |csv|
csv.each(&block)
end
end
The csv.each part is a call to IO#each, which reads the file line by line (rb_io_getline_1 invokation) and leaves the line read to be garbage collected:
static VALUE
rb_io_each_line(int argc, VALUE *argv, VALUE io)
{
// ...
while (!NIL_P(str = rb_io_getline_1(rs, limit, io))) {
rb_yield(str);
}
// ...
}

Resources