2

I'm in need of some explanation of the following code, because they are the same concepts but not working the same. So, I'm trying to do the following:

#!/bin/sh

ssh -T username@host << EOF

relative="$HOME/Documents"
command=$(find \$relative -name GitHub)
command2=$(echo \$relative)
echo "HERE: \$command"
echo "HERE: \$command2"

EOF

Here is the output I get:

find: ‘$relative’: No such file or directory 
HERE:
HERE: /home/username/Documents

I have tried the following:

"\$relative"
'\$relative'
"\${relative}"
"\$(relative)"
Cyrus
  • 84,225
  • 14
  • 89
  • 153
AaronV77
  • 109
  • 1
  • 11

2 Answers2

3

You did the most parts right, but forgot to escape the command-substitution construct in the here-doc $(..). Not doing it will make the command expand in the local shell and not in the remote host.

Also while running the find command an escaped \$relative will pass the literal string to the find command which it does not understand, i.e. the following happens on the local machine

find \$relative
#   ^^^^ since $relative won't expand, find throws an error

So you need to escape the whole command-substitution constructs, to move the whole here-doc expansion in the remote host.

ssh -T username@host << EOF
relative="\$HOME/Documents"
command=\$(find "\$relative" -name GitHub)
command2=\$(echo "\$relative")
echo "HERE: \$command"
echo "HERE: \$command2"
EOF

Or altogether use an alternate form of heredocs that allows you to not interpret variables in the heredoc text. Simply quote the delimiting identifier as 'EOF'

ssh -T username@host <<'EOF'
relative="$HOME/Documents"
command=$(find "$relative" -name GitHub)
command2=$(echo "$relative")
echo "HERE: $command"
echo "HERE: $command2"
EOF
Inian
  • 80,270
  • 14
  • 142
  • 161
2

The contents of the here-document are parsed twice; once by the local shell, then again by the remote shell (after it's sent over the ssh connection). These two parsings work differently: the second (remote) one plays by the usual shell rules, but the first (local) one only looks at backslashes, $ expressions (e.g. variable and command substitutions), and backtick-style command substitutions. Most importantly, the local one doesn't pay any attention at all to quotes, so doing something like putting single-quotes around something has no effect on how it gets parsed (until it gets to the remote end).

For example, in the line:

relative="$HOME/Documents"

$HOME gets expanded on the local computer, to the local user's home directory path. I'm pretty sure you want it to expand on the remote computer, to the remote user's home directory path. To do that, you have to escape the $:

relative="\$HOME/Documents"

The next line's a bit more complicated:

command=$(find \$relative -name GitHub)

Here, the second $ is escaped (which would normally preserve it for interpretation on the remote end), but the first isn't. That means that the entire $(find \$relative -name GitHub) command substitution gets run on the local computer. And if you run:

find \$relative -name GitHub

...as a regular command, you'll get the error "find: ‘$relative’: No such file or directory", because the escape prevents the variable substitution from ever happening. Plus it's on the wrong computer. So again you need to escape the $ (and BTW you should also double-quote the variable reference):

command=\$(find "\$relative" -name GitHub)

The next line has a similar problem, but sort-of succeeds anyway.

Anyway, there are two possible solutions here: either escape all of the $ characters in the here-document, or put quotes around the document delimiter, and then don't escape any of them because that skips all local parsing (except looking for the end delimiter):

#!/bin/sh

ssh -T username@host << 'EOF'

relative="$HOME/Documents"
command=$(find "$relative" -name GitHub)
command2=$(echo "$relative")
echo "HERE: $command"
echo "HERE: $command2"

EOF

If you don't want anything to expand on the local computer, this method's way simpler.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • Thank you for your solution and explanation, but the above will not work for me. I need to be able to take local variables on the local computer and be able to pass them to remote client. I've tried both ways and the escape way will be the only way thats gives me the best of both worlds! – AaronV77 Nov 27 '18 at 23:11
  • @AaronV77 Yep, if you need *selective* expansion, you need to use the unquoted form, and just be really careful to escape *all* `$`s that you want to be interpreted by the remote end. BTW, you also need to escape any backslashes you want sent to the remote end, even if they're inside single-quotes. – Gordon Davisson Nov 27 '18 at 23:22