file open failure test

D

Derek Smith

Hi All,

I want to test if a file open failed, yet I cannot get this to work.

attempt 1:
File.open( "#{RUNLOG}", "a" ) do |file| or raise StandardError ...


attempt 2
File.open( "#{RUNLOG}", "a" ) do |file|
code here...


or raise StabdardError ...
end


thank you!
 
S

Stefano Crocco

|Hi All,
|
|I want to test if a file open failed, yet I cannot get this to work.
|
|attempt 1:
|File.open( "#{RUNLOG}", "a" ) do |file| or raise StandardError ...
|
|
|attempt 2
|File.open( "#{RUNLOG}", "a" ) do |file|
| code here...
|
|
|or raise StabdardError ...
|end
|
|
|thank you!
|

File.open will raise an exception if the file can't be opened. The exact
exception will depend on what actually goes wrong and will be one of the
classes under the Errno module. Unfortunately, I don't know exactly which
class corresponds to which error (if I remember correctly, they may also
depend on your system). If you want to rescue all those exceptions, you can
use SystemCallError, which is the base class for all of them. For example, you
can do the following:

begin
File.open("#{RUNLOG}", "a") do |file|
...
end
rescue SystemCallError
raise StandardError
end


Both your attempts couldn't work because the syntax you used was invalid. The
correct syntax would have been this:

(File.open("#{RUNLOG}", "a") do |file|
...
end) or raise StandardError

The parentheses enclosing the call to File.open and its block is optional. I
put it only for clarity. While the above syntax is correct, however, it won't
work as you expected because File.open raises an exception when it fails. It
would have worked if the method had returned false or nil on failure.

I hope this helps

Stefano
 
R

Robert Klemme

2009/10/26 Stefano Crocco said:
File.open will raise an exception if the file can't be opened. The exact
exception will depend on what actually goes wrong and will be one of the
classes under the Errno module. Unfortunately, I don't know exactly which
class corresponds to which error (if I remember correctly, they may also
depend on your system). If you want to rescue all those exceptions, you c= an
use SystemCallError, which is the base class for all of them. For example= , you
can do the following:

begin
=A0File.open("#{RUNLOG}", "a") do |file|
=A0 =A0...
=A0end
rescue SystemCallError
=A0raise StandardError
end

This does not really make sense since you loose information and
SystemCallError is a StandardError already:

irb(main):002:0> SystemCallError.ancestors
=3D> [SystemCallError, StandardError, Exception, Object, Kernel, BasicObjec=
t]
Both your attempts couldn't work because the syntax you used was invalid.= The
correct syntax would have been this:

(File.open("#{RUNLOG}", "a") do |file|
=A0 =A0...
end) or raise StandardError

The parentheses enclosing the call to File.open and its block is optional= I
put it only for clarity. While the above syntax is correct, however, it w= on't
work as you expected because File.open raises an exception when it fails.= It
would have worked if the method had returned false or nil on failure.

Please note that the block form of File.open returns the last value of
the block and not the File object:

irb(main):004:0> File.open("mm") {|io| 123}
=3D> 123

If you just want the boolean information whether the open succeeded or
not you can do this:

irb(main):010:0> File.open("mm").close || true rescue false
=3D> true
irb(main):011:0> File.open("not existent").close || true rescue false
=3D> false

However, Derek, generally it is better to work with exceptions the way
they are intended. You do not _test_ whether you could do something
and branch the code but you _just do it_ and catch the error if it
surfaces. That keeps the code short and also you can do the exception
handling in a much more appropriate place (i.e. up the call stack).

The "checking idiom" also has a logical flaw: even if the check
signals that the operation could succeed, the operation can still fail
when attempted (because the situation changes - someone changed
permissions of the file just this moment, the file was removed or your
process runs out of file descriptors because some other thread used
them up). This means, even with the check you need to handle the
exception anyway. The check does not add anything to the logic,
rather it makes things more complicated, costs time and conveys a
false notion of safety / robustness.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
B

Bertram Scharpf

Hi,

Am Montag, 26. Okt 2009, 18:40:20 +0900 schrieb Robert Klemme:
If you just want the boolean information whether the open succeeded or
not you can do this:

irb(main):010:0> File.open("mm").close || true rescue false
=> true
irb(main):011:0> File.open("not existent").close || true rescue false
=> false

If you just want to test then ask:

File.stat("xx").readable?

As Robert pointed out it is better straight to do what you want to
do and catch the exceptions where they happen.

Bertram
 
D

Derek Smith

Bertram said:
Hi,

Am Montag, 26. Okt 2009, 18:40:20 +0900 schrieb Robert Klemme:

