玩转 SHELL 脚本之:Shell 命令 Buffer 知多少?
1、问题:
下午有同学问了这么一个问题:
tail -n +$(tail -n1 /root/tmp/n) -F /root/tmp/ip.txt 2>&1| awk 'ARGIND==1{i=$0;next}{i++;if($0~/文件已截断/){i=0};print $1"---"i;print i >> "/root/tmp/n"}' /root/tmp/n -
seq 10 > /root/tmp/ip.txt && tail -f /root/tmp/n
把这两条语句分别在同一台机器的两个终端上执行,你会发现第二条语句的 tail 跟踪不到结果,而第一条语句明明是有结果输出的。
在往下细说之前,咱们先简单介绍下第一个语句干嘛的:
这个语句是实时 tail 一份日志,并实现了两个小功能:
当文件被重写的时候将文件的行号置 0,并且当进程挂掉后,重启进程时,能从上次挂掉的地方开始 tail 起,类似“断点续传”。
不熟悉 awk 的同学看起来估计比较费劲,没关系,咱们简化下场景,写个简单的 test case 模拟上面的语句(1):
{ echo 21;sleep 10;echo 22; }|awk '{print >> "/root/tmp/n"}'
你会发现确实是当屏幕输出了 21 的时候, n 的值没有变化,但是当整个 echo 执行完成时,n 的值却一起变化了,输出了 21、22。
对此,你很容易写出另一个 test case:
while [[ $i -lt 10 ]]; do ((i++)); echo $i|awk '{print >> "/root/tmp/n"}'; sleep 2; done
那为什么这个 case 能实时看到 n 的值在变化呢?别急,读完本文,你自会找到答案。^ _ ^
其实语句(1)的问题在于 shell 下的一个概念引发的:buffer
写过程序的同学应该知道 磁盘与内存,内存与CPU 的 IO 交互速度都不在一个量级上,那么为了提高数据的存取效率,一般都会在软件程序、硬件设计中采用 buffer 的设计,当 buffer 满了才会请求一次 IO 操作,而不是一个字符或者一个字节的方式请求 IO 操作,具体说来一般是交互式的会无 buffer 或者小 buffer,非交互式的操作一般 buffer 都会比较大,因为对用户来说“实时性”要求不是那么高了嘛~
语句 command1 | command2 大体的流程图如下:
例如如下语句的流程图如下:
tail -f access.log | cut -d' ' -f1 | uniq
语句(1) 的重定向就是一个典型的非交互式操作,会由于 buffer 的原因,用户无法实时的看到日志中数据的变化。
2、解决方案:
知道原因了,咱们可以有如下几种方式,让 awk 中的重定向变得无 buffer 实时输出:
{ echo 21;sleep 10;echo 22; }|awk '{print >> "/root/tmp/n"; fflush("")}'
{ echo 21;sleep 10;echo 22; }|awk '{print >> "/root/tmp/n"; system("")}'
{ echo 21;sleep 10;echo 22; }|awk '{print >> "/root/tmp/n"; close("/root/tmp/n")}'
{ echo 21;sleep 10;echo 22; }|awk '{system("echo "$0" >> /root/tmp/n")}'
{ echo 21;sleep 10;echo 22; }|awk '{print |"cat - >> /root/tmp/n"}'
关于 fflush 的说明如下:
fflush([file]) Flush any buffers associated with the open output file or pipe file. If file is missing, then standard output is flushed. If file is the null string, then all open output files and pipes have their buffers flushed.
说道这儿,有同学或许会有疑问:还有什么办法去验证是 buffer 的原因呢?
其实你调大你的输出就行了:
{ seq 5000;sleep 10;seq 1000; }|awk '{print >> "/root/tmp/n"}'
3、推而广之
其实 linux shell 下的众多命令都采用了 buffer 的设计,例如 grep,比如就曾经有同学问过我:
tail -f logfile | grep 'ooxx' 为什么看不到结果呢?日志中明明就有的呀? 等等。。。
那本文在此稍稍总结下常用命令的 buffer 问题以及应对措施:
grep (e.g. GNU version 2.5.1) |
--line-buffered |
---|---|
sed (e.g. GNU version 4.0.6) |
-u,--unbuffered |
awk (GNU awk) |
use the fflush() function |
awk (mawk) |
-W interactive |
tcpdump, tethereal |
-l |
例如上文提到的 grep buffer 问题:
tail -f /var/log/foo | grep --line-buffered
也有专门的命令或者工具包来解决这个问题,比如 unbuffer、stdbuf,或者直接调用 c 语言库禁用 buffer:
setvbuf(stdout, 0, _IONBF, 0);
4、Refer:
[1] 9.1.4 Input/Output Functions
https://www.gnu.org/software/gawk/manual/html_node/I_002fO-Functions.html
[2] What is buffering? Or, why does my command line produce no output: tail -f logfile | grep 'foo bar' | awk ...
http://mywiki.wooledge.org/BashFAQ/009
[3] How to fix stdio buffering
http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html
[4] buffering in standard streams
http://www.pixelbeat.org/programming/stdio_buffering/
[5] 关于awk中通过管道执行shell后的管道关闭问题
http://hi.baidu.com/leejun_2005/item/26a5f8273e7e3555c28d5970
[6] Why does awk do full buffering when reading from a pipe
[7] tailf and tail -f
http://blogread.cn/it/article/6892?f=wb
- PHP经典面试题目汇总(上篇)
- 横向滑动的HorizontalListView滑动指定位置的解决方法
- 高可用架构-- MySQL主从复制的配置
- 零基础入门深度学习 | 第二章:线性单元和梯度下降
- 比特币价,黄金和无稽之谈 - 怎样不去给比特币估值
- 在PHP中,cookie和session的使用
- 剑指 offer代码解析——面试题29数组中出线次数超过一半的数字
- 剑指offer代码解析——面试题25二叉树中和为某一值的路径
- Spring MVC 4.2 CORS 跨域访问
- 剑指offer代码解析——面试题31连续子数组的最大和
- 在VS2010上使用C#调用非托管C++生成的DLL文件(图文讲解) 背景
- 剑指offer代码解析——面试题25二叉树中和为某一值的路径
- IntPtr 转 string
- 微信开发中网页授权access_token与基础支持的access_token异同
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 在 Android 的 /data 目录下添加虚拟内存
- 玩转安卓模拟器命令行
- 如何利用NLog输出结构化日志,并在Kibana优雅分析日志?
- Android 应用保存状态
- 2020-8-9日报:修复zip在某些X64机器上的运行崩溃问题
- 专题一:预处理数据(使用sklearn-preprocessing)
- 「Docker」使用 Docker run 覆盖 ENTRYPOINT
- 尝试在 Mono 3.0 下运行 ASP.NET MVC 4
- CentOS7使用yum安装nginx报错:获取 GPG 密钥失败:[Errno 14] curl#60 - "Peer's Certificate has expired."
- iOS 应用使用位置信息
- Silverlight CreateObjectEx 参考
- NHibernate 配置使用 Formula
- Not allowed to navigate top frame to data URL问题
- MonoTouch绑定CocoaTouch类库
- NuGet 使用自定义本地类库目录