1

As part of a series of tasks I have to do, I have to build a bash script that accesses a remote computer, executes 3 commands, waits for a process to end through a SIGINT (or simply end) and then do some cleanup afterwards.

Currently, I'm using this code:

#! /bin/bash

# local preparations
# ...

ssh -t [email protected] <<-'COMMANDS'

    echo "Preparing execution"

    java -jar execute.jar &
    executePID=$!

    echo "Ready."
    echo "CTRL+C to clean and close"

    trap "kill $executePID" INT HUP
    wait $executePID

    #cleanup code here

    echo "done. Logging out"
    sleep 2
    logout
COMMANDS

# Final local cleanup

This code seem to work just find until wait (builtin) command. wait seems to be consuming all commands that come after it and so, when I try to send a SIGINT (Ctrl+C), it seems to fail to execute the trap content and all the cleanup code.

How can I fix this so it works the way I expect?

I'm not allowed to split this bash file into multiple ones and I'm not allowed to create any scripts on the remote computer even if temporary.
Both computers are running the same flavor of Linux.

3
  • What if you move the wait $executePID outside of the ssh command? You could then replace $executePID with the PID of the ssh command instead of the Java command. Also, are you saying that if you execute this script & ctrl-c it then you want it to run some additional commands to cleanup the remote host? If so, then what you need to look at creating your own trap handling function using trap ctrl_c INT and then writing a function called ctrl_c that will be called if a SIGINT is triggered see this blog post.
    – stuts
    Commented Jan 6, 2017 at 16:43
  • Further to my previous comments, do you know that the script is definitely hanging at the wait? Your java command does not launch the command in the background (by finishing the line with &) so what might be happening is that your script launches the java process and that is where the ctrl-c is hitting the command. Are you definitely seeing the 'Ready' & 'CTRL-C' echos when you execute your script?
    – stuts
    Commented Jan 6, 2017 at 16:45
  • @stuts Sorry for the delay. I know where it is hanging because I made some debug outputs to console for the bash and some debug to file for the java program. The <kbd>CTRL</kbd>-<kbd>C</kbd> is being sent to java and the java program is terminating as expected. You got a point about the missing & in that example script. I'll fix it.
    – brunoais
    Commented Feb 1, 2017 at 18:40

1 Answer 1

1

Explanation

It's not about wait. I think this is what happens:

You use <<, so stdin of ssh is redirected to some descriptor through which the entire here document flows.

This other answer explains how ssh is able to capture Ctrl+C when -t is used. This is what matters:

On the client side, ssh will try to set the tty used by stdin to "raw" mode […] Setting raw mode means that characters that would normally send signals (such as Ctrl+C) are instead just inserted into the input stream.

In your case it's not the same stdin your local shell uses. tty used by your local shell is left intact, it is never set to "raw" mode.

So when you hit Ctrl+C it acts locally and terminates ssh. At this moment the remote side gets SIGHUP. Your trap works and kills java. I think there's a pitfall here: a trap executes some code in response to a given signal but it doesn't prevent the signal from having its normal effect. Therefore it looks to me your java would be killed even without a trap because it's a job of the shell that gets terminated in response to SIGHUP.

The shell that gets terminated stops reading and interpreting its stdin. That's why everything that follows wait is discarded.


Solution

commands() { cat <<-'COMMANDS'

    cleanup() {
    # cleanup code here
    echo "Done. Logging out"
    sleep 2
    logout
    }

    echo "Preparing execution"

    java -jar execute.jar &
    executePID=$!

    echo "Ready."
    echo "CTRL+C to clean and close"

    trap "kill $executePID; cleanup" INT HUP
    wait $executePID

    cleanup
COMMANDS

}

stty raw -echo; cat <(commands) - | ssh -t [email protected]; stty -raw echo

The last line is the actual command. First we prepare tty so Ctrl+C cannot act locally. Then we concatenate commands and standard input, we pass it to the remote shell. I cannot do this with here document directly, the command function is a workaround (it would be easier to use a regular file but you said you cannot use more than one). After ssh and cat exit we set the tty to its normal state.

Having pseudo-terminal is essential so make sure ssh -t works (use -tt if needed).

The remote shell must read (buffer) all the commands from commands, only then it can get Ctrl+C when you press it. I guess this means you cannot have too much code after wait. Additionally whatever you want to do after SIGINT must be run from inside the trap. These are the reasons I used a single cleanup function that does it all.

The code is not foolproof. Hit Ctrl+C too early or multiple times and you'll find yourself in the remote shell or with somewhat "broken" local tty. In this latter case use the command reset to reset your tty.

You must press a key (e.g. Enter) after you see "Connection to … closed." The reason is cat won't notice the pipe is broken (due to ssh being no more) until it tries to write something to it.


Alternative

If for any reason the above solution doesn't work for you, use this simpler alternative. The here document is exactly as before. In this case we don't mess with tty, Ctrl+C terminates local ssh as it does with your original code. The difference (with respect to your code) is the trap does the cleaning. I think it would be enough to trap SIGHUP only.

ssh -t [email protected] <<-'COMMANDS'

    cleanup() {
    # cleanup code here
    echo "Done. Logging out"
    sleep 2
    logout
    }

    echo "Preparing execution"

    java -jar execute.jar &
    executePID=$!

    echo "Ready."
    echo "CTRL+C to clean and close"

    trap "kill $executePID; cleanup" INT HUP
    wait $executePID

    cleanup
COMMANDS

Note: when the trap is triggered cleanup echoes a message but you won't see it because your local ssh is already disconnected. You will see the message only if java exits without the trap. Still your cleanup code (# cleanup code here) should be executed.

1
  • Looks great! Thank you! I'll try it when I can and then I'll try to report back on it.
    – brunoais
    Commented Sep 26, 2017 at 8:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .