Is it bad practice to not close the file in this snippet? - ruby

Just some food for thought about how necessary it is to close the files that I opened explicitly in the code. I came from a background of programming in C and C++, and starting to navigate my way through Ruby. Thanks in advance for your feedback.
from_file, to_file = ARGV
script = $0
puts "Copying from #{from_file} to #{to_file}"
File.open(to_file, 'w').write(File.open(from_file).read())
puts "Alright, all done."

Not closing files is always bad practice unless you are using something like the with statement in python.
While a scripting language will usually close open files on exit, it's cleaner to do it as soon as you are done with the file - especially when writing to it.
Apparently Ruby has something similar to python's with:
File.open(from_file, 'r') do |f_in|
File.open(to_file, 'w') do |f_out|
f_out.write(f_in.read)
end
end
Relevant docs: http://ruby-doc.org/core-1.9.3/File.html#method-c-open

Here's a shorter version:
File.write to_file, File.read(from_file)

This code (Matheus Moreira) closes files automatically:
File.write to_file, File.read(from_file)
There are no ways to close files in this code:
File.open(to_file, 'w').write(File.open(from_file).read())
I guess automatically closing too.

It's a good answer but it's more 'ruby' to put the output file on the outer block and use << :
File.open(to_file, 'w') do |f_out|
f_out << File.open(from_file){|f| f.read}
end
note how you do not need the 'r' when reading.

Related

Understanding IO methods and objects in Ruby

I am learning Ruby by following learnrubythehardway and it seems very easy and straightforward until you are asked to explain what certain things do. I have commented in my code what I believe is happening in the program. Wanted to see if I am on target, need to rethink things or have no clue and should stop trying to learn how to code.
# Sets variables to arguments sent through terminal
from_file, to_file = ARGV
# Saves from_file object into in_file
in_file = open(from_file)
puts in_file
# Saves from_file data into indata
indata = in_file.read
puts indata
# Save to_file object into out_file with writing privileages
out_file = open(to_file, 'w')
puts out_file
# Writes(copies) indata into out_file
out_file.write(indata)
# Closes files so they can not be accessed anymore
out_file.close
in_file.close
This is what the output in the terminal looks like:
#<File:0x0000000201b038>
This is text from the ex17_from.txt file.
Did it copy to the ex17_to.txt file?
#<File:0x0000000201ae58>
We are also given the task to try to reduce the amount of code needed and are told that we can do this entire action in one line of code. I figured I can just erase out all of the comments and puts statements, while everything else is put into one line of code. However, that will be one long line and I don't think that is what the author is asking for. Any ideas on how to shorten this code will be helpful.
I have commented in my code what I believe is happening in the program. Wanted to see if I am on target, need to rethink things or have no clue
You need to learn to see beyond the written words. For example, this comment is pretty useless:
# Saves from_file object into in_file
in_file = open(from_file)
Not only useless, but actually incorrect. What is this from_file object? What kind of object will in_file be? Why don't you mention open in any way?
What actually happens here is that a File/IO object is created by calling open. And from_file, in this case, is a path to file. Not much of an "object", is it? But in_file is a full File object, which you can use to read the file's contents.
The same goes for the rest of your comments. You simply reword the line of code with human words, without describing the intent behind the code.
You can use FileUtils#cp, and do the following:
require "fileutils"
FileUtils.cp *ARGV
* splats the ARGV array into two parameters needed by cp method
Alternatively, below is concise version of your code:
# Sets variables to arguments sent through terminal
from_file, to_file = ARGV
# Saves from_file object into to_file
open(to_file, 'w') do |out|
open(from_file) do |f|
f.each do |line|
out << line
end
end
end

Reading and writing to and from files - can you do it the same way? (Ruby)

