You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获STDOUT、STDERR与进程PID且避免产生僵尸进程

Terraform包装脚本遗留僵尸进程问题排查与解决

问题背景

我正在为Terraform二进制文件编写包装脚本,作为企业解决方案的一部分,需满足以下需求:

  • 文件日志捕获:分别捕获STDOUT、STDERR用于后续分析
  • 实时日志捕获:在Jenkins任务中运行时,保持STDOUT和STDERR的实时输出与分离
  • PID捕获:让Terraform进程后台运行,后续将添加SIGTERM处理逻辑

当前脚本核心代码如下:

#!/bin/bash
...
terraform "$@" > >(tee "${STDOUT_LOG}") 2> >(tee "${STDERR_LOG}" >&2) & TF_PID="$!"
wait "$TF_PID"
EXIT_CODE="$?"
...
wait
exit "$EXIT_CODE"

在容器环境中调用该脚本数百次后,发现系统中遗留了大量执行tee命令的shell僵尸进程。即使在脚本退出前添加了通用wait命令,也无法让shell等待这些子进程完成并收割它们。

补充进程状态信息

执行ps aux --forest的输出:

> ps aux --forest
  11295 ?        S      0:00      |       \_ /bin/bash /home/jenkins/workspace/build-1629@tmp/terraform.sh init
  11376 ?        Sl     0:01      |       |   \_ terraform init
  11377 ?        S      0:00      |       |       \_ /bin/bash /home/jenkins/workspace/build-1629@tmp/terraform.sh init
  11379 ?        S      0:00      |       |       |   \_ /usr/bin/coreutils --coreutils-prog-shebang=tee /usr/bin/tee -a /home/jenkins/workspace/build-1629/src/vnet-01/stdout.log
  11378 ?        S      0:00      |       |       \_ /bin/bash /home/jenkins/workspace/build-1629@tmp/terraform.sh init
  11380 ?        S      0:00      |       |           \_ /usr/bin/coreutils --coreutils-prog-shebang=tee /usr/bin/tee -a /home/jenkins/workspace/build-1629/src/vnet-01/stderr.log

数分钟后的ps aux输出:

> ps aux
...
  11377 ?        Z      0:00 [terraform.sh] <defunct>
  11378 ?        Z      0:00 [terraform.sh] <defunct>
...

问题根源

问题出在bash进程替换的子shell管理机制上:

  • >(tee ...)这种进程替换语法会创建独立的子shell来执行tee命令,这些子shell是包装脚本的直接子进程,和Terraform进程属于同级关系,而非Terraform的子进程。
  • 原脚本仅wait "$TF_PID"等待Terraform进程退出,并未等待这些执行tee的子shell。
  • 额外添加的wait命令默认仅等待脚本的直接子进程,但bash对进程替换创建的子shell会特殊处理——当父脚本准备退出时,不会主动等待这些子shell完成,导致它们的退出状态无人收割,最终变成僵尸进程(Z状态)。

解决方案

方法1:用命名管道替代进程替换(兼容所有bash版本)

通过手动创建命名管道,显式管理tee进程的生命周期,避免进程替换的子shell问题:

#!/bin/bash

# 创建临时命名管道
mkfifo stdout_pipe stderr_pipe

# 启动tee进程并记录PID,后台运行
tee "${STDOUT_LOG}" < stdout_pipe & STDOUT_TEE_PID=$!
tee "${STDERR_LOG}" < stderr_pipe >&2 & STDERR_TEE_PID=$!

# 运行Terraform,将输出重定向到管道
terraform "$@" > stdout_pipe 2> stderr_pipe & TF_PID=$!

# 等待Terraform进程退出
wait "$TF_PID"
EXIT_CODE=$?

# 删除管道,触发tee进程自动退出(管道关闭后tee会终止)
rm stdout_pipe stderr_pipe

# 等待两个tee进程完全退出
wait "$STDOUT_TEE_PID" "$STDERR_TEE_PID"

exit "$EXIT_CODE"

方法2:利用进程组等待(bash 4.3+适用)

启用作业控制,让Terraform和进程替换的子shell归属同一个进程组,然后等待整个进程组:

#!/bin/bash

# 启用作业控制,使后台进程形成独立进程组
set -m

# 运行Terraform,进程替换的子shell会与它同属一个进程组
terraform "$@" > >(tee "${STDOUT_LOG}") 2> >(tee "${STDERR_LOG}" >&2) & TF_PID=$!

# 等待整个进程组退出,而非单个PID
wait -p "$TF_PID"
EXIT_CODE=$?

# 关闭作业控制
set +m

exit "$EXIT_CODE"

如果你的bash版本低于4.3,可替换为以下逻辑:

#!/bin/bash
set -m

terraform "$@" > >(tee "${STDOUT_LOG}") 2> >(tee "${STDERR_LOG}" >&2) & TF_PID=$!

# 等待Terraform进程退出
wait "$TF_PID"
EXIT_CODE=$?

# 向整个进程组发送终止信号,确保子shell退出
kill -TERM -- -$TF_PID 2>/dev/null
# 等待所有子进程退出
wait

set +m
exit "$EXIT_CODE"

内容的提问来源于stack exchange,提问作者Bernard Halas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:26:04