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
.