Suppose that some variable FOO
is defined (and export
-ed) in the current environment. Let me call this environment E0
.
Now, I log into some other Unix system using ssh
.
% ssh frobozz@for.example.com
Let me call the environment of this newly established session E1
.
In general, FOO
will not be defined in E1
, and, in the exceptional cases where it is, in general its value will be set independently of the value of FOO
in E0
.
How can I arrange things so that, upon log in via ssh
, the variable FOO
is set in E1
to the value it has in E0
(and, ideally, export
-ed in E1
as well)?
In case it matters, I'm using zsh
for both E0
and E1
.
For the answer to this question, assume that I do not have any control over the configuration of the sshd
process running on for.example.com
. This rules out, in particular, any solution that requires modifying /etc/ssh/sshd_config
, or the like, on for.example.com
.
Also, assume that the value of FOO
is not constant; it will change from one login session to another. In particular, FOO
may not be set in FOO
at all, in which case it is ok if the effect on E1
is analogous to that of FOO=
or export FOO=
or unset FOO
.
UPDATE: I tried this
% ssh frobozz@for.example.com FOO=$FOO env
…and indeed, the output shows that FOO
is set as specified. But I have not figured out how to apply this idea when initiating a login session. Specifically, if I run
% ssh frobozz@for.example.com FOO=$FOO
…the command just returns without establishing a connection (I figure that ssh
interprets FOO=$FOO
as the "command" to run on the remote host). On the other hand, after I run this
% ssh frobozz@for.example.com FOO=$FOO zsh
…I never see the shell prompt from the remote host; the command just hangs there. (I do, however, get a password prompt from ssh
right before this.) The same thing happens for other variants of the last command, such as
% ssh frobozz@for.example.com FOO=$FOO /path/to/zsh
% ssh frobozz@for.example.com FOO=$FOO env zsh
% ssh frobozz@for.example.com env FOO=$FOO zsh
% ssh frobozz@for.example.com env FOO=$FOO zsh -l
% ssh frobozz@for.example.com 'env FOO=$FOO zsh'
UPDATE2: Actually, what looked like "hanging" turns out to be that the prompt is not visible. I can still run commands. Also, the sequence of zsh initialization files that gets run upon login is different from normal, and (perhaps not suprisingly) the environment is substantially (and non-trivially, IOW above and beyond FOO
) different from normal.
UPDATE3: With help from dave_alcarin and meuh I found that this gets pretty close to what I am after:
% ssh -t frobozz@for.example.com env FOO=$FOO zsh -l -s
There are still some non-trivial differences between the new environment and the normal one that I need to sort out, but this is pretty close to what I want.
Best Answer
SSH has a feature to pass environment variables from the client to the server, but OpenSSH disables it in the default server configuration. There's one exception:
TERM
, which has a special place in the protocol; but communicating information viaTERM
is awkward because you have to be sure that the client will decode it.Nevertheless some servers are configured to let some environment variables through. For example, Debian configures the SSH server to allow all variables whose name begins with
LC_
. Usually these are locale variables, but you can put whatever you like.If the server isn't configured to allow any variable, you can pass them as part of the command, but there are a couple of wrinkles. The wrinkle you're hitting is that if you pass a command to
ssh
, then by default no terminal is created on the server. Sossh frobozz@for.example.com FOO=$FOO zsh
does in fact run a shell, but that shell is connected to a pipe, not to a terminal, so it doesn't display a prompt or support command line edition. Nonetheless, you can type commands — try typingls
Enter, andexit
Enter or Ctrl+D to exit. The solution is to explicitly tell SSH to open a terminal, with the-t
option.Depending on what your login shell is, you may want to run
to avoid having an extra shell process that's the parent of that explicitly-invoked
zsh
.The second problem you haven't run into yet is quoting. The SSH protocol transmits a string which is executed by the remote shell. Here, you're passing the string consisting of
FOO=
, followed by the value ofFOO
, followed by a space andexec zsh
. Thus the value ofFOO
is interpreted as a bit of shell source code, not as a string to store in theFOO
variable. This makes a difference if the value contains characters that have a special meaning in shell syntax. You need to add an extra level of quoting. Assuming your local shell is zsh, you can use theq
parameter expansion flag:Another difference with the straight case is that you're first running a login shell on the remote side, and then replacing it with a non-login shell. Since the chain starts with a login shell (this cannot be avoided), your
.profile
or.zprofile
does get run; however the interactive instance of zsh is not a login shell, which might make a difference depending on the content of your dot files. The best solution for that is probably to avoid doing fragile things in your dot files. You can runzsh -l
, but that runs a second login shell, which is prone to making more of a difference (though you can fix that, too, by making sure that your.profile
or.zprofile
is idempotent).