Read file after writing in same script (Ruby the Hard Way ex16) - ruby

Here is my code:
filename = ARGV.first
puts "We're gong to erase #{filename}"
puts "If you don't want that, hit CTRL-C (^C)."
puts "If you do want that, hit RETURN."
$stdin.gets
puts "Opening the file..."
target = open(filename, 'w')
puts "Truncating the file. Goodbye!"
target.truncate(0)
puts "Now I'm going to ask you for three lines."
print "line 1: "
line1 = $stdin.gets.chomp
print "line 2: "
line2 = $stdin.gets.chomp
print "line 3: "
line3 = $stdin.gets.chomp
puts "I'm going to write these to the file."
target.write(line1)
target.write("\n")
target.write(line2)
target.write("\n")
target.write(line3)
target.write("\n")
print target.read
puts "And finally, we close it."
target.close
I'm trying to get it to write and then read. It works if I do target.close and then target = open(filename) again at the bottom of the script. Is there another way?
I saw this post about python explaining you need to close a file after writing to it. Does this same thing apply to Ruby? Do I need to use flush?
Also should I be using parentheses after read and close? The example does not.

There's two ways to approach this. You can, as you've done, open the file for writing, write to it, close the file, and reopen it for reading. This is fine. Closing the file will flush it to disk and reopening it will put you back at the beginning of the file.
Alternatively you can open a file for both reading and writing and manually move around within the file, like a cursor in an editor. The options to do this are defined in IO.new.
The problem with your code is this.
target.write("\n")
print target.read
At this point you've been writing to the file. The target file pointer is pointing at the end of the file, like a cursor in an editor. When you target.read it's going to read the end of the file, so you get nothing. You'd have to go back to the beginning of the file first with rewind.
target.write("\n")
target.rewind
print target.read
You'll also have to open the file for reading and writing. w+ can do that, and truncate the file for you.
puts "Opening the file..."
target = File.open(filename, 'w+')
This is an advanced technique most often useful for when you want to hold a lock on a file during the whole reading and writing process to make sure nobody else can work on the file while you are. Generally you do this when you're reading and then writing. For example, if you had a counter in a file you want to read and then increment and make sure nobody can write between.
def read_and_update_counter
value = 0
# Open for reading and writing, create the file if it doesn't exist
File.open("counter", File::RDWR|File::CREAT, 0644) {|f|
# Get an exclusive lock to prevent anyone else from using the
# file while we're updating it (as long as they also try to flock)
f.flock(File::LOCK_EX)
# read the value
value = f.read.to_i
# go back to the beginning of the file
f.rewind
# Increment and write the new value
f.write("#{value + 1}\n")
# Flush the changes to the file out of the in-memory IO cache
# and to disk.
f.flush
# Get rid of any other garbage that might be at the end of the file
f.truncate(f.pos)
}
# File.open automatically closes the file for us
return value
end
3.times { puts read_and_update_counter }

Related

Ruby read file_name with gets

