I want to transfer an environment variable over SSH.

The "correct" way is using SendEnv/~/.ssh/environment, but that requires the server to support AcceptEnv or PermitUserEnvironment, which it does not in my case.

So instead I am thinking to set the variable on the remote site like this:

export FOO
ssh server export FOO=$FOO'; do_stuff_which_uses_FOO'

That part is easy. I want a generic solution, so no matter the content of $FOO it will work. E.g.

FOO="  '\""
export FOO
QFOO=`quote "$FOO"` # quote will return "\ \ \'\\\""
export QFOO
ssh server export FOO=$QFOO'; do_stuff_which_uses_FOO'

This works no matter if the sending or receiving shell is sh or bash.

However, I also need it to work for csh/tcsh. And I will not know in advance which shell the receiving end is running. That means I have to code something that will work both in /bin/sh and /bin/csh.

So far I have managed to get it working for sh/bash:

ssh server // followed by the below quoted
eval `echo $SHELL | grep -E "/(t)?csh" > /dev/null && echo setenv FOO \\\ \\\ \\\\\'\\\\\" || echo export FOO=\\\ \\\ \\\\\'\\\\\";` ; echo "$FOO"

I can also get it to work for csh/tcsh (the user csh has csh as login shell):

ssh csh@server // followed by the below quoted
eval `echo $SHELL | grep -E "/(t)?csh" > /dev/null && echo setenv FOO \\\ \\\ \\\\\'\\\\\" || echo export FOO=\\\ \\\ \\\\\'\\\\\";` ; echo "$FOO"

If $FOO is * or ? it works fine with BASH:

ssh server eval\ \`echo\ \$SHELL\ \|\ grep\ -E\ \"/\(t\)\?csh\"\ \>\ /dev/null\ \&\&\ echo\ setenv\ FOO\ \\\\\\\\\\\*\\\;\ \|\|\ echo\ export\ FOO=\\\\\\\\\\\*\\\;\`\;echo\ \"\$FOO\"\ a;

But it fails with csh:

ssh csh@server eval\ \`echo\ \$SHELL\ \|\ grep\ -E\ \"/\(t\)\?csh\"\ \>\ /dev/null\ \&\&\ echo\ setenv\ FOO\ \\\\\\\\\\\*\\\;\ \|\|\ echo\ export\ FOO=\\\\\\\\\\\*\\\;\`\;echo\ \"\$FOO\"\ a;
No match.
FOO: Undefined variable.

It seems * and ? refuse to be quoted with .

To answer this question your solution should:

  • be able to transfer an environment variable to the remote server
  • not use SendEnv/AcceptEnv/PermitUserEnvironment
  • work no matter if the source shell is sh/bash/csh/tcsh
  • work no matter if the destination shell is sh/bash/csh/tcsh
  • work no matter the content of the environment variable. Specifically it should at least work for: \n * space ' " ? < > ! $ \ and any combination of those.

If you can find a better way to transfer the variable than quoting it, then that is fine, too.

  • It sounds like we're doing your homework. Why can you not use the ssh SendEnv or AcceptEnv options? That's what they are DESIGNED for. – UtahJarhead Oct 15 '12 at 13:50
  • It is to be used for --env in GNU Parallel. GNU Parallel is used by estimated 25000 users. A lot of these users do not have root access on the systems they are using (often it is compute clusters). If AcceptEnv had not required root access then I would agree. As you can see below I have found a solution that works for all but \n. – Ole Tange Oct 15 '12 at 17:57
  • Ah, that make sense. Thanks for the clarification. – UtahJarhead Oct 15 '12 at 18:01

I now have a working model for all characters except \n:

sub shell_quote_scalar {
    # Quote the string so shell will not expand any special chars
    # Returns:
    #   string quoted with \ as needed by the shell
    my $a = shift;
    $a =~ s/([\002-\011\013-\032\\\#\?\`\(\)\{\}\[\]\*\>\<\~\|\; \"\!\$\&\'\202-\377])/\\$1/g;
    $a =~ s/[\n]/'\n'/g; # filenames with '\n' is quoted using \'
    return $a;

sub env_quote {
    my $v = shift;
    $v =~ s/([ \n\&\<\>\(\)\;\'\{\}\t\"\$\`\*\174\!\?\~])/\\$1/g;
    return $v;

my @qcsh = map { my $a=$_; "setenv $a " . env_quote($ENV{$a})  } @vars;
my @qbash = map { my $a=$_; "export $a=" . env_quote($ENV{$a}) } @vars;

$Global::envvar =
    (q{echo $SHELL | grep -E "/t?csh" > /dev/null && }
     . join(" && ", @qcsh)
     . q{ || }
     . join(" && ", @qbash)

print shell_quote_scalar($Global::envvar);

Can you just base64-encode the variable? The easiest way to deal with this is probably to simply treat the data as binary.

export QFOO=`echo $FOO | base64 --wrap=0`
ssh server "export FOO=`echo \"$QFOO\" | base64 --decode --wrap=0`; <command>"

You might have to fiddle with the quoting on the ssh line, but that's the gist of it. This should remove shell specifics with regards to quoting, but might introduce some with sub-commands (I'm not much familiar with anything outside of bash)

  • Not a bad idea. Though I probably cannot assume base64 is installed (base64 is part of GNU coreutils and only introduced 2006 - thus we cannot assume it is installed on non-GNU systems and on pre-2006 GNU systems). I can, however, assume an old version of Perl is installed, so using (un)pack with template "u*", that should be doable. – Ole Tange Oct 2 '12 at 20:52
  • uuencoded strings include *. ARGH! But hex encoded should work. – Ole Tange Oct 2 '12 at 21:07
  • I cannot find a way to make this work: As the command need to work under both csh and bash, I need to eval it. And as soon as I eval it then the * and ? will cause problems in csh. echo setenv U perl -e '($a="212223c2a425262f28293d410a417b5b5d7d5e7e2a41") =~ s/(..)/chr hex $1/eg;print $a' – Ole Tange Oct 2 '12 at 21:46
  • @OleTange Is openssl available? hints.macworld.com/article.php?story=20030721010526390 – Darth Android Oct 2 '12 at 21:52
  • Nope. But even if base64 was available it would not fix the issue: I still need to do the eval and it is that step that breaks in (t)csh if the variable contains * or ? (Bash works fine, and so does csh/tcsh as long as the variable does not contain * or ?). – Ole Tange Oct 3 '12 at 12:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.