我有一个像这样的脚本,我用它来将命令广播到连接到多个不同服务器的 postgresql 数据库 CLI 的多个实例。我正在使用一组硬编码的进程替换。
#!/bin/bash
# names have been changed to protect the guilty
cred="user=dbadmin password=SECRET"
domain=example.com
tee \
>( psql -X "host=db1.$domain dbname=db1 $cred" ) \
>( psql -X "host=db2.$domain dbname=db2 $cred" ) \
>( psql -X "host=db3.$domain dbname=db3 $cred" ) \
>( psql -X "host=xdb1.$domain dbname=xdb1 $cred" ) \
> /dev/null
wait
我想做的是使用 for 循环构建替换数组并将该数组传递给 tee,如下所示:
tee "${p[@]}" > /dev/null
但是当我使用循环时,我得到了每个项目,$p
因为/dev/fd/63
tee 给了我每个项目的错误。
tee: /dev/fd/63: No such file or directory
示例非工作代码:
p=()
for z in db1 db2 db3 xdb1
do
p+=( >( psql -X "host=$z.$domain dbname=$z $cred" ) )
done
tee "${p[@]}" > /dev/null
有办法让这项工作发挥作用吗?
答案1
发生这种情况是因为在这条线上:
p+=( >( psql -X "host=$z.$domain dbname=$z $cred" ) )
...bash 认为该行是一个完整的命令。当进行进程替换时,被替换进程的 STDIN 在命令完成时关闭。
我认为只有两种方法可以做到这一点:
eval
。我们不要去那里。exec
。让我们去那里吧:
p=()
for z in db1 db2 db3 xdb1
do
exec {fd}> >(psql -X "host=$z.$domain dbname=$z $cred")
p+=( $fd )
done
cd /dev/fd && exec tee "${p[@]}" >/dev/null
该{fd}>
语法使 bash 分配一个新的文件描述符,并将其值赋给$fd
,然后将其推入$p
。
现在$p
是一堆文件描述符编号,我们必须tee
写入其中,因此我们cd
将/dev/fd
文件描述符编号写入实际文件,然后调用tee
.
(还有很多其他的剥猫皮的方法,但这是我想到的第一个也是最简单的方法)
答案2
这是我在评论中的意思的一个简单例子。
for 循环(每个 psql 命令重复直到成功,在后台子 shell 中运行)。不需要生成的进程替换文件描述符的数组。
cred='user=dbadmin password=SECRET'
domain='example.com'
tf=$(mktemp)
cat > "$tf"
for z in db1 db2 db3 xdb1 ; do
( while ! psql -X "host=$z.$domain dbname=$z $cred" < "$tf" ; do : ; done) &
done > /dev/null
# don't delete the tempfile until all jobs have completed
wait
rm -f "$tf"
可以选择使用sleep x
而不是:
在while
循环中,以便在每次重试尝试之间有一个小的延迟。
更智能的版本会在 while 循环内执行更多操作,检查退出状态和/或 grep stderr 以找出任何失败的原因并做出适当的响应。