如何在远程通过 SSH 运行 for 循环以便变量扩展?

如何在远程通过 SSH 运行 for 循环以便变量扩展?

如果我ls在远程主机中没有 for 循环的文件,一切都很好,但是,如果我捕获ls变量中的 并尝试echo每个文件,它会失败,因为变量没有扩展/没有值。

我的意思是说:

IFS=$'\n'
servers=(
  blue
  green
  red
)
for i in ${servers[@]}; do 
   ssh $i "
     ls /dir/xyz_${i}*/details.txt
   "
done
/dir/xyx_blue0/details.txt 
/dir/xyz_blue1/details.txt
/dir/xyx_green2/details.txt 
/dir/xyz_green4/details.txt
/dir/xyx_red1/details.txt 
/dir/xyz_red8/details.txt

但我实际上需要循环遍历输出,ls以便我可以对文件执行操作,但是变量不会扩展:

for i in ${servers[@]}; do 
   ssh $i "
     for i in $(ls /dir/xyz_${i}*/details.txt); do 
        echo $i
     done
   "
done
not found: /dir/xyx_blue*/details.txt 
not found: /dir/xyx_green*/details.txt 
not found: /dir/xyx_red*/details.txt 

$i在远程主机上运行循环时如何扩展?

答案1

问题在于您想要在远程服务器上运行的命令替换是在本地运行的。这是因为替换发生在双引号内,这意味着本地 shell 将在调用之前计算其扩展ssh

另外,读取 的输出ls是不明智的(参见为什么*不*解析`ls`(以及该怎么做)?)。

反而:

servers=(
  blue
  green
  red
)

for server in "${servers[@]}"; do
    ssh -T "$server" <<END_SCRIPT
shopt -s nullglob
for pathname in /dir/xyz_$server*/details.txt; do
    printf 'file path is "%s"\n' "\$pathname"
done
END_SCRIPT
done

这使用不带引号的“here-document”作为发送到远程服务器的脚本。该脚本首先设置nullglobshell 选项(假设远程 shell 是bash),以避免在模式与任何文件名不匹配时在内循环中发生循环。然后它迭代将用当前服务器名称替换的/dir/xyz_$server*/details.txt模式。$server

然后循环打印出一条关于哪些文件名与模式匹配的短消息,并且代码确保该$pathname变量不会被当地的shell 通过转义$.

请注意,$in$server未转义,因此将被本地 shell 扩展,这正是我们想要的,但$in$pathname需要转义为压制局部扩张。

我们不需要设置IFS任何非默认值。我假设您这样做是为了能够像您一样编写数组分配,或者出于其他原因,但上面的代码都不需要这样做。

我使用-Twithssh显式关闭伪 TTY 分配。


可以如果您觉得有必要,还可以将脚本作为单个字符串提供:

servers=(
  blue
  green
  red
)

for server in "${servers[@]}"; do
    ssh "$server" "
shopt -s nullglob
for pathname in /dir/xyz_$server*/details.txt; do
    printf 'file path is \"%s\"\n' \"\$pathname\"
done"
done

在这种情况下,您还必须确保对每个嵌入的双引号进行转义(否则它们将结束构成远程 shell 脚本的双引号字符串)。

答案2

永远不要循环;for的输出ls从字面上看,这就是*通配符的用途,而您对 的整个使用ls是完全多余的。

老实说,这解决了整个问题,因为您当前正在$(…)本地而不是目标主机上运行。

相关内容