shell脚本中while重定向的一个坑

假设我有一个需求:从data.txt里按行读取一个文件,按格式"[行号]内容"输出,再输出读取的行数。
data.txt:
youth is not a time.
it is a state of mind.
oh, you are right.

很简单:
#!/bin/bash
i=0
cat data.txt | while read line
do
  i=`expr $i + 1`
  echo "[$i]$line"
done

echo $i 

执行的结果却令人大吃一惊:
-----输出结果----------------------
[1]youth is not a time.
[2]it is a state of mind.
[3]oh, you are right.
0
------------------------------------
为什么是1?明明有3行的,应该是3啊!!!

原因就是:当while遇到管道(| ,即竖线符号)的时候,循环体会被fork到另一个子shell去执行,此时while中i被改变,然而循环退出时,父shell的i并没变,依旧是0,故而会输出0。
什么?你不信?那么我们试试这个shell:
#!/bin/bash
while true
do
  sleep 20
  cat data.txt | while read line
  do
    sleep 1
  done
done
言简意赅,让外层循环先跑起来,20s后,内循环开始跑,内循环就是使用管道。
$sh test.sh
20sne立刻查看进程
$ps aux | grep sh
可以看到有如下进程:
----输出结果--------------------------
kyle     22777  0.0  0.0   2216   540 pts/0    S+   12:30   0:00 sh tread.sh
---------------------------------------
20s后,再次查看:
$ps aux | grep sh
----输出结果--------------------------
kyle     22777  0.0  0.0   2216   540 pts/0    S+   12:30   0:00 sh tread.sh
kyle     22797  0.0  0.0   2216   540 pts/0    S+   12:30   0:00 sh tread.sh
---------------------------------------
这下知道了为什么i的值为0了吧。

-------------------------------------我是分割线---------------------------
如何避免以上情况?
答案:不要使用管道。
那用什么?
答案:使用重定向。
#!/bin/bash
i=0
while read line
do
  i=`expr $i + 1`
  echo "[$i]$line"
done < data.txt

echo $i
再次运行:
-----输出结果----------------------
[1]youth is not a time.
[2]it is a state of mind.
[3]oh, you are right.
3
------------------------------------

最后,一个for循环也可以达到效果,而且貌似这种速度更快:
for line in `cat data.txt`
do
     echo $line
done
搞定,收工!

你可能感兴趣的:(shell备忘录)