I have a beginners coding task, first step is my program "should prompt the user to enter a filename of a file that contains the following information:" There's already pre-made code to work on, a "music_player.rb" (where I have to write the code) and "albums.text" (which is the file I want to read from)
I know a_file = File.new("mydata.txt", "r") is to read from file. I'm trying to do:
file_name = gets()
a_file = File.new("#{file_name}" , "r") # (line 13)
I keep getting error
music_player_with_menu.rb:13:in `initialize': No such file or directory # rb_sysopen - albums.txt (Errno::ENOENT)
when I enter albums.txt. If I just remove gets and have File.new("albums.txt" , "r") it works. I'm not sure what I'm doing wrong.
Trying to read from mydata.txt\n is going to raise an exception unless the filename actually ends in \n, which is rarely the case. This is because you're using #gets, which includes the newline character(s) from the user pressing RETURN or ENTER.
When you read from STDIN, you will get a line-ending (e.g. \n on *nix, and \r\n on Windows). So, when you call #gets, you almost always need to call String#chomp on the result.
file_name = gets
#=> "foo\n"
file_name = gets.chomp
#=> "foo"

How do I save the text of puts in Ruby to a txt file?

I wrote a madlib in Ruby, and want to save the resulting madlib to a txt file. This is what I wrote, but the resulting txt file is empty:
file=File.open("madlib_output.txt","a")
file.puts
file.close
There are ways to save the output of a script to a file without having to modify every puts in the script.
The easiest is to route the output at the command-line using redirection. Running a script with > some_file at the of the command will route all STDOUT to the file. Similarly, using > some_file 2>&1 will route both STDOUT and STDERR to the same file. This won't capture anything typed in at a gets as the code waits for input though, because that won't count as program output.
If you don't mind changing your code a little, you can temporarily change the interpreter's idea of what STDOUT is by reassigning it to a file:
old_stdout = $stdout
File.open('output.txt', 'w') do |fo|
$stdout = fo
# ----
# your code goes here
puts "hello world"
# ----
end
$stdout = old_stdout
Run that, then look at the file "output.txt" and you'll see "hello world", even though we didn't print to the file-handle fo directly, like we would normally do using fo.puts.
There are a variety of ways of doing the same thing but they amount to pointing STDOUT or STDERR somewhere else, writing to them, then resetting them.
Typically, if we intend from the start to output to a file, then we should use a File.open block:
File.open('output.txt', 'w') do |fo|
fo.puts "hello world"
end
The benefit of that is the file will be closed automatically when the block exits.
Is this what you looking for ? You can open madlib_output.txt file in append mode and whatever you want to write will be inside the block eg: "hi"
File.open("madlib_output.txt","a") do |f|
f.puts "hi"
end

Only "puts" one line to a text document

The code I'm working with at the moment is supposed to spit back every line of information in one text document that contains the word "DEBUG" and then paste it in a new text document titled "debug.txt".
For whatever reason it is only printing the final line into the new text document and I have no clue why. However, another function is to spit back every line to the command terminal, and it does that successfully, it just won't write them all to the file.
log_file = File.open("main_file.rb")
File.readlines(log_file).each do |line|
if line.include? "DEBUG"
puts line
File.open("debug.txt", "w") do |out|
out.puts line
end
end
end
You're overwriting the file every time you find a DEBUG line in main_file. You have your blocks backwards. The File.open('debug.txt') should be outside of the File.readlines.
Like this:
log_file = File.open("main_file.rb")
File.open("debug.txt", "w") do |out|
File.readlines(log_file).each do |line|
if line.include? "DEBUG"
puts line
out.puts line
end
end
end
You could also open the file in append mode by passing 'a' instead of 'w' in your File.open('debug.txt') call but this would be needlessly reopening the file every time you find a line that contains DEBUG in it. It would be better to open the debug file once for writing and using the file handle from there on as I show above.
Write it like this:
File.open("debug.txt", "w") do |out|
File.foreach("main_file.rb") do |line|
if line['DEBUG']
puts line
out.puts line
end
end
end
You need to:
Open the output file.
Iterate over the lines in the input file.
For each line, check to see if it contains the string you want.
If so, write it.
Loop until the input file is completely read.
Close the output file.
Notice I don't open the file for output as a single step. Ruby's use of blocks are really handy: By passing a block to open, Ruby will close the file when the block exits, avoiding the problem of open files hanging around to clutter memory or consume available file handles.
Use foreach to read the file. It reads a single line at a time and is extremely fast. It's also scalable, which means it'll work for a one-line file or a 10-million line file equally well. Using readlines, as in your code, results in Ruby loading the entire file into memory, splitting it into separate lines, then iterating over them. That can cause real problems if your input file exceeds available RAM.
line['DEBUG'] is shorthand for "do a substring match for this text". See String#[] for more information.

Why won't file contents copy?

I am trying to duplicate the contents of one file to another. Whilst the file is copying, the contents of the files are not. I am not sure where I am going wrong here.
puts "What file do you want to copy?"
print ">"
to_duplicate = STDIN.gets.chomp
puts "what do you want to call the new file?"
print ">"
output_file = STDIN.gets.chomp
puts "copying #{to_duplicate} to #{output_file}."
input = File.open(to_duplicate, 'r') ; prepare_file = input.read
output = File.open(output_file, 'w')
output.write(prepare_file)
puts "finished duplicating files."
Use FileUtils#cp
The easiest way to copy files is with FileUtils#cp from the Ruby Standard Library. For example:
require 'fileutils'
FileUtils.cp '/tmp/foo', '/tmp/bar'
You can certainly use variables to store filenames collected from standard input if you like. Just don't reinvent the wheel if you don't have to, and leverage the standard libraries whenever you can.

Weird Ruby IO with Tempfile

This is driving me crazy. Consider the following:
require 'open-uri'
#set up tempfile
extname = File.extname file_url
basename = File.basename(file_url, extname)
file = Tempfile.new([basename,extname])
#read form URI into tempfile
uri = URI.parse(file_url)
num_bytes_writen = file.write(uri.read)
puts "Wrote #{num_bytes_writen} bytes"
# Reading from my tempfile
puts "Opening: #{file.path} >>"
puts "#### BEGINING OF FILE ####"
puts File.open(file.path,'rb').read
puts "#### END OF FILE ####"
It looks like bytes get written, but when I try to open the file -- its empty. Whats up ?!
And to make it more weird -- everyting works in the Rails Console, but not when executed by a worker triggered by Resque.
Any ideas? Thanks guys
This is a problem of buffering. You need to flush the IO buffer to disk before trying to read it. Either file.close (if you've finished with it) or file.flush before doing the File.open for the read.
Update
I hadn't thought about this, but you don't need to reopen the temp file just to read it. It's already open for writing and reading, all you need to do is seek to the start of the file before reading. This way you don't have to do the flush (because you're actually reading from the buffer)...
# starting partway into your code...
num_bytes_written = file.write(uri.read)
puts "Wrote #{num_bytes_written} bytes"
puts "No need to open #{file.path} >>"
puts "### BEGINNING OF FILE ###"
file.rewind # set the cursor to the start of the buffer
puts file.read # cursor is back at the end of the buffer now
puts "### END OF FILE ###"
Another Update
After a comment from #carp I have adjusted the code above to use rewind instead of seek 0 because it also resets lineno to 0 (and not having that done, if you were using lineno would be very confusing). Also actually it's a more expressive method name.
Always close your files. Try using this style in your code, to avoid such mistakes:
File.open(myfile,"w") {|f| f.puts content }
This way, it will automatically call close when the block ends.

Resources