一、 故事开始

今天有人给了我一个文本文件,里面只有几十行,大致内容如下:

$ cat random.txt
6600
8000
8900
9600
500
1500
2800
3600
…… 此处省略10行

他想写一个shell脚本算下这几十个数的总和,问我能不能帮忙写一个。

额,看到这么低级的东西,我的内心是拒绝的!但是还是拿起键盘给他写了个脚本。

果然30S内,我就写完了这个脚本!如下:

$ cat sum.sh
#!/bin/bash
sum=0
cat random.txt | while read line
do
    ((sum+=$line))
done
echo $sum

正当我得意之时,运行了一下这个脚本:

$ ./sum.sh
0

WTF ! 难道是整数溢出了?

可是不管我怎么删除random.txt的行,结果是始终是0 !这跟整数溢出有毛关系啊!

二、调试开始

没办法,只能开始第一轮调试了。

$ sh -x sum.sh
+ sum=0
+ cat random.txt
+ read line
+ sum+=6600
…… 省略数十行
+ sum+=9400
+ read line
+ echo 0
0
没看出有什么语法或者逻辑问题,所有语句都执行正常!那问题出在哪?

三、进阶调试篇

接下来只能用最原始的方法了。

我在while循环中加入 echo $sum 再执行一遍,结果:

$ ./sum.sh
6600
14600
23500
33100
33600
……
216000
0

加法正常执行了,可是sum还是输出为0 !

这就让人十分恼火了,只能祭出压箱宝了,用更高级的手段调试了。

我利用bash提供的DEBUG信号,逐行跟踪sum变量。

 #!/bin/bash
 trap 'echo "[${BASH_SOURCE}:$LINENO] > \$sum = \"$sum\""' DEBUG
 sum=0
 cat random.txt | while read line
 do
         ((sum+=$line))
         #echo $sum
 done
 echo $sum

修改完后再运行一遍。

$ ./sum.sh
[./sum.sh:3] > $sum = ""
[./sum.sh:4] > $sum = "0"
[./sum.sh:9] > $sum = "0"
0

终于看到点眉目了!

原来我的while循环压根没在当前脚本的逻辑的上下文里执行!

因为管道符后面的while循环是个子进程,子进程是不能修改父进程的变量的!

知道了问题所在,那就好解决了。不让while循环在子进程里执行就好了。

这里可以看出有多坑了,代码本身没有任何毛病,但是逻辑却和常规的变成语言完全不一样。

再一次修改代码:

#!/bin/bash
trap 'echo "[${BASH_SOURCE}:$LINENO] > \$sum = \"$sum\""' DEBUG
sum=0
while read line
do
        ((sum+=$line))
        #echo $sum
done < random.txt
echo $sum

执行一遍:

$ ./sum.sh
[./sum.sh:3] > $sum = ""
[./sum.sh:4] > $sum = "0"
[./sum.sh:6] > $sum = "0"
[./sum.sh:4] > $sum = "6600"
[./sum.sh:6] > $sum = "6600"
[./sum.sh:4] > $sum = "14600"
……省略数十行
[./sum.sh:4] > $sum = "216000"
[./sum.sh:9] > $sum = "216000"
216000

脚本如愿的输出了我想要的结果。终于松了一口气!

四、优雅篇

可是,上面的代码虽然得出了正确的结果,但是仍然输出了大量的调试信息,这要是循环个几千次,是个人都受不了啊!而且保不齐代码执行到哪里会出错又不能把调试接口关了。

这里我最常用的做法是把echo 直接重定向到文件,这在调试接口固定单一的时候还是很不错的,不过要是脚本再复杂一点,加了大量的调试信息的时候,每个使用echo的地方都要添加重定向代码,这种做法可不是我高效程序员的作风。

这里我决定把标准输出重定向到文件(~/test.log),一次性解决。

继续修改代码:

#!/bin/bash
exec 6>&1 # 备份标准输出指针
exec >> ~/test.log
trap 'echo "[${BASH_SOURCE}:$LINENO] > \$sum = \"$sum\""' DEBUG
sum=0
while read line
do
        ((sum+=$line))
        #echo $sum
done < random.txt
exec 1>&6 6>&- # 恢复标准输出指针,关闭 fd6
echo $sum

执行脚本:

$ ./sum.sh
[./sum.sh:14] > $sum = "216000"
216000

这里多输出了一行调试,因为没有抛弃DEBUG信号的,执行的echo $sum 的时候,仍会打印调试信息,应该在这里抛弃DEBUG信号的。

再看~/test.log 文件

$ cat ~/test.log
[./sum.sh:7] > $sum = ""
[./sum.sh:8] > $sum = "0"
[./sum.sh:10] > $sum = "0"
[./sum.sh:8] > $sum = "6600"
[./sum.sh:10] > $sum = "6600"
[./sum.sh:8] > $sum = "14600"
……
[./sum.sh:10] > $sum = "206600"
[./sum.sh:8] > $sum = "216000"
[./sum.sh:13] > $sum = "216000"

里面有我打印的全部调试信息。