0

I'm trying to execute an command via sh -c over SSH in an LXC container. Since lxc-attach is involved I have to use sh -c like this: ssh <host> "lxc-attach -- sh -c \"<command>;<command>\"". This works for most cases. But when I try to use a literal environment variable in the command, the variable keeps expanding.

The problem can be simplified to this: I want to print a literal $HOME

Local:

user@local:~$ sh -c 'echo $HOME'
/home/user
# Despite single quote the variable gets expanded, one has to escape the $ sign
user@local:~$ sh -c 'echo \$HOME'
$HOME

So far so good, but when I try this over SSH remote:

user@local:~$ ssh remote "sh -c 'echo \$HOME'"
/root
user@local:~$ ssh remote 'sh -c 'echo \$HOME''

user@local:~$ ssh remote "sh -c 'echo $HOME'" /root': 1: Syntax error: Unterminated quoted string

I tried serveral variants of escaping the variable. It keeps expanding, nothing is printed or an error appears.

Harry
  • 1

1 Answers1

2

SSH always runs a shell on remote to process the command line you give. See: How do ssh remote command line arguments get parsed

$ ssh remote "sh -c 'echo \$HOME'"
/root

The local shell undoes one layer of quotes, turning \$ into $ SSH sends sh -c 'echo $HOME' to the remote for running in a shell, and the sh you explicitly start expands $HOME.

$ ssh remote 'sh -c 'echo \$HOME''

This is the same as

$ ssh remote 'sh -c echo' \$HOME              # or
$ ssh remote 'sh -c echo' '$HOME'

Note that the space is unquoted (and the echo in the original, but that doesn't matter).

SSH joins the arguments it gets with spaces, and then sends the string sh -c echo $HOME to the remote. The resulting command passed via -c to the inner shell is just the command echo. The expansion of $HOME goes in $0 of that inner sh.

$ ssh remote "sh -c \'echo \$HOME\'"
/root': 1: Syntax error: Unterminated quoted string

Here, the local shell again turns \$ into $, and SSH sends the string sh -c \'echo $HOME\' to the remote, where the shell started by SSH parses those backslashes and runs the command sh with the arguments -c, 'echo, /home/username' (expanding $HOME). The inner sh sees the command 'echo which indeed has an unterminated quoted string.

You'd need something like this

$ ssh localhost 'sh -c "echo \\\$HOME"'
$HOME

to protect the expansion from both the local shell (single quotes above), the shell started by SSH (backslashes), and the shell you started with sh -c (another level of backslashes, one for \, one for $).

Or with single quotes instead of one layer of backslashes:

$ ssh localhost 'sh -c '\''echo \$HOME'\'
$HOME

It's easier to pass the command through a file or a (quoted) here-doc, or some such:

$ ssh localhost 'sh -s' <<'EOF'
echo '$HOME'
EOF
$HOME

See also: Quoting in ssh $host $FOO and ssh $host "sudo su user -c $FOO" type constructs

ilkkachu
  • 138,973