如果我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”作为发送到远程服务器的脚本。该脚本首先设置nullglob
shell 选项(假设远程 shell 是bash
),以避免在模式与任何文件名不匹配时在内循环中发生循环。然后它迭代将用当前服务器名称替换的/dir/xyz_$server*/details.txt
模式。$server
然后循环打印出一条关于哪些文件名与模式匹配的短消息,并且代码确保该$pathname
变量不会被当地的shell 通过转义$
.
请注意,$
in$server
未转义,因此将被本地 shell 扩展,这正是我们想要的,但$
in$pathname
需要转义为压制局部扩张。
我们不需要设置IFS
任何非默认值。我假设您这样做是为了能够像您一样编写数组分配,或者出于其他原因,但上面的代码都不需要这样做。
我使用-T
withssh
显式关闭伪 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
是完全多余的。
老实说,这解决了整个问题,因为您当前正在$(…)
本地而不是目标主机上运行。