I'm in the process of learning Ruby and reading through Chris Pine's book. I'm learning how to read (and write) files, and came upon this example:
require 'yaml'
test_array = ['Give Quiche A Chance',
'Mutants Out!',
'Chameleonic Life-Forms, No Thanks']
test_string = test_array.to_yaml
filename = 'whatever.txt'
File.open filename, 'w' do |f|
f.write test_string
end
read_string = File.read filename
read_array = YAML::load read_string
puts(read_string == test_string)
puts(read_array == test_array )
The point of the example was to teach me about YAML, but my question is, if you can read a file with:
File.read filename
Can you write to a file in a similar way?:
File.write filename test_string
Sorry if it's a dumb question. I was just curious why it's written the way it was and if it had to be written that way.
Can you write to a file in a similar way?
Actually, yes. And it's pretty much exactly as you guessed:
File.write 'whatever.txt', test_array.to_yaml
I think it is amazing how intuitive Ruby can be.
See IO.write for more details. Note that IO.binwrite is also available, along with IO.read and IO.binread.
The Ruby File class will give you new and open but it inherits from the IO class so you get the read and write methods too.
I think the right way to write into a file is the following:
File.open(yourfile, 'w') { |file| file.write("your text") }
To brake this line down:
We first open the file setting the access mode ('w' to overwrite, 'a' to append, etc.)
We then actually write into the file
You can read or write to a file by specifying the mode you access it through. The Ruby File class is a subclass of IO.
The File class open or new methods take a path and a mode as arguments:
File.open('path', 'mode') alternatively: File.new('path','mode')
Example: to write to an existing file
somefile = File.open('./dir/subdirectory/file.txt', 'w')
##some code to write to file, eg:
array_of_links.each {|link| somefile.puts link }
somefile.close
See the source documentation as suggested above for more details, or similar question here: How to write to file in Ruby?

How to create a file in Ruby

I'm trying to create a new file and things don't seem to be working as I expect them too. Here's what I've tried:
File.new "out.txt"
File.open "out.txt"
File.new "out.txt","w"
File.open "out.txt","w"
According to everything I've read online all of those should work but every single one of them gives me this:
ERRNO::ENOENT: No such file or directory - out.txt
This happens from IRB as well as a Ruby script. What am I missing?
Use:
File.open("out.txt", [your-option-string]) {|f| f.write("write your stuff here") }
where your options are:
r - Read only. The file must exist.
w - Create an empty file for writing.
a - Append to a file.The file is created if it does not exist.
r+ - Open a file for update both reading and writing. The file must exist.
w+ - Create an empty file for both reading and writing.
a+ - Open a file for reading and appending. The file is created if it does not exist.
In your case, 'w' is preferable.
OR you could have:
out_file = File.new("out.txt", "w")
#...
out_file.puts("write your stuff here")
#...
out_file.close
Try
File.open("out.txt", "w") do |f|
f.write(data_you_want_to_write)
end
without using the
File.new "out.txt"
Try using "w+" as the write mode instead of just "w":
File.open("out.txt", "w+") { |file| file.write("boo!") }
OK, now I feel stupid. The first two definitely do not work but the second two do. Not sure how I convinced my self that I had tried them. Sorry for wasting everyone's time.
In case this helps anyone else, this can occur when you are trying to make a new file in a directory that does not exist.
If the objective is just to create a file, the most direct way I see is:
FileUtils.touch "foobar.txt"
The directory doesn't exist. Make sure it exists as open won't create those dirs for you.
I ran into this myself a while back.
File.new and File.open default to read mode ('r') as a safety mechanism, to avoid possibly overwriting a file. We have to explicitly tell Ruby to use write mode ('w' is the most common way) if we're going to output to the file.
If the text to be output is a string, rather than write:
File.open('foo.txt', 'w') { |fo| fo.puts "bar" }
or worse:
fo = File.open('foo.txt', 'w')
fo.puts "bar"
fo.close
Use the more succinct write:
File.write('foo.txt', 'bar')
write has modes allowed so we can use 'w', 'a', 'r+' if necessary.
open with a block is useful if you have to compute the output in an iterative loop and want to leave the file open as you do so. write is useful if you are going to output the content in one blast then close the file.
See the documentation for more information.
data = 'data you want inside the file'.
You can use File.write('name of file here', data)
You can also use constants instead of strings to specify the mode you want. The benefit is if you make a typo in a constant name, your program will raise an runtime exception.
The constants are File::RDONLY or File::WRONLY or File::CREAT. You can also combine them if you like.
Full description of file open modes on ruby-doc.org

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.

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