Retrieve a file in Ruby - ruby

So what I am trying to do is pass a file name into a method and and check if the file is closed. What I am struggling to do is getting a file object from the file name without actually opening the file.
def file_is_closed(file_name)
file = # The method I am looking for
file.closed?
end
I have to fill in the commented part. I tried using the load_file method from the YAML module but I think that gives the content of the file instead of the actual file.
I couldn't find a method in the File module to call. Is there a method maybe that I don't know?

File#closed? returns whether that particular File object is closed, so there is no method that is going to make your current attempted solution work:
f1 = File.new("test.file")
f2 = File.new("test.file")
f1.close
f1.closed? # => true # Even though f2 still has the same file open
It would be best to retain the File object that you're using in order to ask it if it is closed, if possible.
If you really want to know if your current Ruby process has any File objects open for a particular path, something like this feels hack-ish but should mostly work:
def file_is_closed?(file_name)
ObjectSpace.each_object(File) do |f|
if File.absolute_path(f) == File.absolute_path(file_name) && !f.closed?
return false
end
end
true
end
I don't stand by that handling corner cases well, but it seems to work for me in general:
f1 = File.new("test.file")
f2 = File.new("test.file")
file_is_closed?("test.file") # => false
f1.close
file_is_closed?("test.file") # => false
f2.close
file_is_closed?("test.file") # => true
If you want to know if any process has the file open, I think you'll need to resort to something external like lsof.

For those cases where you no longer have access to the original file objects in Ruby (after fork + exec, for instance), a list of open file descriptors is available in /proc/pid/fd. Each file there is named for the file descriptor number, and is a symlink to the opened file, pipe, or socket:
# Returns hash in form fd => filename
def open_file_descriptors
Hash[
Dir.glob( File.join( '/proc', Process.pid.to_s, 'fd', '*' ) ).
map { |fn| [File.basename(fn).to_i, File.readlink(fn)] rescue [nil, nil] }.
delete_if { |fd, fn| fd.nil? or fd < 3 }
]
end
# Return IO object for the named file, or nil if it's not open
def io_for_path(path)
fd, fn = open_file_descriptors.find {|k,v| path === v}
fd.nil? ? nil : IO.for_fd(fd)
end
# close an open file
file = io_for_path('/my/open/file')
file.close unless file.nil?
The open_file_descriptors method parses the fd directory and returns a hash like {3 => '/my/open/file'}. It is then a simple matter to get the file descriptor number for the desired file, and have Ruby produce an IO object for it with for_fd.
This assumes you are on Linux, of course.

Related

Trying to open a file in Ruby - Getting TypeError: can't convert String into Integer

Not sure whats going on here, or what could be the integer in this case. Here's the code:
def build_array_from_file(filename)
contents = []
File.read(File.expand_path('lib/project_euler/' + filename), 'r') do |file|
while line = file.get
contents << line
end
end
contents
end
filename is a string and I've checked to make sure the path comes up valid.
Any thoughts? Thanks.
File.read has no second argument for mode nor block, that's File.open:
contents_string = File.read(File.expand_path('lib/project_euler/' + filename))
Note that you can also write:
contents = File.open(path).lines # returns a lazy enumerator, keeps the file open
Or:
contents = File.readlines(path) # returns an array, the file is closed.
File.read doesn't need the mode r - you already request 'read' in File.read. The parameters fo File.read are - after the filename - the offset and length (that's why a integer was expected in the error message).
You may give the mode as File.read(filename, :mode => 'r') This may be usefull, if you need the mode rb or r:utf-8 (but there is also a encoding-option).

Storing images in filesystem is breaking the files. Is there a better way to write this? Why do I get broken files?

