问题
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)"
回答1:
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
回答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.
来源:https://stackoverflow.com/questions/53493901/why-does-this-shell-script-work-for-one-instance-and-not-the-other