Transferring environment variable through SSH / quoting in bash/sh/csh/tcsh
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:
FOO=val
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.
Solution 1:
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 =
join"",
(q{echo $SHELL | grep -E "/t?csh" > /dev/null && }
. join(" && ", @qcsh)
. q{ || }
. join(" && ", @qbash)
.q{;});
print shell_quote_scalar($Global::envvar);