Access Ruby exit code in END {} - ruby

Ruby has BEGIN {} and END {} blocks that guarantee they run before and after the main portion of code, respectively.
I have the following code:
BEGIN {
$my_args = ARGV.dup
ARGV.clear
} # clean up 'gets'
# For the truly paranoid in all of us
def self.run_away?
print "You're paranoid. Exit? (Y/n) "
ans = gets.chomp.downcase
if ["no", "n"].include?(ans)
puts "Alright, your call. Let's keep going."
else
puts "EXITING"
log("Exiting at paranoid users request.")
exit 3
end
end
END { } # do stuff here
I have a handful of error codes that I have defined in my script.
I would like to be able to read the error code and print a short description based on that code. E.g. - EXITING - 3: Exit at user request instead of writing a descriptive string every time I use an exit in my code. Is there a way to do this in the END {} block? Or something else I am missing?
Edit/Note: I'm stuck with Ruby 1.8.7 and the following doesn't work: (see below)
BEGIN { puts "BEGIN block!" }
puts "Main block!"
exit 3
END {
puts "END block!"
puts "Current Exception: \n#{$!}"
puts "Current Backtrace: \n#{$#}"
}
Output:
~: $ ./test.rb
BEGIN block!
Main block!
~: $ echo $?
3
~: $
Edit #2: I had to define my END block before I exited. Thanks to #Stefan

Kernel#exit raises a SystemExit exception and since the global variable $! contains the current exception, you can get the exit status via $!.status:
END {
puts "exit status: #{$!.status}"
}
exit 3
Output:
exit status: 3
From the documentation:
When an exception has been raised but not yet handled (in rescue,
ensure, at_exit and END blocks) the global variable $! will contain
the current exception and $# contains the current exception’s
backtrace.

A way to centralize exit message :
module Kernel
alias :real_exit :exit
def exit status
puts "Hello World"
real_exit status
end
end
The method is just an around alias on the Kernel#exit method. Be aware that once you will have define this override, all other exit call will go through your override.

Related

Why the global variable not works in .rb file but works in irb?

I was trying to add indentation based on how deep the block goes. I used a global variable to record the depth of block.
$depth = 0
def log(des, &block)
indentation = " " * $depth
$depth += 1
puts "#{indentation}Begginning the #{des} block"
puts "#{indentation}Finished #{des} and returned: #{block.call}"
$depth -= 1
end
log "outer block" do
log "second level block" do
log "third level block" do
"I am number 3"
end
"I am number 2"
end
"I am out most!"
end
In terminal I tried several times ruby file_name.rb, it showed no indentation, it even won't puts the global variable out. After then I copy the code to irb and it worked.
Why this happened?
What's the difference when running ruby code in between these two places?
I suspect you have a different definition of log() in your environment. Changing the name of your routine to something else (say 'mylog') will test this hypothesis.

ruby: finish loop iteration before raising Interrupt

I'm looping through a lot of items and I want to periodically interrupt the loop to save and continue at a later time like this:
begin
big_list.each do |i|
# sensitive stuff
sensitive_method(i)
# other sensitive stuff
end
rescue Interrupt
# finish the current iteration
# then do something else (save)
# don't raise (print done)
end
By sensitive I mean that, if Interrupt is raised in the middle of an iteration, data will be corrupted so I need to guarantee that the iteration finishes before exiting.
Also, if another exception is raised, it should still finish the loop but raise it afterwards
EDIT:
Using the answer by mudasobwa in a test scenario:
while true
result = begin
puts "start"
sleep 1
puts "halfway"
sleep 1
puts "done\n\n"
nil
rescue Exception => e
e
end
case result
when Interrupt
puts "STOPPED"
break
when Exception then raise result
end
end
I get:
start
halfway
done
start
^C: /...
STOPPED
which is my exact problem, I need it to finish the loop (sleep, print halfway, sleep, print done) and only then break out (wrapping the puts, sleep... in a method does not help)
TL;DR: There is no way to continue the execution of the method from inside the middle of it.
big_list.each do |i|
# sensitive stuff
result = begin
sensitive_method(i)
nil
rescue Exception => e
e
end
# other sensitive stuff
case result
when Interrupt
puts "done"
break "done"
when Exception then raise result
end
end
Sidenote: you probably don’t want to rescue the topmost Exception, but some subclass that makes sense to rescue.
To make it possible to finish the chunk of operations:
operations = [
-> { puts "start" },
-> { sleep 1 },
-> { puts "halfway" },
-> { sleep 1 },
-> { puts "done\n\n" }
]
def safe_chunk(operations, index = 0)
result = operations[index..-1].each_with_index(index) do |op, idx|
begin
op.()
rescue Exception => e
safe_chunk(operations, idx) # or idx + 1
break e
end
end
result.is_a?(Array) ? nil : result
end
The Interrupt exception is raised in the main thread. If you use a worker thread to process the list it will never be interrupted. You will need a way to tell the worker thread to terminate though. Rescuing Interrupt in the main thread and setting a flag that's checked by the child can accomplish this.
BigList = (1..100)
def sensitive_method(item)
puts "start #{item}"
sleep 1
puts "halfway #{item}"
sleep 1
puts "done #{item}"
puts
end
#done = false
thread = Thread.new do
begin
BigList.each do |item|
break if #done
sensitive_method item
end
end
end
begin
thread.join
rescue Interrupt
#done = true
thread.join
end
The keyword ensure, used in rescue clauses, is available for situation such as this one, where code must be executed after an exception occurs.
[-1, 0, 1].each do |i|
begin
puts "i=#{i} before exception"
# <additional code>
n = 1/i
rescue ZeroDivisionError => e
puts "Exception: #{e}"
exit
ensure
puts "Just executed 1/#{i}"
# <additional code>
end
end
i=-1 before exception
Just executed 1/-1
i=0 before exception
Exception: divided by 0
Just executed 1/0
Notice that begin/rescue/ensure/end must be inside the loop and that the code after ensure is executed for each i regardless of whether a zero-divide exception occurs.

