为什么这个 shell 脚本适用于一个实例而不适用于另一个实例?

Why does this shell script work for one instance and not the other?

我需要对以下代码进行一些解释,因为它们是相同的概念,但工作方式不同。所以,我正在尝试执行以下操作:

#!/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

这是我得到的输出:

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

我试过以下方法:

"$relative"
'$relative'
"${relative}"
"$(relative)"

您做对了大部分内容,但忘记转义此处文档中的命令替换结构 $(..)。不这样做会使命令在本地 shell 而不是在远程主机中展开。

此外,虽然 运行 find 命令转义 $relative 会将文字字符串传递给它不理解的 find 命令,即以下情况发生本地机器

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

因此您需要转义整个命令替换结构,以将整个 here-doc 扩展移动到远程主机中。

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

或者完全使用另一种形式的 heredocs,它允许您解释 heredoc 文本中的变量。只需将分隔标识符引用为 'EOF'

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

here-document的内容被解析了两次;一次由本地 shell,然后由远程 shell 再次发送(在通过 ssh 连接发送后)。这两个解析的工作方式不同:第二个(远程)解析按通常的 shell 规则播放,但第一个(本地)解析 查看反斜杠,$表达式(例如变量和命令替换)和反引号式命令替换。最重要的是,本地的根本不注意引号,所以做一些像在某物周围加上单引号这样的事情不会影响它的解析方式(直到它到达远程端)。

例如,在行中:

relative="$HOME/Documents"

$HOME 在本地计算机上扩展到本地用户的主目录路径。我很确定您希望它在远程计算机上扩展到远程用户的主目录路径。为此,您必须转义 $:

relative="$HOME/Documents"

下一行有点复杂:

command=$(find $relative -name GitHub)

在这里,第二个 $ 被转义(通常会保留它以便在远程端进行解释),但第一个不是。这意味着整个 $(find $relative -name GitHub) 命令替换在本地计算机上得到 运行。如果你 运行:

find $relative -name GitHub

...作为常规命令,您将收到错误 "find: ‘$relative’: No such file or directory",因为转义会阻止变量替换的发生。再加上它在错误的计算机上。所以你再次需要转义 $ (顺便说一句,你还应该双引号变量引用):

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

下一行也有类似的问题,但还是成功了。

无论如何,这里有两种可能的解决方案:要么转义 here-document 中 $ 个字符的 all,要么在文档定界符周围加上引号,以及然后不要转义它们中的任何一个,因为这会跳过所有本地解析(除了寻找结束定界符):

#!/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

如果您不想在本地计算机上扩展任何内容,此方法更简单。