通过Bash函数管道时ping退出日志不显示的问题排查
问题描述
我需要编写一个可并行运行多命令的脚本,按下CTRL+C时能终止所有运行中的命令。但遇到异常:将ping命令的输出通过自定义Bash函数log做管道处理时,无法打印ping退出时的统计日志(如rtt min/avg/max/mdev = 6.808/6.855/6.906/0.040 ms);而直接用sed命令做相同格式处理时,该统计日志能正常显示。
测试脚本
完整测试脚本
#!/bin/bash trap terminate SIGINT terminate(){ echo "terminating started" pkill -SIGINT -P $$ echo "terminating finished" exit } log () { sed -e "s/^/[$1]: /" } ping 1.1.1.1 |& log "first_log" & ping bbc.com |& sed -e "s/^/[bbc_log]: /"& wait
简化测试脚本
#!/usr/bin/env bash log() { sed 's/^/[log]: /' } if [[ $1 == 1 ]]; then ping 1.1.1.1 2>&1 | sed 's/^/[log]: /' & else ping 1.1.1.1 2>&1 | log & fi wait
运行./tst.sh 1(直接调用sed)能显示ping最终统计日志,运行./tst.sh 2(通过log函数)则无法显示,两次均在相同时机按下CTRL+C终止。
原因分析
当用Bash函数作为管道环节时,Bash会创建子shell执行该函数,子shell会成为独立进程组。按下CTRL+C发送SIGINT信号时:
- 直接调用
sed场景:ping与sed同属一个进程组,SIGINT会同时送达两者。ping收到信号后先输出统计日志再退出,sed处理完所有输出后退出,因此统计日志能正常显示。 - 通过
log函数场景:ping在一个进程组,执行log的子shell及内部sed在另一个进程组。pkill -SIGINT -P $$发送信号时,子shell可能先收到SIGINT并退出,导致sed无法读取ping最后输出的统计日志,最终这条日志丢失。
此外,子shell提前退出会导致管道断开,ping的标准输出/错误可能被系统丢弃,也会造成统计日志无法传递到终端。
解决方法
方法1:用exec让sed替换子shell进程
修改log函数,用exec让sed直接接管执行函数的子shell进程,这样sed与ping会属于同一个进程组:
log () { exec sed -e "s/^/[$1]: /" }
此方法最直接有效,exec会消除额外的子shell进程,确保ping输出的统计日志能被sed处理并输出。
方法2:调整信号处理逻辑,等待ping输出统计日志
修改trap中的终止函数,先触发ping生成统计日志,短暂等待后再终止后续进程:
terminate(){ echo "terminating started" # 发送SIGINT让ping生成统计日志 pkill -SIGINT -P $$ # 短暂等待,确保ping的输出被处理 sleep 0.1 # 终止剩余子进程 pkill -P $$ echo "terminating finished" exit }
这种方式通过延迟终止后续进程,给ping足够时间输出统计日志,让管道下游的进程有机会处理。
方法3:避免子shell执行函数逻辑
将函数逻辑直接内联,或用eval避免创建子shell:
log() { eval "sed 's/^/[$1]: /'" }
此方式让sed直接在ping的进程组中执行,确保信号传递和输出处理的一致性。
内容的提问来源于stack exchange,提问作者dewijones92
相关产品推荐
相关产品推荐

