Bash 与 ksh 管道

Bash 与 ksh 管道

我的 ksh 脚本遇到了一些问题。 FWIW 我无法克服的问题是,当我使用这样的结构时

command | while read VAR1 
do
   many.commands using $VAR1
done

我经常发现我的脚本不会对通过管道传送到 while 的每一行执行循环。为了测试这一点,我将结构更改为

command > /tmp/tempfile
cat -n /tmp/tempfile >&2
cat /tmp/tempfile | while read VAR1
etc

这证明输出中有很多行。

此外,我在 do 之后立即添加一行,例如

echo DEBUGGING: $VAR1  >&2

这证明循环只运行一次。我真的很困惑。

一种并不总是可行的解决方法是

for X in $(cat /tmp/tempfile )
do
...
done

然后,这可以正常工作,但除了我讨厌这种结构这一事实之外,这意味着您在命令行上扩展整个输入数据(有硬限制)

看来 bash 在处理这种事情上比 ksh 更好。特别是,这似乎可能与读取调用失败有关,但如果循环需要很长时间才能运行,则不会重试。

然而,bash 似乎没有内置的“读取”功能,这意味着我的大部分脚本都需要重新编写。我经常使用大型结构,例如

command1 | command2 | while read SOMEVAR; do awk -F: "... long awk program" | sed "long sed program" ; done | sort -u | tail -1 | read FINAL_ANSWER

问题是 bash 使用 /usr/bin/read ,正如预期的那样,它会尽快丢弃 FINAL_ANSWER 的结果。明显的解决方法是替换

| read FINAL_ANSWER

> /tmp/final_answer && FINAL_ANSWER="$(cat /tmp/final_answer)"

那么......这里有任何脚本专家能够对此提供更多说明吗?我故意没有在这里发布我的真实脚本,因为它们是为客户开发的敏感解决方案的一部分,而且因为我不希望脚本的实际细节混淆问题。

我经常使用“边读边读”格式。它通常有效。事实上,在 25 年的 shell 脚本编写生涯中,我从未遇到过任何问题。现在我遇到了问题。非常令人沮丧。令人困惑。

最初我认为 while read 仅接收或传递第一行输入。但后来我发现了一种情况,当我一遍又一遍地运行脚本时,它会越来越深入地运行到输入中。具体来说我有一些东西

command | while read NEXT_ONE DONEFLAG
do
   if [ $DONEFLAG = "yes" ]
   then
       echo Already completed work for $NEXT_ONE
   else
       dowork $NEXT_ONE && set_flag $NEXT_ONE
   fi
done

事实证明,每次运行脚本时,它都会执行dowork一次。dowork只要花费的时间超过几秒钟,具体是什么并不重要。发生某种 shell 管道超时,然后输入的其余部分消失。谷歌告诉我 dtksh 可以解决这个问题(显然它会重试读/写什么的,我读得不够)

我看到 dtksh 存在于 /usr/st/bin/dtksh 中

这是谁?我不喜欢使用我不知道的 shell,但使用 /usr/dt/bin/dtksh 作为解释器将一小部分脚本拆分为子脚本可能是值得的。

有什么建议吗?

编辑:提供一个示例来说明为什么我不能使用 bash 作为 ksh 作为解释器的直接替代品:

sol10-primary> # cat test.sh
#!/bin/ksh
echo hello| read VAR1
echo $VAR1
sol10-primary> # ./test.sh
hello
sol10-primary> # sed 's/ksh/bash/' <test.sh >test2.sh
sol10-primary> # chmod +x test2.sh
sol10-primary> # ./test2.sh

sol10-primary> #

答案1

你的问题有点漫无目的。我将回答您观察到的 ksh 和 bash 之间的差异,这似乎是核心部分。

当涉及到脚本时,您可能遇到了 ksh 和 bash 之间的第一大不兼容性。 ATT ksh(ksh88 和 ksh93)和 zsh 执行父 shell 中管道中的最后一个(最右边)命令,而其他 shell(Bourne、ash、bash、pdksh、mksh)执行所有命令,包括子 shell 中的最后一个命令。

这是一个简单的测试程序:

msg="a subshell"
true | msg="the parent shell"
echo "This shell runs the last command of a pipeline in $msg"

在 ATT ksh 和 zsh 中,第二次赋值是msg在父 shell 中执行的,因此效果在管道之后可见。在其他 shell 中,此赋值在子 shell 中执行,因此第一个赋值保留在父 shell 中。

解决方法是在管道中执行脚本的其余部分。这是读取数据并随后进行一些处理的常见习惯用法:

output_some_stuff | {
  var=
  while IFS= read -r line; do
    var=$(process "$line")
  done
  use "$var"
}

你似乎有遇到 ksh bug。我建议升级到无错误的版本。如果那不可能,请尝试Stephane Chazelas 的解决方法。虽然您可以尝试在 bash 中运行脚本,但它不是(也不会假装是)ksh 的直接替代品;有很多 bash 没有的 ksh 功能(反之亦然)。 Bash 和 ksh 仅在其 POSIX 核心和其他一些核心功能(特别是数组、[[ … ]]以及由 声明的函数中的局部变量)方面兼容typeset

您还可以尝试 zsh,当调用 as 时,ksh它的行为方式比 bash 更接近 ksh。尽管如此,您可能会遇到不兼容的情况。

相关内容