Home » Why does bashrc check whether the current shell is interactive?

Why does bashrc check whether the current shell is interactive?

Solutons:


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 $- or $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 .bashrc.

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
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.

Example

Insert the following at the start of a remote .bashrc. (If .bashrc is sourced by .profile or .bash_profile, temporarily disable this while testing):

echo bashrc
fun()
{
    echo functions work
}

Run the following commands locally:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • No i in $- indicates that the shell is non-interactive.
  • No leading - in $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
bashrc
functions work

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, .profile or .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 rsh or 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 rcp, scp and 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 scp uses 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 WONTFIX).

Why scp and sftp fail

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 (.bashrc, .profile,
/etc/csh.cshrc, .login, etc.) which output text messages on login,
intended to be read by humans (like fortune, 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 scp2/sftp and sftp-server.

The reason the shell startup files are relevant at all, is that sshd
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
advantages:

  • 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
remote system:

echo “”

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 scp just
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 rsh or 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 scp or sftp. Exiting after verifying that the current shell is non-interactive is the safest behaviour for .bashrc.

The man page neglects to mention that bash also sources .bashrc for non-interactive remote shells, as in

ssh hostname command

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1010

 COMMAND        EXECUTE BASHRC
 --------------------------------
 bash -c foo        NO
 bash foo           NO
 foo                NO
 rsh machine ls     YES (for rsh, which calls 'bash -c')
 rsh machine foo    YES (for shell started by rsh) NO (for foo!)
 echo ls | bash     NO
 login              NO
 bash               YES

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1050

          /* If we were run by sshd or we think we were run by rshd, execute
             ~/.bashrc if we are a top-level shell. */
          if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
            {
              maybe_execute_file (SYS_BASHRC, 1);
              maybe_execute_file (bashrc_file, 1);

By convention, .bashrc is place where user store the customize configuration for the shell.

These customize configuration can be environment variables, aliases, fancy prompt. With a non-interactive shell, those short of things are meaningless. Moreover, a non-interactive shell can be call in many contexts, you’re not sure those environment variables can lead to false negative case, or even security vulnerable.

A closest example is an alias like:

alias cp='cp -i'

Then it hang your non-interactive shell forever.

So the check perform at the top of .bashrc to ensure that we won’t get trouble.


Because the shell can be called as non-interactive login shell, so explicitly block sourcing *bashrc make no sense.

When the shell was called as non-interactive login shell, it source /etc/profile, then source the first one found in ~/.bash_profile, ~/.bash_login, and ~/.profile:

When bash is invoked as an interactive login shell, or as a
non-interactive shell with the –login option
, it first reads
and executes commands from the file /etc/profile, if that file
exists. After reading that file, it looks for ~/.bash_profile,
~/.bash_login, and ~/.profile, in that order, and reads and
executes commands from the first one that exists and is
readable.

Nothing prevent those files from sourcing .bashrc itself, so doing the check inside .bashrc is safer and make things simple.

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...