本文属记录性的文章,仅供参考
问题描述
写shell脚本时,如果将输出保存到文件,有些支持在屏幕上动态显示进度的命令(如docker pull
,curl
,wget
,aws s3
,ossutil
等)会通过回车符CR
,即 \r
来更新最后一行已经输出过的内容,用来更新进度百分比、进度条等。
比如阿里云的ossutil
工具,在交互式终端使用时,它的进度条会不断更新:
# 过程中
root@localhost:~$ ossutil cp oss://oss-bucket/software/example-desktop-11.0.0-win64-zip-chs.zip .
Total num: 1, size: 2,315,088,831. Dealed num: 0, OK size: 1,070,261,950, Progress: 51.001%, Speed: 107325.88KB
# 完成后
root@localhost:~$ ossutil cp oss://oss-bucket/software/example-desktop-11.0.0-win64-zip-chs.zip .
Succeed: Total num: 1, size: 2,315,088,831. OK num: 1(download 1 objects).
average speed 136382000(byte/s)
19.781217(s) elapsed
完成后的总结报告,共有约169个字符。
但若将这些命令的输出(stdout、stderr)重定向到文件时,输出的这些中间过程便也会进入文件,形成超长的一段内容(这里测试时是702字符),如下:
# 将标准输出重定向到文件
root@localhost:~$ ossutil cp oss://oss-bucket/software/example-desktop-11.0.0-win64-zip-chs.zip . > down.log
root@localhost:~$ ls -l down.log
-rw-r--r-- 1 root root 702 Jun 21 01:19 down.log # 足足有702字符长
root@localhost:~$ less down.log #查看文件内容
^MTotal num: 1, size: 2,088,315,831. Dealed num: 0, OK size: 1,049,378,790, Progress: 50.250%, Speed: 203556.46KB/s^M
^MTotal num: 1, size: 2,088,315,831. Dealed num: 0, OK size: 1,388,730,140, Progress: 66.500%, Speed: 62521.22KB/s^M
^MTotal num: 1, size: 2,088,315,831. Dealed num: 0, OK size: 1,931,692,300, Progress: 92.500%, Speed: 105637.46KB/s^M
^MSucceed: Total num: 1, size: 2,088,315,831. OK num: 1(download 1 objects).
average speed 124630000(byte/s)
16.756609(s) elapsed
down.log (END)
这些^M
实际上就是\r
,将此行光标覆写。如果命令执行的时间比较长,文件中将有非常多的这种进度信息。怎么让这类中间过程不进入最终的日志文件,只保留最终的文本,就像在终端上最后留下的信息一样?
方案一:由程序自动处理
很多命令会自行检测自己的标准输出是否为终端( isatty(3) ),若不是终端(如文件),程序的行为会自行改变,如禁用颜色高亮、自动静默进度输出等。若程序没有检测,或相应行为达不到要求,看下一个方案。
方案二:使用程序的命令行参数
不少命令(grep,curl,wget等),拥有一些控制输出方式的参数。通过使用命令-h
或--help
或man 命令
等方式,看看有没有关于quiet(-q),verbose(-v),silent(-s),no-progress相关的参数。
-
grep
命令,使用--color=auto/always/never
这样的命令强制控制颜色 -
curl
命令,使用-s
参数禁用stderr进度条输出 -
wget
命令,使用-q
参数禁用进度输出
但也有可能,你使用的命令并没有这样的参数(比如ossutil我没有找到),则继续往后看
方案三:直接关闭标准输出(简单粗暴)
直接 ossutil ... > /dev/null 问题解决
方案四:小工具col登场
我们可以了解一下col
这个自带的命令(重点关注-b
参数,文档页链接 col(1), col )
col过滤掉反向的换行(回车符),只保留正向的换行,所以输出的顺序是正确的。它还将任何空白的字符替换为制表符,这在处理 nroff(1) 和 tbl(1)的输出时非常有用。col从标准输入读取并写入标准输出。
-b, --no-backpaces
假设使用的输出设备不具备(光标)后退功能。在这种情况下,如果要在同一位置显示两个或更多的字符,在输出中只显示最后一个被读取的字符。
这正是我们想要的效果。我们用这个命令过滤之后再试一次:
# 将标准输出重定向到文件
root@localhost:~$ ossutil cp oss://oss-bucket/software/example-desktop-11.0.0-win64-zip-chs.zip . | col -b > down.log
root@localhost:~$ ls -l down.log
-rw-r--r-- 1 root root 169 Jun 21 01:44 down.log # 只有169字符长了
root@localhost:~$ less down.log #查看文件内容,还是有些问题,但短了
Succeed:mTotalsnum: 1,0size:52,088,315,831.uOK num:K1(download61,objects).Progress: 60.750%, Speed: 42627.66KB/ss
average speed 143982000(byte/s)
14.504900(s) elapsed
down.log (END)
发现确实没有了中间过程,但输出的内容有些问题。真实的报告第一行应该是:
Succeed: Total num: 1, size: 2,088,315,831. OK num: 1(download 1 objects).
猜测可能是空格没有得到妥善处理。在很多命令这个col -b
是有效的,可能是这个ossutil的输出比较特殊?
方案五:放弃文件瘦身,能看到正确结果就行
直接将原始命令command > down.log
,得到肥胖的文件,然后
cat down.log
我们就看到了仿佛在屏幕上执行的结果。因为我们的终端会正确处理所有文件中存储的控制字符。
关键词
stdout 标准输出 回车符 进度 日志文件 重定向 col命令