shell管道咋堵住了?

584 阅读1分钟

原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。

背景

起因是这样的,我们想开发一个小脚本,当cpu使用率过高时,使用jstack将java的线程栈保存下来,以便后面分析。  

获取cpu使用率

获取cpu使用率是比较容易的,使用vmstat就可以了,第15列id就是cpu空闲率,用100减一下,就是cpu使用率了。 

于是,我使用如下命令获取了cpu使用率,发现能获取到,如下: 

vmstat 1|awk '{print 100-$15}'

问题出现

但当我在后面再加一个脚本读取cpu使用率时,却发现当cpu到90%以上时,脚本半天都没有输出,如下: 

# 让一个核满载
stress --cpu 1

# cpu高时,自动jstack取线程栈
vmstat 1|awk '{print 100-$15}'|while read cpu; do [[ $cpu -gt 90 ]] && jstack `pgrep java`; done

我以为是我脚本的问题,于是把后面的脚本换成了cat,如下: 

vmstat 1|awk '{print 100-$15}'|cat

发现还是没有输出,这就比较疑惑了,就好像最后那个管道被堵住了一样! 

问题解决

经过在网上一顿搜索,终于发现答案,原来是缓存的锅。当awk的输出目标是终端时,awk不会缓存数据立马输出,而当输出目标是文件或管道时,awk会缓存数据,到一定大小后再输出。  

并且,在awk中可以使用fflush函数,让其立即输出,如下: 

vmstat 1|awk '{print 100-$15; fflush()}'|cat

  同样的,像grep, sed, python之类的命令,都有这样的问题,可如下避免: 

grep --line-buffered
sed -u
python -u

另外,Linux专门提供了一个stdbuf命令,用来避免命令输出时缓存数据,用法如下: 

stdbuf -o L grep

最后,我的小脚本修改如下,终于可以实现目标了。 

vmstat 1|awk '{print 100-$15; fflush()}'|while read cpu; do [[ $cpu -gt 90 ]] && (jstack `pgrep java` > "$(date +'%FT%T')_stack.log"); done

往期内容

这grep咋还不支持\d呢(BRE,ERE,PCRE)
原来awk真是神器啊
Linux文本命令技巧(上)
Linux文本命令技巧(下)
字符编码解惑