7

I would like run remote ssh commands and have them always load server-side startup files by default. I am looking for a solution that does not require:

  • configuration by root
  • adding extra boiler plate to the command each time
  • duplicating environment variables across multiple files

I am not looking to transport local environment variables to the remote shell. I want to run a remote command using the vanilla ssh <host> <command> syntax and have it run using the same environment a remote login session would get.

Background

The below assumes bash is being used for simplicity

By default, remote ssh commands start a non-interactive, non-login shell. Quoting the man page:

If command is specified, it is executed on the remote host instead of a login shell.

You can see this more explicitly on the shell by running:

# the lack of 'i' indicates non-interactive
$ ssh localhost 'echo $-'
hBc 

$ ssh localhost 'shopt login_shell'
login_shell     off

But what if your remote command needs certain environment variables set? Being a non-interactive, non-login shell means neither .bash_profile nor .bashrc will be sourced.

This is problematic, for example, if you're using tools like perlbrew or virtualenv and want to use your custom interpreter in the remote command:

$ which perl
/home/calid/perl5/perlbrew/perls/perl-5.20.1/bin/perl

$ ssh localhost 'which perl'
/usr/bin/perl

Solutions that do not satisfy the requirements above

Explicitly invoke a login shell in the remote command

$ ssh localhost 'bash --login -c "which perl"'

Requires extra boiler plate each time

Explicitly source your profile before the command

$ ssh localhost 'source ~/.bash_profile && which perl'

Requires extra boiler plate each time

Set environment variables in ~/.ssh/environment

Requires root to enable this functionality on the server

Duplicates environment variables already set in server startup files

Set ENV and BASH_ENV

Does not work: .bash_profile and .bashrc aren't sourced

Setting in ~/.ssh/environment works, but requires root access to enable

Preface the command with the environment variables you need

Requires extra boiler plate each time (potentially a lot)

Duplicates environment variables already set in server startup files

Related posts

https://stackoverflow.com/questions/415403/whats-the-difference-between-bashrc-bash-profile-and-environment

https://stackoverflow.com/questions/216202/why-does-an-ssh-remote-command-get-fewer-environment-variables-then-when-run-man#216204

dot file not sourced when running a command via ssh

  • While I think this is a nice and informative question, I do not think it is a good fit for the site. "Best" is going to subjective. – jordanm Mar 25 '15 at 18:56
  • @calid, simplify your question to "how to transport my environment to a remote ssh session", and then provide an answer with the possibilities. That would be a useful question. – glenn jackman Mar 25 '15 at 18:58
  • @jordanm point taken on Best, I've replaced it with Elegant. In terms of subjective question in general, all the solutions (at least I know of) are objectively cumbersome simply in terms of required setup or boiler plate required.. would be nice to know if there's any solution that is less cumbersome. – Dylan Cali Mar 25 '15 at 19:00
  • @glennjackman I feel like that has largely been covered elsewhere (see for example the second link in Related Posts). I'd really like to know if there's a simple solution to this problem. – Dylan Cali Mar 25 '15 at 19:02
  • @jordanm I've edited the post. The issue with being "primarily opinion-based" should be resolved. – Dylan Cali Mar 25 '15 at 23:49
  • Doesn't my answer in http://unix.stackexchange.com/questions/4921/sh-startup-files-over-ssh/4953#4953 include solutions that work for you? Namely, .bashrc if your login shell is bash, or a command= directive in .ssh/authorized_keys. – Gilles 'SO- stop being evil' Mar 28 '15 at 00:36
  • @Gilles those are definitely options. I was actually considering listing this as a possible duplicate of that question and/or hoping you would post those answers here (I didn't want to post them myself as I would have gotten them from your post). The only problem: the command= hack is super icky (especially if you're in a shared account situation - which I am, have to add that to each key line), and putting env vars in bashrc messes up virtualenv stuff (since it clobbers the environment variables set by the virtualenv shell). Is there anything that will cleanly let you reuse your profile? – Dylan Cali Mar 28 '15 at 06:20
  • @Gilles it also turns out the .bashrc trick is hit or miss and may require explicitly compiling bash with SSH_SOURCE_BASHRC set, which makes it even less palatable. (For example it works on my work RHEL6 but not my home Arch Linux). Unfortunate the bash man page doesn't mention this caveat. – Dylan Cali Mar 28 '15 at 07:12
  • I didn't know that sourcing .bashrc over SSH was optional, I'd only encountered bash binaries that have it, thanks. The command= solution is as good as it gets without root's intervention, and having it per-key is actually a good thing on a shared account since it lets each person have their own initialization file. What's icky about it? – Gilles 'SO- stop being evil' Mar 28 '15 at 14:53
  • "on a shared account since it lets each person have their own initialization file." - You know I never thought of it that way – Dylan Cali Mar 28 '15 at 15:46

2 Answers2

2

Create a wrapper shell function sshc which prefixes the source ~/.bash_profile boiler plate for you:

function sshc {
    local host=$1
    local cmd=$2

    ssh $host "source ~/.bash_profile && $cmd"
}

You can then use this as:

$ sshc localhost 'which perl'
/home/calid/perl5/perlbrew/perls/perl-5.20.1/bin/perl
  • Just occurred to me as I was walking over to get my coffee. The only downside is this is a client-side thing, not a one-time server-side solution. – Dylan Cali Mar 25 '15 at 19:40
2

bash does read ~/.bashrc though even when non-interactive when invoked over ssh when compiled with #define SSH_SOURCE_BASHRC as it is on Debian and derivatives at least (a misfeature IMO, but comes handy to you here).

So you could add to the top of ~/.bashrc on the remote host:

if [ -n "$SSH_CLIENT" ] &&
   [ "$SHLVL" = 0 ] &&
   [ -n "${-##*[il]*}" ]; then
   . /etc/profile
   . ~/.bash_profile
fi
  • Confirmed it works for me. The first command in stock .bashrc is usually preceded by a comment # If not running interactively, don't do anything. If you put anything before that line, it will be executed before command when running ssh host command. – raj Jul 12 '22 at 15:02