除了read / echo之外还有一种方法是从文件描述符读取一行到stdout而不关闭fd吗?

时间:2014-08-27 19:00:21

标签: bash pipeline file-descriptor

我遇到了我正在做的情况:

outputStuff |
filterStuff |
transformStuff |
doMoreStuff |
endStuff > endFile

我希望能够以如下方式插入一些调试跟踪内容:

tee debugFile.$((debugNum++)) |

但显然管道会创建子壳,所以我想这样做。

exec 5< <(seq 1 100)
outputStuff |
tee debugFile.$(read -u 5;echo $REPLY;) |
filterStuff |
tee debugFile.$(read -u 5;echo $REPLY;) |
transformStuff |
tee debugFile.$(read -u 5;echo $REPLY;) |
doMoreStuff |
endStuff > endFile

即,我希望我插入的调试行是相同的,所以我不必担心踩到各种东西。 read / REPLY回声看起来真的很难看。我想我可以包装一个函数..但有一种方法可以从文件描述符读取一行到stdout而不关闭fd (就像一个头-1将关闭fd是我的头-1&lt;&amp; 3)

4 个答案:

答案 0 :(得分:1)

将所有读取放在单个复合语句中,并将描述符5中的输入重定向到

{ outputStuff    | tee debugFile.$(read -u 5;echo $REPLY;) |
  filterStuff    | tee debugFile.$(read -u 5;echo $REPLY;) |
  transformStuff | tee debugFile.$(read -u 5;echo $REPLY;) |
  doMoreStuff    |
  endStuff
} 5< <(seq 1 100) > endFile

现在,文件描述符5打开一次(并关闭一次),每次调用read都会从该描述符中获取连续的行。

(您也可以稍微简化一下;除非您通过键盘向outputStuff提供输入,否则似乎不需要使用文件描述符5而不是标准输入,因为只有{{ 1}}正在从标准输入读取。所有其他程序都通过管道读取它们的标准输入。)

答案 1 :(得分:1)

我尝试了几件事,但最后@Etan Reisner(无意中)向我证明,即使有办法做你所问的(聪明的,Etan),它也不是你真正想要的。如果你想确保按顺序读回数字,那么读取必须被序列化,而管道中的命令则不是。

实际上,这也适用于您的原始方法,因为命令替换是在子壳中执行的。我认为你可以这样做,但是:

debugum=1
eval "
    outputStuff |
    tee debugFile.$((debugNum++)) |
    filterStuff |
    transformStuff |
    doMoreStuff |
    tee debugFile.$((debugNum++)) |
    endStuff > endFile
"

这样,在启动任何命令之前,所有替换都由字符串上的父shell执行。

答案 2 :(得分:0)

以均匀填充你的号码为代价,你可以用dd做到这一点,虽然你最终没有找到一个更好看的命令来解决你的麻烦。 =)

exec 5< <(seq -w 10 -1 1)
echo -n |
{ echo "d 1:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
{ echo "d 2:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
{ echo "d 3:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
{ echo "d 4:$(dd bs=1 count=3 2>/dev/null <&5)"; cat; } |
cat

您也可以使用read read -u 5 a;echo $a更短的变量,但只能保存两个字符。

答案 3 :(得分:0)

这是一种根本不需要seq的方法:

定义一个递归构造管道的函数。

buildWithDebugging() {
  local -a nextCmd=( )
  local debugNum=$1; shift
  while (( $# )) && [[ $1 != '|' ]]; do
    nextCmd+=( "$1" )
    shift
  done
  if (( ${#nextCmd[@]} )); then
    "${nextCmd[@]}" \
      | tee "debugFile.$debugNum" \
      | buildWithDebugging "$((debugNum + 1))" "$@"
  else
    cat # noop
  fi
}

......并且,使用它:

buildWithDebugging 0 \
  outputStuff '|'
  filterStuff '|'
  transformStuff '|'
  doMoreStuff '|'
  endStuff > endFile

更安全的版本将使用以Pascal字符串而不是C字符串形式完成的管道组件 - 也就是说,而不是使用文字|,在每个命令字符串前面加上其长度: / p>

buildWithDebugging 0 \
  1 outputStuff
  3 filterStuff filterArg filterSecondArg
  2 transformStuff filterArg
  1 doMoreStuff
  endStuff > endFile

对读者来说,建立这个应该是一个完全无足轻重的练习。 :)