Weird Ruby IO with Tempfile - ruby

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.

Related

Ruby: Reading from a file written to by the system process

I'm trying to open a tmpfile in the system $EDITOR, write to it, and then read in the output. I can get it to work, but I am wondering why calling file.read returns an empty string (when the file does have content)
Basically I'd like to know the correct way of reading the file once it has been written to.
require 'tempfile'
file = Tempfile.new("note")
system("$EDITOR #{file.path}")
file.rewind
puts file.read # this puts out an empty string "" .. why?
puts IO.read(file.path) # this puts out the contents of the file
Yes, I will be running this in an ensure block to nuke the file once used ;)
I was running this on ruby 2.2.2 and using vim.
Make sure you are calling open on the file object before attempting to read it in:
require 'tempfile'
file = Tempfile.new("note")
system("$EDITOR #{file.path}")
file.open
puts file.read
file.close
file.unlink
This will also let you avoid calling rewind on the file, since your process hasn't written any bytes to it at the time you open it.
I believe IO.read will always open the file for you, which is why it worked in that case. Whereas calling .read on an IO-like object does not always open the file for you.

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

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 }

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

When does ruby release its File.write assignments?

I am writing to a file instance. While the program is still running, the file is always empty. When I check the file after the script has executed, the file has content.
class A
def initialize
#file_ref=File.new("/user/shared/ruby/ruby-example/test.html","w+")
end
def fill
#file_ref.write("whatever\nwhatever\nwhatever\n")
end
end
The Main script:
require_relative 'A'
a=A.new
a.fill
puts File.size("/user/shared/ruby/ruby-example/test.html")
After the A instance has done its job, the puts statement will print "0" as if the file is empty. Indeed it is during program execution, but if I start irb:
puts File.size("/user/shared/ruby/ruby-example/test.html")
# => 27
$ cat test.html
whatever
whatever
whatever
Is my code wrong?
Is it normal that streams are flushed only after the execution of a process?
Ruby flushes IO buffers when you call IO#close or IO#flush. Since you are not calling neither close nor flush the buffers are flushed when the program terminates and the opened file descriptors are released.
Given your simple example a possible solution is:
class A
def initialize
#file_ref_name = '/user/shared/ruby/ruby-example/test.html'
end
def fill
File.open(#file_ref_name, 'w+') do |file|
file.write("whatever\nwhatever\nwhatever\n")
end
end
end
Passing a block to IO#open makes the opened file (the file variable in this example) to be closed (and therefore flushed) once the execution of the block terminates.
Please note that Ruby (to my knowledge since version 1.9) features a one liner shortcut for simple file writes as well, flush included:
File.write('/path/to/file.txt', 'content')

how to solve this I/O error in ruby

require 'fileutils.rb'
k=FileUtils.mkdir_p "d:/pptomasdsdr1xks_6b27"
filename = "#{k}""/kddabab"
extension = ".txt"
co=1
$stdout=File.open("#{filename}#{co}#{extension}" ,'w')
puts "sachin"
$stdout.close
puts "amit"
im getting error like
stdout.rb:14:in write': closed stream (IOError)
from stdout.rb:14:inputs'
i don't want amit to be printd in my file so that i close the file but i got this error
help me plzzz
Don't use $stdout to write to a file.
Don't change $stdout, and certainly not without storing away the old value somewhere so you can restore it after you're done with it.
Instead, call puts on the file object:
File.open("#{filename}#{co}#{extension}" ,'w') do |file|
file.puts "sachin" # This goes to the file
end
puts "amit" # This goes to standard output

Resources