post '/upload' do
unless params[:file] && (tmpfile = params[:file][:tempfile]) && (name = params[:file][:filename])
return haml(:upload)
end
time = Time.now.to_s
time.gsub!(/\s/, '')
name = time + name
while blk = tmpfile.read(65536)
File.open(File.join(Dir.pwd,"public/uploads", name), "wb") { |f| f.write(tmpfile.read) }
end
'success'
end
Everything goes where expected the files just end up being corrupted.
This bit looks really funky:
while blk = tmpfile.read(65536)
File.open(File.join(Dir.pwd,"public/uploads", name), "wb") { |f| f.write(tmpfile.read) }
end
I'm guessing you're trying to read your tempfile a 65536-byte block at a time, and then write those blocks successively to your destination file. But you never write blk, which is the first block you read; you write the rest of the file (tempfile.read) instead. And even if this loop did write blocks like it should, it opens the file anew for each block, overwriting the old contents! Anyway, I suspect you meant something like this:
File.open(File.join(Dir.pwd,"public/uploads", name), "wb") do |f|
while(blk = tempfile.read(65536))
f.write(blk)
end
end
That said, if you've got the file as a temp file (presumably already on your local file system), maybe all you need to do is move that file? It'll go way faster if that's the case - if the source and destination are on the same disk, it's just a matter of swapping some file system pointers, rather than copying all that data.
Hope that helps!
The code opens and replaces the file during every iteration of the loop, which causes part of the problem. The code also reads the tmpfile into blk then throws that data away. Time.now.to_s contains colons, which is the path separator on Mac OS X, and could cause a problem on OS X. The user-supplied filename could contain some bad stuff like .. which may allow users to overwrite files. Try this instead:
require 'pathname'
require 'zaru'
post '/upload' do
unless tmpfile = params[:file].try(:[], :tempfile)
return haml(:upload)
end
name = Zaru.sanitize!("#{Time.now.to_i}#{params[:file][:filename]}")
Pathname.pwd.join("public/uploads", name).open("wb") do |f|
while blk = tmpfile.read(65536)
f.write(blk)
end
end
'success'
end
You should also make sure that the filename doesn't end in something nefarious, like .js or .css, which could be exploited.

How do I copy file contents to another file?

As basic as this seems, I simply can't manage to copy the contents of one file to another. Here is my code thus far:
#!/usr/bin/ruby
Dir.chdir( "/mnt/Shared/minecraft-server/plugins/Permissions" )
flist = Dir.glob( "*" )
flist.each do |mod|
mainperms = File::open( "AwesomeVille.yml" )
if mod == "AwesomeVille.yml"
puts "Shifting to next item..."
shift
else
File::open( mod, "w" ) do |newperms|
newperms << mainperms
end
end
puts "Updated #{ mod } with the contents of #{ mainperms }."
end
Why copy the contents of one file to another? Why not use either the OS to copy the file, or use Ruby's built-in FileUtils.copy_file?
ri FileUtils.copy_file
FileUtils.copy_file
(from ruby core)
------------------------------------------------------------------------------
copy_file(src, dest, preserve = false, dereference = true)
------------------------------------------------------------------------------
Copies file contents of src to dest. Both of src and
dest must be a path name.
A more flexible/powerful alternate is to use Ruby's built-in FileUtils.cp:
ri FileUtils.cp
FileUtils.cp
(from ruby core)
------------------------------------------------------------------------------
cp(src, dest, options = {})
------------------------------------------------------------------------------
Options: preserve noop verbose
Copies a file content src to dest. If dest is a
directory, copies src to dest/src.
If src is a list of files, then dest must be a directory.
FileUtils.cp 'eval.c', 'eval.c.org'
FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
This works for me
IO.copy_stream mainperms, mod
ยง copy_stream
I realize that this isn't the completely approved way, but
IO.readlines(filename).join('') # join with an empty string because readlines includes its own newlines
Will load a file into a string, which you can then output into newperms just like it was a string. There's good chance the reason this isn't working currently is that you are trying to write an IO handler to a file, and the IO handler isn't getting converted into a string in the way you want it to.
However, another fix might be
newperms << mainperms.read
Also, make sure you close mainperms before the script exits, as it might break something if you don't.
Hope this helps.

How to write to file in Ruby?

