如何处理含 的命令输出(如rsync进度)以实现逐行处理?
解决rsync进度输出(带
\r)的逐行读取问题 我太懂这个痛点了——rsync用\r(回车符)刷新进度行,导致管道后的while read只能拿到最后一行输出,根本没法实时捕获进度来更新dunstify通知。这事儿我之前折腾过好一阵,给你几个实用的解决方案:
为什么会出现这个问题?
默认情况下,read命令是按\n(换行符)分割输入行的。而rsync的--info=progress2用\r把光标拉回行首覆盖内容,不是真正的换行。所以所有进度更新会被read当成同一行,直到rsync结束输出\n时,才会一次性把所有内容传给read,自然只能拿到最后一次的进度。
解决方案1:用tr把\r转成\n
最简单直接的办法,就是把所有\r转换成\n,让read能正常识别每一次进度更新:
# 关键是用tr替换回车为换行,结合stdbuf关闭缓冲保证实时性 stdbuf -o0 rsync -au --info=progress2 /path/to/source /path/to/dest | tr '\r' '\n' | while IFS= read -r line; do # 提取百分比(根据rsync输出格式调整正则) progress=$(echo "$line" | grep -Eo '[0-9]+%' | head -n1) if [ -n "$progress" ]; then # 更新dunstify进度通知,-r指定通知ID避免重复弹窗 dunstify -r 9999 -p "Rsync 同步中" "$progress" fi done
stdbuf -o0:强制rsync标准输出无缓冲,确保进度实时传递到管道,不会攒到缓冲区满才输出tr '\r' '\n':把每次进度更新的回车符换成换行符,让read能逐行捕获IFS= read -r:保留行首空格(rsync进度行开头有空格),同时不转义特殊字符
解决方案2:修改read的分隔符为\r
直接让read把\r当成行分隔符,不用转换字符也能逐行读取:
stdbuf -o0 rsync -au --info=progress2 /path/to/source /path/to/dest | while IFS= read -d $'\r' line; do # 这里的line包含每次\r前的进度内容,注意可能末尾有残留字符 progress=$(echo "$line" | grep -Eo '[0-9]+%' | head -n1) if [ -n "$progress" ]; then dunstify -r 9999 -p "Rsync 同步中" "$progress" fi done # 处理最后一行(rsync结束时可能输出\n而非\r) read -r line progress=$(echo "$line" | grep -Eo '[0-9]+%' | head -n1) [ -n "$progress" ] && dunstify -r 9999 "Rsync 完成" "$progress"
这种方法要注意最后一行的额外处理,因为rsync结束时可能输出\n而不是\r。
快速测试验证
你可以用test2的场景验证效果:
# 模拟rsync的\r输出 printf "10%%\r20%%\r30%%\r100%%\n" | tr '\r' '\n' | while read line; do echo "捕获到: $line"; done
运行后会逐行输出捕获到: 10%、捕获到: 20%等,说明能正常逐行处理了。
内容的提问来源于stack exchange,提问作者Bepitic
相关产品推荐
相关产品推荐