Ruby: edit thread program to enter function upon termination

Basically in my search for code which will loop, and terminate upon user input, i managed to find code here, and after some alteration, produced this:
#desired destination method, however loop persists!!
def desired_method
print "method entered"
end
Thread.new do
while line = STDIN.gets
break if line.chomp == "" # code detects user input
end
desired_method
end
# program will loop here until user presses enter
loop do
puts "foo"
sleep 1
end
This code is brilliant, and will enter the method 'desired_method' when i hit enter, however the loop persists!! printing 'foo' perpetually after "method entered"!!. I have done some research prior to posting this question on how to kill threads, which i believe may hold the answer. My attempts included naming the thread and using the 'thread.exit' function to kill it, however these techniques have remained unsuccessful.
Can anyone illustrate how i might enter the 'desired_method' method without the persisting "foo" print?
Thanks in advance, and greatly appreciated.
An easy solution here is to use semaphore, signalling between threads with a variable access to both places:
# This will be out stop flag, for signalling between threads.
#time_to_stop = false
def desired_method
print "method entered"
# Here we want the loop in the other thread to stop.
#time_to_stop = true
end
Thread.new do
while line = STDIN.gets
break if line.chomp == "" # code detects user input
end
desired_method
end
# program will loop here until user presses enter
loop do
puts "foo"
sleep 1
# only continue if the stop flag is not set.
break if #time_to_stop
end
Hope this helps.

ruby - How to return from inside eval?

I have a code which I need to use within eval. Sometimes I need to get out from the eval code, but my tries lead to errors.
E.g.:
# expected to see 1, 2 and 5; not 3 nor 4; and no errors
eval "puts 1; puts 2; return; puts 3; puts 4" # => Error: unexpected return
puts 5
I tried with return, end, exit, break, and I couldn't get success. exit doesn't raise errors, but then I don't get the 5.
(Note: I know that eval is evil, but in this case I need to use it.)
Thank you all, but I found a solution which fits best into my problem:
lambda do
eval "puts 1; puts 2; return; puts 3; puts 4"
end.call
puts 5
This way the intuitive return keyword can be used inside eval to get out from it successfully.
I didn't like the conditional-like solutions in this case because it would force me (or the user) to add an end at the end.
About using throw/catch or break, I consider the return keyword more intuitive.
eval'd code is just being run in this place. It's not a function or block. How would you do it without eval? Probably like this:
puts 1
puts 2
if(conditionFor3And4)
puts 3
puts 4
end
you Can't. You can return out of methods, and break out of blocks or loops, but not eval.
You could try a throw/catch block instead
eval "
should_stop = true
catch :stop do
puts 1
puts 2
throw :stop if should_stop
puts 3
end
"
or this:
should_stop = true
catch :stop do
eval "
puts 1
puts 2
throw :stop if should_stop
puts 3
"
end
or just do a conditional like Mchl said, since you probably want it to conditional stop, not just always, but throw catch will let you jump out of a block no matter how many levels down you are, which make it more robust, if you need to break out of a nested loop or something like that.
You could just use conditionals instead of early returns.
You could quite happily define a function and execute it last at the end of the script as follows:
def ev(s)
eval("def doStuff()\n" + s + "\nend\ndoStuff()")
end
ev("
1
return 2
3
")
#=> 2
Demo

How could one block detect that its inside another block?

This is my code:
def block
puts "from block"
yield
end
block do
puts "from command line"
block do
end
end
Here is the output:
from block
from command line
from block
I wonder how the second block could detect that its inside another block (of the same method).
So that the output will be this instead:
from block 1
from command line
from block 2
Is this possible? Because I want the nested block to be aware of this and run some additional code.
Thanks!
You could keep track of the block level with an instance variable, increment it whenever you enter a block, and decrement it whenever you leave a block:
def block
#block_level ||= 0
#block_level += 1
puts "from block ##block_level"
yield
#block_level -= 1
end
This answer is mostly just for fun, I don't suggest you use it.
Ruby lets you inspect the call stack in the form of a backtrace, but only when an exception is raised. So let's raise an exception and then stick out our arm and catch it before it goes to anyone else, and then: the backtrace is all ours!!
Then all you need to do is search the backtrace (an array) for any method calls to our method named "block", and count them.
class InspectBacktrace < Exception
end
def block
raise InspectBacktrace
rescue InspectBacktrace => e
level = e.backtrace.count { |x| x =~ /in `block'/ }
puts "from block #{level}"
yield
end
block do
puts "from command line"
block do
puts "from command line"
block do
puts "from command line"
end
end
end
Output:
from block 1
from command line
from block 2
from command line
from block 3
from command line
Edit: I've since come across the Kernel#caller method which just gives you the current execution stack with no hassles. So the following code might be acceptable, as long as you don't have two methods named "block" in the same file that call each other:
def block
level = caller.count { |x| x =~ /^#{ Regexp.escape(__FILE__) }:\d+:in `block'$/ } + 1
puts "from block #{level}"
yield
end
What yjerem says, just use ensure to avoid troubles with exceptions, and it sounds like a global variable, not instance variable.
def block
begin
$block_level ||= 0
$block_level += 1
puts "from block #{$block_level}"
yield
ensure
$block_level -= 1
end
end

Resources