`ssh <host>` is a login shell, but `ssh <host> <command>` is not?

12

4

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:

  1. ssh <host> sources only .bash_profile, while
  2. ssh <host> <command> sources only .bashrc, which means
  3. 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?

Ryan Lue

Posted 2017-07-02T09:18:15.347

Reputation: 393

output of .bashrc? That file is not supposed to produce any output. Any output from .bashrc can break all tools using ssh as their transport. – kasperd – 2017-07-02T22:57:38.597

Fair enough. In this case, a couple lines in .bashrc were throwing an error, while similar lines in .bash_profile were not. I took the opportunity to investigate the discrepancy before fixing the offending lines. – Ryan Lue – 2017-07-03T01:15:55.287

Answers

12

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.

Eric Renouf

Posted 2017-07-02T09:18:15.347

Reputation: 1 548

"...it is executed on the remote host instead of a login shell." I don't get this dichotomy. Is the command is executed on the remote host instead of in a login shell, or is the command executed on the remote host, instead of a login shell being executed there? If the former, how is it either/or? (isn't it normally both?) If the latter, it's still executed in the context of some shell, isn't it? (an interactive, non-login one?) So my question is also about semantics — what does "login shell" signify, and why would OpenSSH be designed not to create one for single commands? – Ryan Lue – 2017-07-02T12:40:23.357

@RyanLue The different "flavors" of shells each make certain tasks easier/more secure/optimized etc. While doing an ssh does require a login, the implementers apparently decided that under some circumstances, e.g., asking it to execute a command and return, do not need/benefit from the extra steps that a login shell takes, and so they skip that. So indeed there is a shell that's run, I presume mostly to setup the environment, and since the shell is not going to be provided to the user who logged in, they treat it as though the user had just started a new shell to run that command – Eric Renouf – 2017-07-02T12:48:44.980

"So indeed there is a shell that's run, I presume mostly to setup the environment..." < but I just experimented with this, and it appears that ssh <host> <command> doesn't inherit the environment of any existing login shell. For instance, $ ssh <host> \$PATH returns the path as it would be without sourcing .bash_profile (or .profile, as it were)... In a practical sense, why would you want to circumvent that step? – Ryan Lue – 2017-07-02T13:10:51.800

“...the implementers apparently decided that under some circumstances, e.g., asking it to execute a command and return, do not need/benefit from the extra steps that a login shell takes, and so they skip that.” < also, specifically looking for clarification / insight on this design choice. I define my PATH in .profile — isn't that exactly the kind of thing you'd want loaded before running an arbitrary command on a remote host? – Ryan Lue – 2017-07-02T13:17:45.337

@RyanLue I just editted my answer to try to take a stab at why I think they made that choice – Eric Renouf – 2017-07-02T14:35:23.250

Thanks! But re: "They create a non-interactive, non-login shell to execute the command," if that's true, then why does .bashrc get sourced then? (.bashrc is executed on non-login interactive shells, for the record.)

– Ryan Lue – 2017-07-02T14:52:53.037

@RyanLue good catch, and the answer is now updated to include that too – Eric Renouf – 2017-07-02T15:00:18.123

Wow. You really went the extra mile there; wish I had more than one upvote to give. If you feel like winning some points on the Unix SE, I cross-posted this question there. (Do you think this has any implications on what kinds of settings belong in .bash_profile vs. what kinds belong in .bashrc? For instance, I use .bashrc to ensure that the terminal opens in a tmux session with tmux new -A, but would naturally prefer this not get sourced on a non-interactive ssh shell...)

– Ryan Lue – 2017-07-02T15:10:09.780

@RyanLue SE actually discourages cross posting, so I would recommend deleting either this one or the other one, and I think the problem you're having is why a lot of bashrc's have tests to see if they're interactive or not – Eric Renouf – 2017-07-02T15:12:07.633

1@RyanLue the Boks ssh server distinguishes distinct uses of ssh and can grant permissions for each. Remote login, remote execution, remote copy, Maybe this helps understanding why you would have the behaviour you describe. Remote login (interactive use) might be too much to ask for in a high security environment. Openssh can be restricted by use of rbash/rksh and 'logout' in .bash_profile, or chroot. – bbaassssiiee – 2017-07-03T21:55:17.020

4

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.

zwol

Posted 2017-07-02T09:18:15.347

Reputation: 1 130

1

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.

Genaro Morales

Posted 2017-07-02T09:18:15.347

Reputation: 315

Okay, but this raises an interesting question: .bashrc may be written so as not to be sourced if it's called in a non-interactive context (i.e., if there is no “prompt statement” / $PS1 variable). But ssh <host> <command> is decidedly non-interactive; that is, it does not raise a command prompt. So why would OpenSSH be designed to create a non-login, interactive prompt (one which tries to source .bashrc) for this seemingly login, non-interactive use case?? – Ryan Lue – 2017-07-02T13:15:27.797