I assume you know what shell should interpret the script. This example uses sh:
# somewhat flawed
ssh user@remote "FOO='$FOO' exec sh" < script.sh
The local shell will substitute $FOO because it's double-quoted (it is). Single-quotes are for the remote shell (i.e. the original remote shell that execs to the final sh).
Note ssh passes the whole command as a string. It will be interpreted on the remote side. If $FOO expands to a string containing one or more single-quotes, the whole command may misbehave. Code injection is possible.
To "disarm" possible single-quotes you can try this approach:
# still somewhat flawed
FOOx="$(printf '%s' "$FOO" | sed "s|'|'\"'\"'|g")"
ssh user@remote "FOO='$FOOx' exec sh" < script.sh
In my Debian 9 it changes every ' in $FOO into '"'"' in $FOOx. Analyze quoting in the remote shell to see the center single-quote will survive literally.
I can do this without the extra variable:
# still somewhat flawed
ssh user@remote "FOO='$(printf '%s' "$FOO" | sed "s|'|'\"'\"'|g")' exec sh" < script.sh
It's not as firm as I wish. Problems:
- I think some implementations of
sed may print a complete line (with trailing newline character) if their input is an incomplete line (without trailing newline character). Or sed can ignore an incomplete line. This is very important if the content of the variable doesn't end with a newline.
$() removes all trailing newlines.
To deal with these I append x and a newline character before sed. The tool (regardless of implementation) should then accept everything, it should add nothing. Then $() will remove only the extra newline. I remove the extra x on the remote side. Like this:
# foolproof (AFAIK)
ssh user@remote "FOO='$(printf '%sx\n' "$FOO" | sed "s|'|'\"'\"'|g")'; FOO=\"\${FOO%x}\" exec sh" < script.sh
Notes:
- If your (local)
$FOO expands to a string that contains no single-quotes for sure, then you can use the first, simplest command.
If your local shell is Bash then you can replace single-quotes with '"'"' upon expansion like this:
# foolproof (AFAIK), needs local Bash
pattern="'"
replacement="'\"'\"'"
ssh user@remote "FOO='${FOO//$pattern/$replacement}' exec sh" < script.sh
This solves the problem of single quotes and uses neither $() nor sed, so other mentioned problems doesn't appear. I used additional variables to avoid this issue.