1

I need pass $var_path to bash script inside single quotes and then get commnd executed on remote host. I know that single quotes do not allow passing variables, so tried double quoting, but getting error in sed. Assume this happens because its template uses " as well.

var="Test text"
var_path="/etc/file.txt"
echo "$var"|ssh root@$host 'cat - > /tmp/test.tmp && sed -n "/]/{:a;n;/}/b;p;ba}" $var_path > /tmp/new.conf.tmp'

so with ""

var="Test text"
    var_path="/etc/file.txt"
    echo "$var"|ssh root@$host "cat - > /tmp/test.tmp && sed -n "/]/{:a;n;/}/b;p;ba}" $var_path > /tmp/new.conf.tmp"

Errors from sed

sed: -e expression #1, char 0: unmatched `{'
./script.sh: line 4: n: command not found
./script.sh: line 4: /}/b: No such file or directory
./script.sh: line 4: p: command not found

If $var_path used directly without substitution script works as expected.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Demontager
  • 217
  • 1
  • 5
  • 12

2 Answers2

2

Arguments parsed as part of the command to send to the remote system in SSH in the are concatenated with spaces and then passed to the remote shell (in a manner similar to "${SHELL:-sh}" -c "$*"). Fortunately, bash has the built-in printf %q operation (an extension, so not available in all other POSIX shells) to make strings eval-safe, and thus ssh-safe, if your remote SHELL is bash; see the end of this answer for a workaround using Python's shlex module to generate a command safe in non-bash shells.

So, if you have a command that works locally, the first step is to put it into an array (notice also the quotes around the expansion of "$var_path" -- these are necessary to have an unambiguous grouping):

cmd=( sed -n '/]/{:a;n;/}/b;p;ba}' "$var_path" )

...which you can run locally to test:

"${cmd[@]}"

...or transform into an eval-safe string:

printf -v cmd_str '%q ' "${cmd[@]}"

...and then run it locally with ssh...

ssh remote_host "$cmd_str"

...or test it locally with eval:

eval "$cmd_str"

Now, your specific use case has some exceptions -- things that would need to be quoted or escaped to use them as literal commands, but which can't be quoted if you want them to retain their special meaning. &&, | and > are examples. Fortunately, you can work around this by building those parts of the string yourself:

ssh remote_host "cat - >/tmp/test.tmp && $cmd_str >/tmp/new.conf.tmp"

...which is equivalent to the local array expansion...

cat - >/tmp/test.tmp && "${cmd[@]}" >/tmp/new.conf.tmp

...or the local eval...

eval "cat - >/tmp/test.tmp && $cmd_str >/tmp/new.conf.tmp"

Addendum: Supporting Non-Bash Remote Shells

One caveat: printf %q is guaranteed to quote things in such a way that bash (or ksh, if you're using printf %q in ksh locally) will evaluate them to exactly match the input. If you had a target system with a shell which didn't support extensions to POSIX such as $'', this guarantee would no longer be available, and more interesting techniques become necessary to guarantee robustness in the face of the full range of possible inputs.

Fortunately, the Python standard library includes a function to escape arbitrary strings for any POSIX-compliant shell:

quote_string() {
  python -c '
import sys
try:
  from pipes import quote  # Python 2
except ImportError:
  from shlex import quote  # Python 3

print(" ".join([quote(x) for x in sys.argv[1:]]))
' "$@"
}

Thereafter, when you need an equivalent to printf '%q ' "$@" that works even when the remote shell is not bash, you can run:

cmd_str=$(quote_string "${cmd[@]}")
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • I checked such code `#!/bin/bash var_path="/home/dem/custom.conf" cmd=( sed -n '/]/{:a;n;/}/b;p;ba}' "$var_path" ) eval "${cmd[@]}"` and it produce error ./out.sh sed -n /]/{:a;n;/}/b;p;ba} /home/dem/custom.conf sed: -e expression #1, char 0: unmatched `{' ./out.sh: line 5: n: command not found ./out.sh: line 5: /}/b: No such file or directory ./out.sh: line 5: p: command not found ./out.sh: line 5: ba}: command not found ` a sample custom.conf is http://pastebin.com/aSRXnfYb If i run in terminal `sed -n '/]/{:a;n;/}/b;p;ba}' /home/dem/custom.conf` it works (cut text between { and ]) – Demontager Apr 01 '14 at 09:37
  • @Demontager, nothing in my answer ever advises you to `eval "${cmd[@]}"`. It's important to copy things correctly. – Charles Duffy Apr 01 '14 at 12:39
  • @Demontager, also, pastebin.com is full of ads. Please use gist.github.com, or sprunge.us, or ix.io, or otherwise something else. – Charles Duffy Apr 01 '14 at 12:44
  • ...to be clear: you can run `"${cmd[@]}"` with no `eval`, or you can run `printf -v cmd_str '%q ' "${cmd[@]}"; eval "$cmd_str"`. You **cannot** run `eval "${cmd[@]}"`, unless your goal is to locally reproduce the same bug you were having over ssh. – Charles Duffy Apr 01 '14 at 12:46
  • noted about pastebin. Found that following works `put cmd_str=( sed -n "'/]/{:a;n;/}/b;p;ba}'" "$var_path" )` So than it expanded like `${cmd_str[@]}` in "cat - >....." – Demontager Apr 01 '14 at 16:27
  • @Demontager, might I inquire as to why you're doing increasingly complex things contrary to best practices, as opposed to simply following the advice I'm giving verbatim and without modification? If you use the `printf -v cmd_str '%q ' "${cmd[@]}"` approach, you're guaranteed to get the **exact same command** from local `"${cmd[@]}"` and remote `ssh ... "$cmd_str"`. You don't get that guarantee, and are opening yourself up to shell injection attacks, rolling your own workarounds. In short, you're insisting on doing something both buggy and insecure. – Charles Duffy Apr 01 '14 at 16:29
0

Once you use double quotes the embedded command can simply use single quotes. This works because double quote variable substitutions treat embedded single quotes as regular characters... nothing special.

var="Test text"
    var_path="/etc/file.txt";
    echo "$var"|ssh root@$host "cat - > /tmp/test.tmp && sed -n '/]/{:a;n;/}/b;p;ba}' $var_path > /tmp/new.conf.tmp"

If you wanted to assign and use variables within the double quotes that would be more tricky.

root@anywhere is dangerous... is there no other way? If you are using certificates I hope they restrict root to specific commands.

Gilbert
  • 3,740
  • 17
  • 19
  • Just embedding single-quotes inside double quotes and using expansions within those single quotes opens you up to shell injection attacks if the string it generates is going to be eval'd (as by `ssh`). If you use `"echo '$password'"`, for instance, think about what happens if the password contains `'"$(rm -rf /)"'`. – Charles Duffy Apr 01 '14 at 00:36
  • @Gilbert in my case doesn't work.Error is `bash: -c: line 0: syntax error near unexpected token `&&' ` – Demontager Apr 01 '14 at 10:23