I need to read the data out of database and then save it in a text file.
How can I do that in Ruby? Is there any file management system in Ruby?
Are you looking for the following?
File.open(yourfile, 'w') { |file| file.write("your text") }
You can use the short version:
File.write('/path/to/file', 'Some glorious content')
It returns the length written; see ::write for more details and options.
To append to the file, if it already exists, use:
File.write('/path/to/file', 'Some glorious content', mode: 'a')
This is preferred approach in most cases:
File.open(yourfile, 'w') { |file| file.write("your text") }
When a block is passed to File.open, the File object will be automatically closed when the block terminates.
If you don't pass a block to File.open, you have to make sure that file is correctly closed and the content was written to file.
begin
file = File.open("/tmp/some_file", "w")
file.write("your text")
rescue IOError => e
#some error occur, dir not writable etc.
ensure
file.close unless file.nil?
end
You can find it in documentation:
static VALUE rb_io_s_open(int argc, VALUE *argv, VALUE klass)
{
VALUE io = rb_class_new_instance(argc, argv, klass);
if (rb_block_given_p()) {
return rb_ensure(rb_yield, io, io_close, io);
}
return io;
}
The Ruby File class will give you the ins and outs of ::new and ::open but its parent, the IO class, gets into the depth of #read and #write.
Zambri's answer found here is the best.
File.open("out.txt", '<OPTION>') {|f| f.write("write your stuff here") }
where your options for <OPTION> 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.
For those of us that learn by example...
Write text to a file like this:
IO.write('/tmp/msg.txt', 'hi')
BONUS INFO ...
Read it back like this
IO.read('/tmp/msg.txt')
Frequently, I want to read a file into my clipboard ***
Clipboard.copy IO.read('/tmp/msg.txt')
And other times, I want to write what's in my clipboard to a file ***
IO.write('/tmp/msg.txt', Clipboard.paste)
*** Assumes you have the clipboard gem installed
See: https://rubygems.org/gems/clipboard
To destroy the previous contents of the file, then write a new string to the file:
open('myfile.txt', 'w') { |f| f << "some text or data structures..." }
To append to a file without overwriting its old contents:
open('myfile.txt', "a") { |f| f << 'I am appended string' }

Ruby unable to use require

This is a newbie question as I am attempting to learn Ruby by myself, so apologies if it sounds like a silly question!
I am reading through the examples of why's (poignant) guide to ruby and am in chapter 4. I typed the code_words Hash into a file called wordlist.rb
I opened another file and typed the first line as require 'wordlist.rb' and the rest of the code as below
#Get evil idea and swap in code
print "Enter your ideas "
idea = gets
code_words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
When I execute this code, it fails with the following error message:
C:/MyCode/MyRubyCode/filecoder.rb:5: undefined local variable or method `code_words' for main:Object (NameError)
I use Windows XP and Ruby version ruby 1.8.6
I know I should be setting something like a ClassPath, but not sure where/how to do so!
Many thanks in advance!
While the top-level of all files are executed in the same context, each file has its own script context for local variables. In other words, each file has its own set of local variables that can be accessed throughout that file, but not in other files.
On the other hand, constants (CodeWords), globals ($code_words) and methods (def code_words) would be accessible across files.
Some solutions:
CodeWords = {:real => "code"}
$code_words = {:real => "code"}
def code_words
{:real => "code"}
end
An OO solution that is definitely too complex for this case:
# first file
class CodeWords
DEFAULT = {:real => "code"}
attr_reader :words
def initialize(words = nil)
#words = words || DEFAULT
end
end
# second file
print "Enter your ideas "
idea = gets
code_words = CodeWords.new
code_words.words.each do |real, code|
idea.gsub!(real, code)
end
#Save the gibberish to a new file
print "File encoded, please enter a name to save the file"
ideas_name = gets.strip
File::open( 'idea-' + ideas_name + '.txt', 'w' ) do |f|
f << idea
end
I think the problem might be that the require executes the code in another context, so the runtime variable is no longer available after the require.
What you could try is making it a constant:
CodeWords = { :real => 'code' }
That will be available everywhere.
Here is some background on variable scopes etc.
I was just looking at the same example and was having the same problem.
What I did was change the variable name in both files from code_words to $code_words .
This would make it a global variable and thus accesible by both files right?
My question is: wouldn't this be a simpler solution than making it a constant and having to write CodeWords = { :real => 'code' } or is there a reason not to do it ?
A simpler way would be to use the Marshal.dump feature to save the code words.
# Save to File
code_words = {
'starmonkeys' => 'Phil and Pete, those prickly chancellors of the New Reich',
'catapult' => 'chucky go-go', 'firebomb' => 'Heat-Assisted Living',
'Nigeria' => "Ny and Jerry's Dry Cleaning (with Donuts)",
'Put the kabosh on' => 'Put the cable box on'
}
# Serialize
f = File.open('codewords','w')
Marshal.dump(code_words, f)
f.close
Now at the beginning of your file you would put this:
# Load the Serialized Data
code_words = Marshal.load(File.open('codewords','r'))
Here's the easy way to make sure you can always include a file that's in the same directory as your app, put this before the require statement
$:.unshift File.dirname(__FILE__)
$: is the global variable representing the "CLASSPATH"

Resources