`ssh <host>` is a login shell, but `ssh <host> <command>` is not?
I've noticed that when I run a command directly on an SSH host using the ssh <host> <command>
syntax, I see the output of .bashrc
but not the output of .bash_profile
(or .profile
).
For instance, if I place the following command at the top of both files,
echo ${BASH_SOURCE[0]}
and manually source .bash_profile
(which sources .bashrc
in turn), I'll see
$ . .bash_profile
.bash_profile
.bashrc
This is the same output I see if I log into this computer remotely via SSH, using the ssh <host>
form of the command. (And if I stow .bash_profile
somewhere else temporarily, neither of these lines gets echoed.)
However, if I execute a command directly on the remote machine with the ssh <host> <command>
form of ssh
, then the output looks like this:
$ ssh <host> echo foo
/home/rlue/.bashrc
foo
My understanding is that the difference between .bash_profile
and .bashrc
is that the former is for login shells while the latter is for interactive, non-login shells.
I've concluded the following:
-
ssh <host>
sources only.bash_profile
, while -
ssh <host> <command>
sources only.bashrc
, which means - the former is a login shell and the latter is not.
Are these conclusions correct? Why is ssh <host> <command>
treated as an interactive, non-login shell? Isn't SSH still logging into the remote machine to execute the command?
OpenSSH (most likely what you're running) decides whether or not to create a login shell, and it only does so if you are not running a specific command. From man ssh
:
If command is specified, it is executed on the remote host instead of a login shell.
So it's an implementation choice for the ssh server whether it wants to create a login shell or not, and if you give a command to run, it does not.
While ssh
does perform a login, if you're having it execute a command and exit, it's really much more similar to creating a shell just to run that command than it is to getting a login environment. It seems, given that, that the people writing OpenSSH decided to treat it like that sort of task.
They create a non-interactive, non-login shell to execute the command, because that's the spirit of running a command in another context/shell. Normally, though, non-interactive shells would not automatically source ~/.bashrc
which is clearly happening here. bash
is actually trying to help us out here. From the docs
Invoked by remote shell daemon
Bash attempts to determine when it is being run with its standard input connected to a network connection, as when executed by the remote shell daemon, usually rshd, or the secure shell daemon sshd. If Bash determines it is being run in this fashion, it reads and executes commands from ~/.bashrc, if that file exists and is readable. It will not do this if invoked as sh. The --norc option may be used to inhibit this behavior, and the --rcfile option may be used to force another file to be read, but neither rshd nor sshd generally invoke the shell with those options or allow them to be specified.
The why of this behavior lies at a lower level than shells: ssh host
(the "login shell" case) uses a pseudoterminal on the remote host, to communicate between the sshd
server process and the shell; ssh host command
uses pipes between sshd
and command
, instead. Pseudoterminals are necessary to make interactive use of a command interpreter, such as a shell, or the "read-eval-print" mode of a scripting language; they implement a bunch of human-friendly features like being able to backspace over typos. But they have more overhead and (depending on configuration) do not allow arbitrary data to pass through unmodified, so SSH avoids using them when interaction is not going to be happening.
Sometimes SSH's command/no command heuristic gets this wrong; it can be overridden with the -t
and -T
switches. For instance, to log into a remote machine and immediately reattach a suspended screen
session, you need to do ssh -t host screen -R
; ssh host screen -R
will cause screen
to complain about not being connected to a terminal. I can't think of a situation when you would actually want to use -T
, but it's there if you ever do find one.
First you need to see the different types, you can read this:
https://unix.stackexchange.com/questions/170493/login-non-login-and-interactive-non-interactive-shells
Now if you open your bashrc you are going to see at the beginning this:
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
That means that depending on how are you accessing to the system this file loads the code inside or not.