If you just want to test then ask:

File.stat("xx").readable?

As Robert pointed out it is better straight to do what you want to
do and catch the exceptions where they happen.

Bertram


Thanks you guys! I understand, but coming from (nasty) Perl land there
are
differences. But why the .close here File.open("mm").close ?
What does it do... auto close at end of the block?
 
R

Robert Klemme

Thanks you guys! =A0I understand, but coming from (nasty) Perl land there
are
differences.

Indeed. The OO in Ruby is significantly different (and better) than
that in Perl. I suggest reading one or more of these:

- online tutorials about Ruby
- Pickaxe (only an old version available online via http://www.ruby-doc.org=
/)
- David's "Well-Grounded Rubyist"
=A0But why the .close here File.open("mm").close ?
What does it do... auto close at end of the block?

There is no block (no spoon either btw.). The file is opened and
closed immediately. But as said, rather use the block form without
beforehand checks. See also my blog article for some more discussion
on methods with blocks:

http://blog.rubybestpractices.com/posts/rklemme/001-Using_blocks_for_Robust=
ness.html

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
D

Derek Smith


OK so in relation, I want to have a method for whose responsibility
is logging runtime errors such as failures to open or run OS commands.
However, when I intentionally made it fail, like putting in a non OS
command
it does not log? Any help on this or best practices?

thx!

For example:

#!/usr/bin/env ruby -w
require 'date'
require 'ftools'
require 'fileutils'

ENV['PATH'] =
'/usr/bin:/usr/sbin:/sbin:/bin:/usr/local/bin:/usr/local/vrep'
VERSIONS = 14.to_i
DB_DIR = "/usr/local/vrep/prod/db"
DB_BKUP1 = Dir.glob("#{DB_DIR}/*.gz")
DB_BKUP = "#{DB_DIR}/prod_db_bkup_"
RUNLOG = "/var/log/prod_db_bkup.log"
DBF = Dir.glob("#{DB_DIR}/*.sqlite3")

unless File.exists?( "#{RUNLOG}" )
FileUtils.touch( "#{RUNLOG}" ) \
|| warn("Touch file failed for /var/log/prod_db_bkup.log!")
end

d = Date.today
t = Time.now
h = Hash.new
h["Sun"] = 0
h["Mon"] = 1
h["Tue"] = 2
h["Wed"] = 3
h["Thr"] = 4
h["Fri"] = 5
h["Sat"] = 6

def log_mtd(arg)
File.open( "#{RUNLOG}", "a" ) do |file|
file.puts(arg)
end
end


def db_bkup(arg)
Dir.chdir( "#{DB_DIR}" ) \
|| die_mtd("Change_Dir_Failed_To_#{DB_DIR}_Dump_Will_Fail!")
if DBF.length.to_i < 1
die_mtd("#{DBF}_File_Is_Missing_Dump_Will_Not_Run.+(d.to_s).+(t.hour.to_s)")
else
%x(echo ".dump"|sqlite3 development.sqlite3 |gzip -c >
"#{DB_BKUP.+(arg)}") \
|| die_mtd("SQL_Dump_Cmd_Failed.+(d.to_s).+(t.hour.to_s)")
end
end

if DB_BKUP1.length.to_i >= 1
File.open( "#{RUNLOG}", "a" ) do |file|
h.each_pair do |key, value|
if d.wday == value
file.puts '+'
65.times do file.print "=" end
file.puts "\n","Beginning day: #{key} DB backup at
#{d.to_s} #{t.hour}:#{
t.min}","\n"
file.puts "Executing command echo .dump|sqlite3
development.sqlite3 |gzip
-c","\n"
65.times do file.print "=" end
file.puts "\n",'+'
db_bkup(key .+(d.to_s) .+(t.hour.to_s) .+(t.min.to_s))
end
end
end ||
die_mtd("#{RUNLOG}_Did_Not_Open_.+(d.to_s).+(t.hour.to_s).+(t.min.to_s)")
end
 
R

Robert Klemme

2009/10/26 Derek Smith said:
OK so in relation, I want to have a method for whose responsibility
is logging runtime errors such as failures to open or run OS commands.
However, when I intentionally made it fail, like putting in a non OS
command
it does not log? =A0Any help on this or best practices?

You could start by omitting unnecessary string interpolation as you do
with RUNLOG all the time.

Also, why don't you put the weekday hash into a constant as well?
Btw, you could use an Array for this as well.

DAYS =3D %w{Sun Mon Tue} # ...

Other than that right now I don't have the time to further analyze
your example (which also seems to be missing something, e.g.
definition of die_mtd).

