This is a question that I was going to post here a few weeks ago. Like terdon, I understood that a
.bashrc is only sourced for interactive Bash shells so there should be no need for
.bashrc to check if it is running in an interactive shell. Confusingly, all the distributions I use (Ubuntu, RHEL and Cygwin) had some type of check (testing
$PS1) to ensure the current shell is interactive. I don’t like cargo cult programming so I set about understanding the purpose of this code in my
Bash has a special case for remote shells
After researching the issue, I discovered that remote shells are treated differently. While non-interactive Bash shells don’t normally run
~/.bashrc commands at start-up, a special case is made when the shell is 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
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
--norc option may be used to inhibit this behavior,
--rcfile option may be used to force another file to be read, but
sshd generally invoke the shell with those options or
allow them to be specified.
Insert the following at the start of a remote
.bashrc is sourced by
.bash_profile, temporarily disable this while testing):
echo functions work
Run the following commands locally:
$ ssh remote_host 'echo $- $0'
$- indicates that the shell is non-interactive.
- No leading
$0 indicates that the shell is not a login shell.
Shell functions defined in the remote
.bashrc can also be run:
$ ssh remote_host fun
I noticed that the
~/.bashrc is only sourced when a command is specified as the argument for
ssh. This makes sense: when
ssh is used to start a regular login shell,
.bash_profile are run (and
.bashrc is only sourced if explicitly done so by one of these files).
The main benefit I can see to having
.bashrc sourced when running a (non-interactive) remote command is that shell functions can be run. However, most of the commands in a typical
.bashrc are only relevant in an interactive shell, e.g., aliases aren’t expanded unless the shell is interactive.
Remote file transfers can fail
This isn’t usually a problem when
ssh are used to start an interactive login shell or when non-interactive shells are used to run commands. However, it can be a problem for programs such as
sftp that use remote shells for transferring data.
It turns out that the remote user’s default shell (like Bash) is implicitly started when using the
scp command. There’s no mention of this in the man page – only a mention that
ssh for its data transfer. This has the consequence that if the
.bashrc contains any commands that print to standard output, file transfers will fail, e.g,
scp fails without error.
See also this related Red Hat bug report from 15 years ago, scp breaks when there's an echo command in /etc/bashrc (which was eventually closed as
SCP (Secure copy) and SFTP (Secure File Transfer Protocol) have their own protocols for the local and remote ends to exchange information about the file(s) being transferred. Any unexpected text from the remote end is (wrongly) interpreted as part of the protocol and the transfer fails. According to a FAQ from the Snail Book
What often happens, though, is that there are statements in either the
system or per-user shell startup files on the server (
.login, etc.) which output text messages on login,
intended to be read by humans (like
echo "Hi there!", etc.).
Such code should only produce output on interactive logins, when there is a
tty attached to standard input. If it does not make this test, it will
insert these text messages where they don't belong: in this case, polluting
the protocol stream between
The reason the shell startup files are relevant at all, is that
employs the user's shell when starting any programs on the user's behalf
(using e.g. /bin/sh -c "command"). This is a Unix tradition, and has
- The user's usual setup (command aliases, environment variables, umask,
etc.) are in effect when remote commands are run.
- The common practice of setting an account's shell to /bin/false to disable
it will prevent the owner from running any commands, should authentication
still accidentally succeed for some reason.
SCP protocol details
For those interested in the details of how SCP works, I found interesting information in How the SCP protocol works which includes details on Running scp with talkative shell profiles on the remote side?:
For example, this can happen if you add this to your shell profile on the
Why it just hangs? That comes from the way how
scp in source mode
waits for the confirmation of the first protocol message. If it's not binary
0, it expects that it's a notification of a remote problem and waits for
more characters to form an error message until the new line arrives. Since
you didn't print another new line after the first one, your local
stays in a loop, blocked on
read(2). In the meantime, after the shell
profile was processed on the remote side,
scp in sink mode was started,
which also blocks on
read(2), waiting for a binary zero denoting the start
of the data transfer.
Conclusion / TLDR
Most of the statements in a typical
.bashrc are only useful for an interactive shell – not when running remote commands with
ssh. In most such situations, setting shell variables, aliases and defining functions isn’t desired – and printing any text to standard out is actively harmful if transferring files using programs such as
sftp. Exiting after verifying that the current shell is non-interactive is the safest behaviour for