This is a fairly interesting issue! Thanks for posting it!
I assumed that this happens as head
exits after processing the first few lines, so SIGPIPE
signal is sent to the bash running the script when it tries to echo $x
next time. I used RedX's script to prove this theory:
#!/usr/bin/bash
rm x.log
for((x=0;x<5;++x)); do
echo $x
echo $x>>x.log
done
This works, as You described! Using t.sh|head -n 2
it writes only 2 lines to the screen and to x.log. But trapping SIGPIPE this behavior changes...
#!/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
Output:
$ ./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
The write error occurs as stdout
is already closed as the other end of the pipe is closed. And any attempt to write to the closed pipe causes a SIGPIPE signal, which terminates the program by default (see man 7 signal
). The x.log now contains 5 lines.
This also explains why /bin/echo
solved the problem. See the following script:
rm x.log
for((x=0;x<5;++x)); do
/bin/echo $x
echo "Ret: $?">&2
echo $x>>x.log
done
Output:
$ ./t.sh |head -n 2
0
Ret: 0
1
Ret: 0
Ret: 141
Ret: 141
Ret: 141
Decimal 141 = hex 8D. Hex 80 means a signal was received, hex 0D is for SIGPIPE. So when /bin/echo
tried to write to stdout it got a SIGPIPE and it was terminated (as default behavior) instead of the bash running the script.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…