2

What I need is:

  1. Execute something before calling a system command.

  2. Execute my system command

    1. that involve prompting and getting answers from the user

    2. keeping the effects of ctrl-c on the called command intact

  3. Get the result of my system command and carry on with more ruby code execution

So far I tried something that looks like:

#!/usr/bin/env ruby
p "Foo"
exit_value = exec 'heroku run console'
p "Bar"
exit exit_value

This one fails because exec replaces and terminate current process, so no more ruby code is executed after exec

I've already read this post: How to run code after ruby Kernel.exec

And I tried to make do with a Kernel#system call:

#!/usr/bin/env ruby
p "Foo"
system 'heroku run console'
p "Bar"
exit $?

This one also fails, because ctrl-c is apparently caught by my ruby process and kills it instead of reaching its intended target.

So, is there a way to deal with these peculiar requirements?

Community
  • 1
  • 1
Bastes
  • 1,116
  • 1
  • 9
  • 18
  • You can use `Ctrl` + `d` to cancel input in your pre-process. – hek2mgl Jan 30 '15 at 14:56
  • Sorry, the question is not on how to stop the cat command ^^° I merely used the cat command as a way to provide an example you can run on any *nix system, but the command that needs to get messages is heroku of the heroku toolbelt, and ctrl-d won't replace ctrl-c there. – Bastes Jan 30 '15 at 15:26
  • `Ctrl` + `d` would work with any *nix program that reads from stdin – hek2mgl Jan 30 '15 at 15:33
  • Yep. Though when the program doesn't read from stdin, I'd still very much like the opportunity to stop it with a big ctrl-c ^^ The ability to send a ctrl-c is really-really what my question is about, not the ability to send an end-of-input on the stdin. – Bastes Jan 30 '15 at 16:00

2 Answers2

1

Thanks a lot to hek2mgl for pointing in the right direction:

include Signal
include Process

# Handling SIGINT by doing nothing prevents default behaviour
# (killing both processes)
Signal.trap("INT") {}

# Fork off subprocess (so exec won't exit from your main process)
pid = fork
if pid == nil then
  # Child code. Use exec(!) to avoid the signal handler
  # getting called in the child.
  exec 'heroku run console'
else
  # Wait for subprocess to exit
  wait pid
  # "wait" sets the $? according to the subprocess exit status
  exit_status = $?.exitstatus

  p "Execute more ruby code"
  exit exit_status
end
Bastes
  • 1,116
  • 1
  • 9
  • 18
0

I would install a signal trap for SIGINT, fork off the sub process, exec the command (to prevent the signal handler from running in parent and child) and kill the subprocess if SIGINT occurs:

include Signal
include Process

# Define a signal handler for SIGINT
Signal.trap("INT") do
    if $pid != nil then
        # Kill the subprocess
        Process.kill("INT", $pid)
    else
        # Terminate ourself
        exit 1 
    end
end

# Fork off subprocess
# The $ marks the variable as global
$pid = fork
if $pid == nil then
    # Child code. Use exec(!) to avoid the signal handler 
    # getting called in the child.
    exec 'bash -c "echo test; sleep 3; echo test"'
end

# Wait for subprocess to exit
wait $pid
# Reset $pid
$pid = nil
exit_status = $?.exitstatus

# Detect whether the child process has been killed
if exit_status == nil then
    p "Child process has been killed."
end

p "Execute more ruby code"
...
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • Interesting trick, thanks ^^ I just tried it, and it seemed to perform well with given command (bash -c "echo test; sleep 3; echo test") but failed when I tried the target command (heroku run console). Instead of the expected behaviour (same as ctrl-c within irb) it stopped abruptly the heroku command, and exited with the "Execute more ruby code" statement (but not the "Child process has been killed" one). Note that with the "irb" command it performed exactly as I hoped it would. – Bastes Feb 02 '15 at 10:19
  • I added 2 reporting lines in the "INT" trapping block to see which branches of the if block was executed ; it seems that only the main parent process branch ( Process.kill("INT", $pid) ) is executed, so I guess sending an "INT" to the heroku command is not the same as typing ctrl-c in its console, for whatever reason. I'm trying to investigate from here... – Bastes Feb 02 '15 at 10:24
  • (using your answere a little bit like a black board, sorry ^^°) I tried opening a console to heroku in the console, obtained the pid and sent a sigint from the console with kill to it: it behaved like it should. Tried using your example, obtaining the child's pid and targetting the child process: it also worked as expected. A bit puzzling. – Bastes Feb 02 '15 at 10:35
  • Found a way to make this work (see my answer)! Thank you so much for pointing in the right direction ^^d – Bastes Feb 02 '15 at 10:42