$#/bin/bash
for i in `seq 5`; do
exec 3> >(sed -e "s/^/$i: /"; echo "$i-")
echo foo >&3
echo bar >&3
exec 3>&-
done
,则结果不同步;可能是这样的:
1: foo
1: bar
2: foo
2: bar
1-
3: foo
3: bar
2-
3-
4: foo
5: foo
4: bar
5: bar
4-
5-
如何确保在进行下一个迭代之前完成过程替换
>(...)
?插入
sleep 0.1
之后的exec 3>&-
有所帮助,但是它不雅致,效率低下,并且不能保证始终正常工作。我正在做的是读取循环中的输入流,将每一行馈入一个在循环中偶尔更改的进程。在代码中更容易解释:# again, simplified for illustration
while IFS= read line; do
case $line in
@*)
exec 3>&-
filename=${line:1}
echo "starting $filename"
exec 3> >(sort >"$filename"; echo "finished $filename")
;;
*)
echo "$line" >&3
;;
esac
done
exec 3>&-
#1 楼
以下是在bash 4中使用协同处理的工作:#!/bin/bash
fd_re='^[0-9]+$'
cleanup_and_wait() {
if [[ ${COPROC[1]} =~ $fd_re ]] ; then
eval "exec ${COPROC[1]}<&-"
echo "waiting for $filename to finish" >&2
wait $COPROC_PID
fi
}
while IFS= read -r line; do
case $line in
@*)
cleanup_and_wait
filename=${line:1}
echo "starting $filename" >&2
coproc { sort >"$filename"; echo "Finished with $filename" >&2; }
;;
*)
printf '%s\n' "$line" >&${COPROC[1]}
;;
esac
done
cleanup_and_wait
对于以前的bash版本,可以使用命名管道代替:
cleanup_and_wait() {
if [[ $child_pid ]] ; then
exec 4<&-
echo "waiting for $filename to finish" >&2
wait $child_pid
fi
}
# this is a bit racy; without a force option to mkfifo,
# however, the race is unavoidable
fifo_name=$(mktemp -u -t fifo.XXXXXX)
if ! mkfifo "$fifo_name" ; then
echo "Someone else may have created our temporary FIFO before we did!" >&2
echo "This can indicate an attempt to exploit a race condition as a" >&2
echo "security vulnarability and should always be tested for." >&2
exit 1
fi
# ensure that we clean up even on unexpected exits
trap 'rm -f "$fifo_name"' EXIT
while IFS= read -r line; do
case $line in
@*)
cleanup_and_wait
filename=${line:1}
echo "starting $filename" >&2
{ sort >"$filename"; echo "finished with $filename" >&2; } <"$fifo_name" &
child_pid=$!
exec 4>"$fifo_name"
;;
*)
printf '%s\n' "$line" >&4
;;
esac
done
cleanup_and_wait
一些注意事项:
使用
printf '%s\n' "$line"
比echo "$line"
更安全;例如,如果一行仅包含-e
,则某些版本的echo
对此将不起作用。使用EXIT陷阱进行清理可确保意外的SIGTERM或其他错误不会使过时的fifo呆在身边。 />如果您的平台提供了一种通过单个原子操作创建具有未知名称的FIFO的方法,请使用该方法;这样可以避免出现要求我们始终测试mkfifo是否成功的情况。
评论
不知道为什么,但是如果直接使用> fifo,而不使用文件描述符(例如3),则子进程将在第一个命令printf处停止。我回答的目的是避免最大程度地更改代码。
– Nahuel Fouilleul
2012年6月21日15:03
@NahuelFouilleul好的,谢谢,我写的方式是试图多次打开FIFO的输入端。全面测试了协同处理版本;我应该对FIFO版本执行相同的操作。
–查尔斯·达菲(Charles Duffy)
2012年6月21日15:43
您还需要关闭文件描述符4>&-
– Nahuel Fouilleul
2012年6月21日16:00
@NahuelFouilleul我已经作为cleanup_and_wait函数的一部分进行了此操作。
–查尔斯·达菲(Charles Duffy)
2012年6月21日19:17
一个警告:如果您执行some_cmd |而IFS =读取行;如果执行...,则while循环在子shell中运行,并且在循环之后将无法识别它启动的协同进程。您应该在IFS =读取行时进行写入;做...完成<<(some_cmd);一个常见的把戏,以及另一种使用流程替代的方法!
–音乐爱好者
2012年6月21日19:40
#2 楼
轻松,只需将所有内容通过管道传送到cat。#!/bin/bash
for i in `seq 5`; do
{
exec 3> >(sed -e "s/^/$i: /"; echo "$i-")
echo foo >&3
echo bar >&3
exec 3<&-
}|cat
done
这里的输出是:
1: foo
1: bar
1-
2: foo
2: bar
2-
3: foo
3: bar
3-
4: foo
4: bar
4-
5: foo
5: bar
5-
评论
这是一个绝妙的把戏!但是,在我的第二个示例中(在“ EDIT”下),打开文件描述符,对其进行写入和关闭该过程并未顺序放置在代码中,以允许将其包装在列表中,这使得很难应用您的方法。
–音乐爱好者
2015年6月12日19:15在
我尝试了您的“ EDIT”代码,每行分别输入100个文件名行(@ 00- @ 99),然后输入100行对应编号的数字,再加上一个计数器。在bash 3.2下,输出是干净的(文件23依次包含100行“ 23-00”至“ 23-99”等)。
–ruief
15年6月12日在22:11
标准输出怎么样:“完成n”后总是“开始n + 1”?您是否可以将您的方法应用于代码?
–音乐爱好者
2015年6月13日在0:04
我无法将这种方法应用于订购“完成”的输出。您可能会发现此答案很有帮助:unix.stackexchange.com/a/388530/32078
–ruief
18年7月11日在20:34
#3 楼
mkfifo tmpfifo
for i in `seq 5`; do
{ sed -e "s/^/$i: /"; echo "$i-";} <tmpfifo &
PID=$!
exec 3> tmpfifo
echo foo >&3
echo bar >&3
exec 3>&-
wait $PID
done
rm tmpfifo
评论
这是一个好方法。作为第二个(“如果您不使用bash 4 ...”的话),我采用了一些健壮性修复程序并对其进行了改进。
–查尔斯·达菲(Charles Duffy)
2012年6月21日14:53
#4 楼
“显而易见”的答案是摆脱流程替换。for i in `seq 5`; do
echo foo | sed -e "s/^/$i: /"; echo "$i-"
echo bar | sed -e "s/^/$i: /"; echo "$i-"
done
所以问题就变成了,您真的需要使用流程替换来构造代码吗?上面的内容比尝试同步异步构造要简单得多。
评论
好吧-使用两个sed; echo结构而不是类似{echo foo;回声条} | {sed;回声; }或{sed; echo;} <<(echo foo; echo bar)(如果希望在处理过程中设置变量)并不完全忠于原始形式提供的某些效率...但是,是的,问题在手的确是为什么?!
–查尔斯·达菲(Charles Duffy)
2012年6月21日在1:35
好一点,它只是原始文档中的一个sed过程,它分两个阶段接收其输入。
–chepner
2012年6月21日在1:40
是的,在我的示例中,sed仅用于说明目的,我不能仅仅像上面的状态那样替换流程替换。我需要将循环中各个迭代的输出馈入单个进程替换中。请参阅以上文章中的我的编辑。
–音乐爱好者
2012年6月21日,2:15
顺便说一句-seq是非标准命令(在POSIX中找不到)。如果为bash编写,则使用C样式的for循环更安全:for((i = 0; i <5; i ++))
–查尔斯·达菲(Charles Duffy)
13年3月27日在13:19
评论
与问题无关,但seq是错误的形式(并非在所有平台上都可用,支持bash,效率相对较低);考虑使用C样式的for循环:for((i = 0; i <5; i ++))至于眼前的问题,最好的答案是“不要那样做”。如果您可以说明要达到的目标,我们可以提供一种更好的方法来实现该目标。
有一个相关的常见问题解答项目。