Bastes
Bastes

Reputation: 1148

Run a command in current terminal in ruby then execute code when it exits

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?

Upvotes: 2

Views: 1954

Answers (2)

Bastes
Bastes

Reputation: 1148

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

Upvotes: 1

hek2mgl
hek2mgl

Reputation: 158020

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"
...

Upvotes: 0

Related Questions