help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Here document expand partial variable


From: Greg Wooledge
Subject: Re: Here document expand partial variable
Date: Tue, 2 Nov 2021 12:12:41 -0400

On Tue, Nov 02, 2021 at 10:09:38PM +0800, Chenyang Yan wrote:
> I need expand partial variable in ssh command with here document.

Some techniques are shown on <https://mywiki.wooledge.org/BashProgramming/05>
(particularly the "Standard input" section), as well as
<https://mywiki.wooledge.org/BashFAQ/096>.

Now, of course you want one of the more difficult things in the world,
and there may not be ready-made examples that show how to pass both a
script *and* a set of variables all over a single ssh connection.

There are two obvious paths that I can see for doing this.

Path 1: Encode the variables in some way that we think will be safe, and
        inject them directly into the script, which is sent on stdin.
        (I believe this is what you had in mind.)

Path 2: Pass the script on stdin, and then encode the variables in some way
        that we think will be safe, and then pass them as arguments of
        "bash -s" which is the actual ssh command.

Let's explore path 2.

First, observe how bash -s works.

unicorn:~$ echo 'echo "\$1 is $1"' | bash -s foo bar
$1 is foo

We'll try to use this basic structure to pass data as positional parameters
to be interpreted by a script fed on stdin.  (This can be a here document,
or a file, a pipe, or whatever.)

Ssh introduces a twist, because everything that you specify in its command
gets mangled.  It's all mashed together with spaces, and then parsed apart
by a shell on the remote end.

unicorn:~$ echo 'echo "\$1 is $1"' | ssh localhost bash -s foo
greg@localhost's password: 
$1 is foo
unicorn:~$ echo 'echo "\$1 is $1"' | ssh localhost bash -s 'foo bar'
greg@localhost's password: 
$1 is foo

You see what happened here?  We wanted "foo bar" to be one argument, but
ssh turned it into two arguments.

To avoid that, we need to wrap an extra layer of shell-proof quoting
around everything.  In bash 4.4, we can use the @Q expansion to do that.

unicorn:~$ a=$'a b c'
unicorn:~$ echo 'echo "\$1 is $1"' | ssh localhost bash -s "${a@Q}"
greg@localhost's password: 
$1 is a b c

So, there's one answer for you.  We can pass the script on stdin, and
then pass data to it as positional parameters by using bash -s "${var@Q}"
as the command that we specify to ssh.

Please note that @Q quoting is allowed to use syntax that only bash can
understand.  So, the remote account's shell (the one that ssh uses on
the remote system to unpack the mashed-together command and arguments)
must be bash.  This is independent of the shell used to interpret the
script.  You'll need to talk to your system administrator if you're
unsure about this.

I'm not going to explore path 1 at the moment.  I really don't like it.
The chance of an accidental code injection is substantial.  That said,
it does have the distinct advantage of not requiring bash as the remote
account's login shell.

If you don't like path 1, and if your login account's shell isn't bash,
there's still a variant you could employ: instead of doing @Q quoting
to wrap up each variable, you could use what I call "sh quoting" --
replace every single quote with the 4 characters '\'' and then put
single quotes around the whole thing.

There isn't an elegant @Q type expansion operator for this, but you
can do something like:

q=\' b=\\
ssh user@remote bash -s \'"${var//$q/$q$b$q$q}"\'

Here it is in action:

unicorn:~$ a="It's here" q=\' b=\\
unicorn:~$ echo 'echo "\$1 is $1"' | ssh localhost bash -s 
\'"${a//$q/$q$b$q$q}"\'
greg@localhost's password: 
$1 is It's here

Ugly?  You bet!  But it gets the job done.  That's The Bash Way.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]