9

When I want to stop tail -f I press CTRL+C and return to prompt. But when I run ssh connection the CTRL+C breaks the connection. (meanings of flags described here)

ssh -t svf "cd ~/w/logs; tail -f some_file.log; exec $SHELL -l"

this post does not describes how to prevent remote shell to exit for ssh -t remotehost command args ...

How to stop command (in this example tail -f) with CTRL+C and do not break ssh connection?

2 Answers2

7

In

ssh -t svf "cd ~/w/logs; tail -f some_file.log; exec $SHELL -l"

-t means the local tty line discipline is set to raw (pass-through) mode and a pseudo-tty is created on the remote side.

That pseudo-tty will get default settings which on most systems means for instance that a ^C character will cause a SIGINT to be sent to the foreground process group of that pseudo-terminal.

Here, when you ssh with a command, sshd runs login-shell-of-the-remote-user -c that-command. That's a non-interactive shell, so it doesn't perform job control.

In particular, that shell like every command it runs will run in the same process group which is the foreground process group of the terminal.

The $SHELL -l shell though (note that $SHELL is expanded on the local machine which is probably not what you want) will be an interactive shell (as it's called without -c and its stdin is a terminal device), so will run commands (pipelines) in different process groups and makes them the foreground process group of the terminal or not as necessary.

Here, to make that only tail -f receives the SIGINT, or at least that only it and not the shell is killed, you've got several options depending on the login shell of the remote user.

If the login shell of the remote user is bash or yash, you can do:

ssh -t svf 'set -m; cd ~/w/logs && tail -f some_file.log; exec "$SHELL" -l'

The -m option enables job-control. That tells the shell to run pipelines in separate process groups (like for interactive shells) and (for bash and yash, generally not for all other shells) that the process group of pipelines run in foreground is made the foreground process group of the terminal device (so that they get the SIGINT on ^C, or SIGTSTP on ^Z for instance), and the ones started in background are not (so that they be forbidden (receive SIGTTIN) to read from the terminal for instance).

That way tail -f will run in its own process group, which will be the foreground process group. If you type CTRL+C while tail is running, only tail will receive the SIGINT.

Another alternative if you know the shell is Bourne like, is to set a handler for SIGINT:

ssh -t svf 'trap : INT; cd ~/w/logs && tail -f some_file.log; exec "$SHELL" -l'

Here, we're configuring the shell so it executes the : (no-op) command upon receiving SIGINT. Child processes will inherit that handler, but upon executing a command, the handler will be reset to the default (of terminating the process).

So now, upon CTRL-C while tail is running, both the shell and tail will receive the SIGINT, but only tail will die of it, and the shell will carry on executing "$SHELL" -l.

If you know bash or yash are available on the remote host (and the remote shell is Bourne or csh like), you could also do:

ssh -t svf 'cd ~/w/logs && bash -c "set -m; tail -f some_file.log"; exec "$SHELL" -l'

We invoke the bash shell explicitly and turn the -m option so that tail will be started in its own foreground process group and only it will receive the SIGINT like for the firs solution.

With bash-4.3 or above, you can replace the bash -c "set -m; with bash -mc ". That won't work with older versions which ignore the -m (and -i) option passed the the bash interpreter if combined with -c.

  • ssh -t svf 'cd ~/w/logs && bash -mc "tail -f some_file.log"; exec "$SHELL" -l' does not work as expected, ssh -t svf 'set -m; cd ~/w/logs && tail -f some_file.log; exec "$SHELL" -l' this is fine – Eugen Konkov Mar 02 '16 at 13:35
  • @EugenKonkov, looks like a version issue. It works for me with bash-4.3, but I can reproduce your problem with 4.1. I'll dig more and update the answer. – Stéphane Chazelas Mar 02 '16 at 14:33
  • my version is 4.3.42(1)-release (x86_64-pc-linux-gnu). Thank you for attention. But I use my own clue answered below. – Eugen Konkov Mar 03 '16 at 08:55
0

To prevent CTRL+C to break connection it should be interactive.

This is achieved with bash -i. So I may run:

ssh -t svf 'cd ~/w/logs; bash -i tail -f some_file.log; exec $SHELL -l'

but this cause that problem. To resolve it you may create workaround. On remote machine create script to run command (Notice -i flag. It is important. If you require same ENV as when normal login you may add -l flag also):

$ cat ~/cmd 
#!/bin/bash -i
cmd="$*"
eval $cmd

Than I modify my command to:

ssh -t svf 'cd ~/w/logs; ~/cmd tail -f some_file.log; exec $SHELL -l'

Now I am happy and can CTRL+C when tail -f is run. Because of interactivity bash -i the ssh connection is not broken and exec $SHELL -l command is executed after that hot key is invoked.

IMPORTANT NOTE: You should use ' instead of " to expand $SHELL on a remote side and not on the local. Thanks to Stéphane Chazelas

  • Instead of bash -i tail -f somefile, use bash -mc 'tail -f somefile'. – Stéphane Chazelas Mar 01 '16 at 21:25
  • What does the -m mean? I have not found this option at the man – Eugen Konkov Mar 02 '16 at 09:12
  • and you solution does not work. The next command after bash is not executed when CTRL+C is pressed: bash -mc 'tail -f ~/w/logs/psgi.log'; echo "YES". In this example "YES" is not echoed. – Eugen Konkov Mar 02 '16 at 09:16
  • The -m option (monitor) makes sure commands are started in command groups. Look at the set builtin description in the manual. -i implies -m. You still need the -t option of ssh. ssh -t svf "cd ~/w/logs && bash -mc 'tail -f some_file.log'; exec \"\$SHELL\" -l" (here quoting the $ in $SHELL, otherwise, it's the local $SHELL that's expanded, not the remote one). – Stéphane Chazelas Mar 02 '16 at 09:24
  • Sorry, process groups, not command groups. -m makes sure commands (actually pipelines) are in process groups, and that the process group of pipelines run in foreground are set as the foreground process group of the terminal device (here the pseudo-terminal device created by sshd), and thus receive the SIGINT when ^C is written to the master side of the pseudo-tty (like when you press CTRL+C). – Stéphane Chazelas Mar 02 '16 at 10:02
  • If you know the login shell of the remote user is bash (or yash), you can also do: ssh -t host 'set -m; tail -f...; other-command...' – Stéphane Chazelas Mar 02 '16 at 10:04