如何在bash/zsh管道中获取函数返回值并实现tee输出拆分
核心问题说明
你当前的写法存在两个核心错误:
- Shell的参数展开发生在命令执行前,写在curl路径中的
$?会在整个管道启动前就完成替换,完全取不到运行时main的返回值 - 默认管道的
$?仅返回最后一个命令的退出状态,你开启的pipefail仅会让$?返回管道中第一个非0的退出状态,没法精准拿到第一个命令main的明确返回值
Bash 解决方案(兼容绝大多数场景)
最稳妥的实现是用临时文件缓存输出,执行完成后通过Bash内置的PIPESTATUS数组取main的返回值:
#!/usr/bin/env -S bash -fuo pipefail hc_ping='https://hc-ping.com/xxx' curl -fsS -m 10 -o /dev/null --retry 5 "$hc_ping/start" main() { echo "do stuff" return 5 } # 创建临时文件缓存输出 temp_log=$(mktemp) # 运行main,输出同时打印到stdout和写入临时文件 main |& tee "$temp_log" # 取管道第一个命令(main)的退出状态,必须在管道执行完成后立刻读取 main_exit_code=${PIPESTATUS[0]} # 取最后10K输出上报到hc-ping tail --bytes=10000 "$temp_log" | curl -fsS -m 10 -o /dev/null --data-binary @- --retry 5 "$hc_ping/$main_exit_code" # 清理临时文件 rm -f "$temp_log"
关键说明:
PIPESTATUS是Bash内置数组,按顺序保存管道中所有命令的退出状态,下标从0开始,执行任何其他命令后该数组会被覆盖,必须在管道执行完成后立刻读取。
Zsh 适配方案
Zsh中对应的内置数组为全小写的pipestatus,且数组下标从1开始,只需修改退出状态读取的一行即可:
# 替换Bash方案中的main_exit_code赋值行 main_exit_code=${pipestatus[1]}
可选无临时文件方案
如果main的输出不会特别大,可以用变量缓存输出,避免磁盘IO:
#!/usr/bin/env -S bash -fuo pipefail hc_ping='https://hc-ping.com/xxx' curl -fsS -m 10 -o /dev/null --retry 5 "$hc_ping/start" main() { echo "do stuff" return 5 } # 缓存输出和退出码,用不可打印的\0分隔 main_output=$(main |& tee /dev/tty; printf '\0%s' "${PIPESTATUS[0]}") # 分离退出码和输出内容 main_exit_code=${main_output##*$'\0'} main_content=${main_output%$'\0'*} # 上报 printf '%s' "$main_content" | tail --bytes=10000 | curl -fsS -m 10 -o /dev/null --data-binary @- --retry 5 "$hc_ping/$main_exit_code"
内容的提问来源于stack exchange,提问作者mpen
相关产品推荐
相关产品推荐

