bash:如何确保终止exec使用的进程替换?

时间:2012-06-21 01:16:37

标签: bash process-substitution

如果我跑

$#/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>&-

5 个答案:

答案 0 :(得分:4)

以下在bash 4中使用coprocesses

#!/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是否成功的条件。

答案 1 :(得分:3)

很简单,只需把所有东西都塞进猫里。

#!/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-

答案 2 :(得分:2)

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

答案 3 :(得分:1)

“显而易见”的答案是摆脱流程替代。

for i in `seq 5`; do
    echo foo | sed -e "s/^/$i: /"; echo "$i-"
    echo bar | sed -e "s/^/$i: /"; echo "$i-"
done

所以问题就变成了,你真的需要使用流程替换来构建代码吗?上述内容比尝试同步异步构造要简单得多。

答案 4 :(得分:1)

另一个用户问同样的问题,并得到详尽的答案here