【问题标题】:Bash: Head & Tail behavior with bash scriptBash:使用 bash 脚本的头部和尾部行为
【发布时间】:2014-10-20 08:06:21
【问题描述】:

假设我有以下脚本:-

test.sh

#!/bin/bash
command1  #prints 5 lines
command2  #prints 3 lines

我使用test.sh|head -n5 运行脚本

在这种情况下会发生什么?它会同时运行这两个命令吗?还是会在 command1 之后停止? 如果我用 -n1 调用它会怎样?

背景:我可能会问一个非常基本的问题,但我实际上注意到了一些有趣的事情。我的脚本(不同的)正在处理 7,000 个文件,每个文件产生 1 行输出。完全运行脚本需要 7 分钟,但是执行 head -n1 会立即给我提示,就像脚本在仅处理第一个文件后终止一样

编辑: 以下是我的脚本

for i in $(ls filepath);do
     echo "$i" # issue here
    python mySript "$i" > "/home/user/output/""$i"".out"
  fi
done

删除上面的 echo 后,脚本可以使用 head -n1 运行整整 7 分钟,但使用 echo 它只会打印第一行然后退出。

【问题讨论】:

  • 它将完整地运行命令,然后简单地将输出传递给 head。一切似乎都在正常工作,我不明白你在问什么?
  • 这似乎是 bash 的行为,因为 echo 是内置命令。尝试将echo 更改为/bin/echo 看看会发生什么。
  • @ymonad 你说得对,你能详细说明一下这种行为吗?
  • 我可能错了,但据我了解,头进程在收到您指定的行数后退出。发生这种情况时,管道被破坏,因此管道另一端的进程也终止。
  • @MangatRai:只是为了更清楚。正如你所描述的echo 是一个bash 内置,SIGPIPE 被发送到bash 解释器(实际上它是管道的作者)。实际上,/bin/echo 确实通过返回退出代码 141 传递了有关接收SIGPIPE 的信息。调用者(此处为 bash)可以方便地处理它(此处忽略)。我可能会建议您是否希望只打印前几行,但您需要运行整个脚本,添加一个命令行参数来执行此操作。

标签: linux bash


【解决方案1】:

这是一个相当有趣的问题!感谢发布!

我假设这发生在head 处理完前几行后退出,所以SIGPIPE 信号被发送到运行脚本的,当它下次尝试echo $x 时。我用 RedX 的脚本来证明这个理论:

#!/usr/bin/bash
rm x.log
for((x=0;x<5;++x)); do
    echo $x
    echo $x>>x.log
done

正如您所描述的,这很有效!使用t.sh|head -n 2 它只向屏幕和x.log 写入2 行。但是捕获 SIGPIPE 这种行为会改变......

#!/usr/bin/bash
trap "echo SIGPIPE>&2" PIPE
rm x.log
for((x=0;x<5;++x)); do
    echo $x
    echo $x>>x.log
done

输出:

$ ./t.sh |head -n 2
0
1
./t.sh: line 5: echo: write error: Broken pipe
SIGPIPE
./t.sh: line 5: echo: write error: Broken pipe
SIGPIPE
./t.sh: line 5: echo: write error: Broken pipe
SIGPIPE

由于管道的另一端已关闭,stdout 已关闭,因此发生写入错误。任何写入已关闭管道的尝试都会导致 SIGPIPE 信号,默认情况下会终止程序(请参阅man 7 signal)。 x.log 现在包含 5 行。

这也解释了为什么/bin/echo 解决了这个问题。请参阅以下脚本:

rm x.log
for((x=0;x<5;++x)); do
    /bin/echo $x
    echo "Ret: $?">&2
    echo $x>>x.log
done

输出:

$ ./t.sh |head -n 2
0
Ret: 0
1
Ret: 0
Ret: 141
Ret: 141
Ret: 141

十进制 141 = 十六进制 8D。十六进制 80 表示收到信号,十六进制 0D 表示 SIGPIPE。因此,当/bin/echo 尝试写入标准输出时,它得到了一个 SIGPIPE,它被终止(作为默认行为)而不是运行脚本的

【讨论】:

  • 很棒的解释,周围的工作也很整洁。我可以猜到为什么 bash 不能处理这种情况。关闭管道并发送 SIGPIPE 是有意义的,否则 shell 中的进程将继续运行,程序员将不得不处理每个进程中的退出场景。非常感谢!
  • @MangatRai:实际上bash 可以通过捕获 SIGPIPE 来处理这种情况。通常在其他进程想要写入时关闭管道是错误的。所以默认行为是终止编写器。但是如果程序员知道这不是一个真正的错误,那么就可以采取行动来解决问题。这种情况不适用于 bash,而是适用于任何写入管道的可执行文件。如果使用head,“管道”可以“考虑”在前几行之后终止管道的源端是正常的。
  • 在我的脚本中,我在 for 循环中有一个 /thepath/otherscript.sh | head | ...。我发现的唯一方法是将head 替换为awk 'NR&lt;=10'sed -ne '1,10p'
  • @Pablo:是的,它们是正确的解决方案,因为它们读取整个输入流,但只打印前 10 行。所以他们在打印前 10 行后没有退出,比如head
【解决方案2】:

很好的发现。根据我的测试,和你说的完全一样。例如我有一个只吃cpu的脚本,让我们在top中发现它:

for i in `seq 10`
  do echo $i
  x=`seq 10000000`
done

使用head -n1 对脚本进行管道传输,我们看到命令在第一行之后返回。这是head 行为:它完成了它的工作,因此它可以停止并将控制权返回给您。

输入脚本应该继续运行,但看看会发生什么:当head 返回时,它的pid 不再存在。所以当linux试图将脚本的输出发送到头进程时,并没有找到该进程,所以脚本崩溃并停止。

让我们用 python 脚本试试吧:

for i in xrange(10):
    print i
    range(10000000)

当运行它并用管道连接到头部时,你会得到这个:

$ python -u test.py | head -n1
0
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    print i
IOError: [Errno 32] Broken pipe

-u 选项告诉 python 像 bash 那样自动刷新标准输入和标准输出。因此,您会看到程序实际上因错误而停止。

【讨论】:

  • 使用python解决问题的根源很可爱。
  • @MangatRai 实际上我做了相反的事情,我想:bash?不,让我们用python写这个,然后弹出错误:)但是下次我遇到一个奇怪的bash问题我会再次使用python!
【解决方案3】:

这更像是一个评论而不是一个答案,但它对于评论来说太大了。

我尝试了以下脚本:

#!/usr/bin/env bash

rm -f "test_head.log"
echo "1 line"
echo "1 line" >> "test_head.log"
echo "2 line"
echo "2 line" >> "test_head.log"
echo "3 line"
echo "3 line" >> "test_head.log"
echo "4 line"
echo "4 line" >> "test_head.log"
echo "5 line"
echo "5 line" >> "test_head.log"
echo "6 line"
echo "6 line" >> "test_head.log"
echo "7 line"
echo "7 line" >> "test_head.log"
echo "8 line"   
echo "8 line" >> "test_head.log"

然后我运行脚本:

./test_head.sh |头 -n1

猫的输出是(令我惊讶):

1 行

我不知道发生了什么。

阅读@ymonad 评论后,我试了一下,将echo 替换为/bin/echo,这样就解决了问题。我希望他能详细解释这种行为。

【讨论】:

    猜你喜欢
    • 2013-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    • 1970-01-01
    • 2016-12-04
    • 2020-04-18
    相关资源
    最近更新 更多