7

Probably it is a weird thing I would like to achieve but:
I would like to SSH to a remote host and then execute some bash commands like "alias" automatically to use them afterwards in an interactive mode. The remote host does not allow the personal environment.

Is this possible? Would "expect" do it? Or is there any other way to achieve this?
Basically I would like to have my bashrc without modifying any files on the remote host.

thonixx
  • 182
  • If you relax any files on the remote host to "no files other than a single temporary file", it becomes easy with bash's --rcfile option or $BASH_ENV environment variable. – derobert Mar 20 '14 at 22:30

3 Answers3

3

In other words, you'd like to have a remote ~/.bashrc, but you can't, not even under another name. Bash doesn't support passing initial commands as command line arguments or via environment variables, they have to be in a file. However, the file doesn't have to be on the filesystem!

bash --rcfile /dev/fd/3 3< <(echo 'alias foo="echo hello"')
$ foo
hello

Many SSH servers allow transmitting environment variables whose name is of the form LC_XXX, because they are usually used to indicate locales, which need to be transmitted between hosts and have no security implication. If yours allows that, you can transmit the content of your .bashrc via an environment variable, then feed that environment variable into a file descriptor.

LC_BASHRC=$(cat ~/.bashrc; exec 3<&-) \
ssh -t remote.example.com \
    'exec bash --rcfile /dev/fd/3 3< <(printf %s "$LC_BASHRC")'
  • The content of the local ~/.bashrc is transmitted to the server in the environment variable LC_BASHRC.
  • exec 3<&- is appended, to close the file descriptor after reading the file.
  • On the remote side, the login shell is replaced (exec) by a new instance of bash, which is told to read its initialization file on file descriptor 3.
  • 3< <(…) redirects file descriptor 3 to a command substitution: the output of printf is fed to the parent process via a pipe.

If your login shell on the server is /bin/sh rather than bash or ksh, you can't use the process substitution directly in that shell, you need an extra layer:

LC_BASHRC=$(cat ~/.bashrc; exec 3<&-) \
ssh -t remote.example.com \
    'exec bash -c '\''exec bash --rcfile /dev/fd/3 \
                           3< <(printf %s "$LC_BASHRC")'\'

You can check which environment variables the SSH server accepts by looking for the AcceptEnv directive in its [configuration](http://www.openbsd.org/cgi-bin/man.cgi?query=sshd_config&sektion=5) (/etc/sshd_configor/etc/ssh/sshd_config`).

Instead of using command substitution 3< <(…), you can use a here-string. This creates a temporary file (in $TMPDIR or /tmp) rather than a pipe, so this only works if you don't mind creating a temporary file.

LC_BASHRC=$(cat ~/.bashrc; exec 3<&-) \
ssh -t remote.example.com \
    'exec bash --rcfile /dev/fd/3 3<<<"$LC_BASHRC"'

If you don't mind creating a temporary file, there is a much simpler technique:

  1. Copy your .bashrc to a temporary remote file. You need to do this only once until the temporary file is deleted.
  2. Launch an interactive shell with --rcfile pointing to the temporary file.
remote_bashrc=$(ssh remote.example.com 'bashrc=$(mktemp) && cat >>"$bashrc" && echo "$bashrc"' <~/.bashrc)
ssh -t remote.example.com "exec bash --rcfile '$remote_bashrc'"

If the SSH implementation isn't too antique, you can use a master connection to speed up the launch of multiple SSH commands to the same host.

If you're logging in with a key and you have control over the public key file, you can automate more. In ~/.ssh/authorized_keys, a key can have a command=… directive that lets you run a command instead of what was specified on the command line. See How can I set environment variables for a remote rsync process? for more explanations.

command="if [ -n \"$SSH_ORIGINAL_COMMAND\" ]; then
           eval \"$SSH_ORIGINAL_COMMAND\";
         else exec bash -c 'bash 3<<<\"$LC_BASHRC\"'; fi" ssh-rsa …

or

command="if [ -n \"$SSH_ORIGINAL_COMMAND\" ]; then
           eval \"$SSH_ORIGINAL_COMMAND\";
         else exec bash -c 'bash 3<<<\"alias foo=bar; …\"'; fi" ssh-rsa …

(This needs to be all on one line, I only put line breaks for legibility.)

  • Need to modify remote machine's sshd-config (add AcceptEnv) is a deal-breaker somehow. Though this is as close to the solution as I can see for this problem. – Kashyap Apr 28 '16 at 20:37
0

You can use SendEnv in your .ssh/config. From the man page of ssh_config:

SendEnv Specifies what variables from the local environ(7) should be sent to the server. Note that environment passing is only supported for protocol 2. The server must also support it, and the server must be configured to accept these environment variables. Refer to AcceptEnv in sshd_config(5) for how to configure the server. Variables are specified by name, which may contain wildcard characters. Multiple environment variables may be separated by whitespace or spread across multiple SendEnv directives. The default is not to send any environment variables.

Ketan
  • 9,226
  • 1
    Can you send bash aliases via the environment? I don't think that works... – derobert Mar 20 '14 at 22:26
  • @derobert True, it won't work with bash aliases, just with environment variables. – Ketan Mar 20 '14 at 23:32
  • 1
    First it does not work with aliases, second I can not modify the SSH configuration on the remote host (ok, basically I would be able but I should not). – thonixx Mar 21 '14 at 09:34
0

A solution based on Gilles's that doesn't require adding AcceptEnv on the server for LC_XXX:

ssh -t remote.example.com "
  exec bash --rcfile <(
    printf '%s\n' '$(
      sed "s:':'\\\\'':g" ~/.bashrc
    )'
  )
"

That's to use your local ~/.bashrc in the remote session. It takes advantage of the fact that everything in between single-quotes in bash is taken literally, so the only thing we need to worry about escaping are the inner single quotes themselves. We can safely quote bash-code by surrounding it with ', and substituting inner 's for '\''s.

If you want to add just a few aliases or something inline to the remote ~/.bashrc:

ssh -t remote.example.com "
  exec bash --rcfile <(
    printf '%s\n' '
      . ~/.bashrc
      alias ll=\"ls -l\"
    '
  )
"
JoL
  • 4,735