在 ssh 命令中使用 shell 变量的未定义变量

undefined variable using a shell variable in a ssh command

我是 bash 脚本的新手,可能不会经常这样做。我有以下脚本来检查某个位置的最新更新,但我得到: location: Undefined variable 并且时间戳未成功检索。

#!/bin/bash

servers=('x' 'y')
results=()
location="/path/to/location/"

for server in "${servers[@]}"; do   
    output=$(ssh "$server" 'find "${location}" -exec stat \{} --printf="%y\n" \; | sort -n -r | head -n 1')
    x=(${server}:${output})
    results+=(${x})
done

echo "Printing results..."
for res in "${results[@]}"; do
    echo "$res"
done

我的目标是在最后打印:

x:<timestamp of last update>
y:<timestamp of last update>

我想知道怎么了。

这里的直接问题是 'foo "$bar"' 在单引号内仍然有 bar 的扩展(这使得双引号成为文字,而不是句法)。这不是一件坏事——文字引号,当传递给 ssh 时,可以被 ssh 连接远程端的 shell 接受——但它有副作用 $location 未展开;因此,对 "${location}" 的引用使其成为未定义的远程 shell。


eval 生成正确转义命令或作为参数传递给 ssh 的最佳方法是让 shell 为您完成。在目前的情况下,看起来有点像这样:

printf -v cmd1 '%q ' find "$location" -exec stat --printf='%y\n' '{}' +
ssh "$server" "$cmd1 | sort -n -r | head -n 1"

管道组件未包含在 cmd1 中,因为 -- 在将内容转义为数据而不是语法时 -- printf %q 会导致文字 | 个字符成为作为参数传递给 find 而不是作为 shell 指令。 (这正是您 想要 它对 $location 中的任何文字内容所做的,否则这些文字内容可能会被 运行 作为命令使用!)。

这样,shell 本身(通过 printf %q 的魔力)将负责扩展变量并正确转义您的 find 命令,以确保内容被视为数据(即 '/tmp/$(rm -rf /)'location 不会删除文件)。