FileUtils.touch throws an exception if it fails. I believe you should
make yourself familiar with the concept of exception handling before
diving further into this project.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
B

Brian Candler

Derek said:
unless File.exists?( "#{RUNLOG}" )
FileUtils.touch( "#{RUNLOG}" ) \
|| warn("Touch file failed for /var/log/prod_db_bkup.log!")
end

1. Why do you want to touch the file? You're going to write to it later
anyway. Opening the file in mode "a" will create it if necessary.

2. If you do touch the file, why would you want to continue if it fails?
If it does fail, you're almost bound to fail when you write to it
anyway.
def log_mtd(arg)
File.open( "#{RUNLOG}", "a" ) do |file|
file.puts(arg)
end
end

Looks OK - as pointed out, File.open(RUNLOG,"a") is sufficient.
def db_bkup(arg)
Dir.chdir( "#{DB_DIR}" ) \
|| die_mtd("Change_Dir_Failed_To_#{DB_DIR}_Dump_Will_Fail!")

Dir.chdir will raise an exception if it fails, not return false/nil.

So you could do something like this:

def db_bkup(arg)
begin
Dir.chdir(DBDIR)
rescue
die_mtd "Change_Dir_Failed_To_#{DB_DIR}_Dump_Will_Fail!"
end

If you end up doing this a lot, you can write a helper function:

def failex(msg)
begin
yield
rescue
die_mtd msg
end
end

...

failex "Change_Dir_Failed_To_#{DB_DIR}_Dump_Will_Fail!" do
Dir.chdir(DBDIR)
end

(Note: 'rescue' by itself captures StandardError and subclasses. If you
want to catch absolutely all exceptions, use 'rescue Exception'. However
this will also catch things like programming errors)

But to be honest, this is not regular Ruby practice. You'd normally just
write your script without error handling, and let the program fail. It
will usually give a fairly obvious exception message itself. This is
easy to try out in irb:
Errno::ENOENT: No such file or directory - /wibble
from (irb):3:in `chdir'
from (irb):3

So if your program gave that message, it would be fairly clear where the
problem lies.
%x(echo ".dump"|sqlite3 development.sqlite3 |gzip -c >
"#{DB_BKUP.+(arg)}") \
|| die_mtd("SQL_Dump_Cmd_Failed.+(d.to_s).+(t.hour.to_s)")

%x{...} forks an new process to run the command, and will not raise an
exception if that program 'fails'. However you can check $? which is the
status of the most recently terminated child.
(irb):4: command not found: flurble
=> ""=> ["success?", "exitstatus", "&", ">>", "stopped?", "stopsig", "to_i",
"coredump?", "to_int", "signaled?", "termsig", "pid", "exited?"]=> false

So you would write your code as:

res = %x{...whatever...}
unless $?.success?
die_mtd "SQL_Dump_Cmd_Failed with status #{$?.exitstatus}"
end

Note that the stdout from the command is captured in 'res'. If you are
not going to make use of this, it's probably better to do

system "...whatever..."

which will allow the child's stdout to go to your ruby script's stdout.
 
S

Simon Krahnke

Just to play around with ruby 1.9:
DAYS = %w{Sun Mon Tue} # ...

You can construct the hash doing the opposite translation with:

DAYS_TO_I = DAYS.each_with_index.inject({}) { | h, (v, i) | h[v] = i; h }.freeze

Any more simple way to do that?

mfg, simon .... l
 
R

Robert Klemme

Just to play around with ruby 1.9:
DAYS = %w{Sun Mon Tue} # ...

You can construct the hash doing the opposite translation with:

DAYS_TO_I = DAYS.each_with_index.inject({}) { | h, (v, i) | h[v] = i; h }.freeze

Any more simple way to do that?

Not that I'm aware of. With a Hash you could use #invert but that is
not available for Array it seems.

Kind regards

robert
 
S

Simon Krahnke

* Robert Klemme said:
Just to play around with ruby 1.9:
DAYS = %w{Sun Mon Tue} # ...

You can construct the hash doing the opposite translation with:

DAYS_TO_I = DAYS.each_with_index.inject({}) { | h, (v, i) | h[v] = i; h }.freeze

Any more simple way to do that?

Not that I'm aware of. With a Hash you could use #invert but that is
not available for Array it seems.

There's Hash#invert? Never saw that before, thank you.

As usual, the Pickaxe is more descriptive than rdoc, the first mentions
that if multiple keys map to the same value, only one of the keys
survives the inversion.

mfg, simon .... l
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,164
Messages
2,570,898
Members
47,440
Latest member
YoungBorel

Latest Threads

Top