Linux命令-wait(Bash 内建 ------ 等待后台进程完成)
-
- [🔰 简介](#🔰 简介)
-
- [确认 wait 是内建命令](#确认 wait 是内建命令)
- [📖 语法](#📖 语法)
- [⚙️ 选项](#⚙️ 选项)
- [💡 示例](#💡 示例)
-
- [示例 1:等待特定后台进程](#示例 1:等待特定后台进程)
- [示例 2:等待所有后台进程](#示例 2:等待所有后台进程)
- [示例 3:并行处理模式 ------ 实际脚本](#示例 3:并行处理模式 —— 实际脚本)
- [示例 4:获取后台进程的退出码](#示例 4:获取后台进程的退出码)
- [示例 5:wait -n 等待任意一个完成](#示例 5:wait -n 等待任意一个完成)
- [示例 6:多服务器部署的并行脚本](#示例 6:多服务器部署的并行脚本)
- [示例 7:陷阱与常见错误](#示例 7:陷阱与常见错误)
- [⚠️ 注意](#⚠️ 注意)
-
- [wait vs sleep vs 其他等待方式](#wait vs sleep vs 其他等待方式)
- [在 crontab 和 systemd 中的限制](#在 crontab 和 systemd 中的限制)
- [各 Shell 实现差异](#各 Shell 实现差异)
- [📝 总结](#📝 总结)
- [📚 相关命令](#📚 相关命令)
快速参考 :wait 是 Bash 的内建命令(Shell Builtin),用于暂停当前 Shell 进程的执行,直到指定的后台进程(或所有后台进程)运行完毕。它是 Shell 脚本中实现并行任务控制 和进程同步 的核心机制。wait 会返回所等待进程的退出码,因此也可用于捕获后台任务的执行结果。
🔰 简介
wait 是 POSIX 标准中定义的 Shell 内建命令,存在于所有 POSIX 兼容的 Shell 中(bash、sh、zsh、ksh 等)。与 sleep(基于时间等待)不同,wait 是基于进程状态的等待 ------ 只有当指定进程终止时才返回。
在 Shell 脚本中,wait 最常见的用途是:
- 并行启动多个后台任务,然后等待全部完成
- 等待特定后台进程完成,并获取其退出码
- 实现简单的进程同步屏障
确认 wait 是内建命令
bash
# wait 是 Shell 内建命令,不是外部命令
type wait
# 输出:wait is a shell builtin
# 没有对应的外部可执行文件
which wait
# 输出:/usr/bin/which: no wait in (...)
# 或某些系统显示:
# wait: shell built-in command
# 查看帮助(bash 内建命令的帮助)
help wait
# 输出:
# wait: wait [-fn] [-p var] [id ...]
# Wait for job completion and return exit status.
📖 语法
wait [PID...]
| 参数 | 说明 |
|---|---|
| 无参数 | 等待所有当前 Shell 的后台子进程 |
| PID | 指定一个或多个进程 ID,等待这些特定进程 |
| %n | 等待作业号(job number)为 n 的后台作业 |
⚙️ 选项
| 选项 | 说明 |
|---|---|
-n |
等待任意一个后台进程完成(而非等待全部) |
-f |
等待作业 ID 指定的进程终止(即使作业已完成) |
-p 变量 |
将完成的进程 ID 存入指定的变量(配合 -n 使用) |
注意:
wait的选项行为可能因 Bash 版本不同略有差异。-n和-p在 Bash 4.3+ 才引入。
💡 示例
示例 1:等待特定后台进程
bash
# 启动一个后台任务,记录其 PID
sleep 10 &
pid=$!
echo "后台进程 PID: $pid,正在等待..."
# 输出:后台进程 PID: 12345,正在等待...
# 等待该进程完成
wait $pid
echo "进程 $pid 已完成,退出码: $?"
# 输出(10秒后):进程 12345 已完成,退出码: 0
# 完整演示(多个任务)
sleep 5 &
pid1=$!
sleep 3 &
pid2=$!
echo "任务1 PID: $pid1"
echo "任务2 PID: $pid2"
echo "等待任务1..."
wait $pid1
echo "任务1 完成!"
echo "等待任务2..."
wait $pid2
echo "任务2 完成!"
# 输出:
# 任务1 PID: 12346
# 任务2 PID: 12347
# 等待任务1...
# (3秒后任务2实际上先完成,但脚本在等任务1)
# (5秒后)任务1 完成!
# 等待任务2...
# 任务2 完成!
示例 2:等待所有后台进程
bash
# 并行启动多个任务
echo "启动并行任务..."
for i in {1..5}; do
(
echo "任务 $i 开始..."
sleep $((RANDOM % 5 + 1))
echo "任务 $i 完成!(耗时各不相同)"
) &
done
echo "等待所有任务完成..."
wait
echo "全部任务已完成!"
# 输出:
# 启动并行任务...
# 等待所有任务完成...
# 任务 1 开始...
# 任务 2 开始...
# 任务 3 开始...
# 任务 4 开始...
# 任务 5 开始...
# 任务 3 完成!(耗时各不相同)
# 任务 5 完成!(耗时各不相同)
# 任务 2 完成!(耗时各不相同)
# 任务 1 完成!(耗时各不相同)
# 任务 4 完成!(耗时各不相同)
# 全部任务已完成!
示例 3:并行处理模式 ------ 实际脚本
bash
# 实战脚本:并行处理多个文件
cat > /tmp/parallel_process.sh << 'SCRIPT'
#!/bin/bash
# 并行处理多个日志文件
PROCESS_DIR="/tmp/logs"
RESULT_DIR="/tmp/results"
MAX_PARALLEL=4
mkdir -p "$RESULT_DIR"
echo "开始并行处理 $PROCESS_DIR 中的文件..."
# 遍历文件并并行处理
running=0
for logfile in "$PROCESS_DIR"/*.log; do
filename=$(basename "$logfile")
# 在后台处理每个文件
(
echo "[$(date)] 处理 $filename ..."
# 模拟处理:统计行数、提取错误
lines=$(wc -l < "$logfile")
errors=$(grep -c "ERROR" "$logfile" 2>/dev/null || echo 0)
echo "$filename: 总行数=$lines, 错误数=$errors" > "$RESULT_DIR/${filename}.result"
sleep 1 # 模拟耗时操作
) &
running=$((running + 1))
# 控制并行度:达到上限时等待一个完成
if [ $running -ge $MAX_PARALLEL ]; then
wait -n # 等待任意一个完成
running=$((running - 1))
fi
done
# 等待剩余任务完成
wait
echo "所有文件处理完成!"
# 汇总结果
echo "=== 处理结果汇总 ==="
cat "$RESULT_DIR"/*.result
SCRIPT
chmod +x /tmp/parallel_process.sh
示例 4:获取后台进程的退出码
bash
# wait 返回所等待进程的退出码
# 启动成功和失败的后台任务
(
echo "成功任务执行中..."
sleep 2
exit 0
) &
pid_ok=$!
(
echo "失败任务执行中..."
sleep 1
exit 42
) &
pid_fail=$!
# 等待并获取退出码
wait $pid_ok
echo "成功任务退出码: $?" # 输出:0
wait $pid_fail
echo "失败任务退出码: $?" # 输出:42
# 使用退出码进行错误处理
perform_backup() {
sleep 2
return 0
}
perform_backup &
backup_pid=$!
wait $backup_pid
if [ $? -eq 0 ]; then
echo "备份成功"
else
echo "备份失败!"
fi
# 输出:备份成功
示例 5:wait -n 等待任意一个完成
bash
# Bash 4.3+ 特性:等待任意一个后台任务完成
bash_version=$(bash --version | head -1 | grep -oP '\d+\.\d+' | head -1)
echo "Bash 版本: $bash_version"
if [ "${bash_version%.*}" -ge 4 ] && [ "${bash_version#*.}" -ge 3 ]; then
# 启动多个任务
sleep 5 & pid_slow=$!
sleep 2 & pid_medium=$!
sleep 1 & pid_fast=$!
echo "任务已启动,等待任意一个完成..."
# wait -n 在任意一个任务完成时返回
wait -n
echo "第一个任务完成了!(最快的是 sleep 1)"
# 同时获取完成的 PID(Bash 4.3+)
wait -n -p done_pid
echo "完成的进程 PID: $done_pid"
# 等待剩余任务
wait
echo "所有任务完成"
else
echo "当前 Bash 版本不支持 wait -n(需要 4.3+)"
fi
示例 6:多服务器部署的并行脚本
bash
cat > /tmp/deploy_parallel.sh << 'DEPLOY'
#!/bin/bash
# 并行部署到多台服务器
SERVERS=("web01" "web02" "web03" "web04")
DEPLOY_SCRIPT="/usr/local/bin/deploy.sh"
RESULTS=()
echo "=== 开始并行部署 ==="
START_TIME=$(date +%s)
for server in "${SERVERS[@]}"; do
(
echo "[$server] 开始部署..."
# 实际部署命令(这里用 ssh 模拟)
ssh deploy@$server "$DEPLOY_SCRIPT" 2>&1 | \
while IFS= read -r line; do
echo "[$server] $line"
done
exit_code=$?
echo "[$server] 部署完成,退出码: $exit_code"
exit $exit_code
) &
pids+=($!)
done
# 等待所有部署完成
deploy_success=true
for pid in "${pids[@]}"; do
wait $pid
if [ $? -ne 0 ]; then
deploy_success=false
fi
done
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "=== 部署结束 ==="
echo "总耗时: ${DURATION} 秒"
if $deploy_success; then
echo "结果: 全部部署成功"
else
echo "结果: 部分部署失败,请检查日志"
fi
DEPLOY
chmod +x /tmp/deploy_parallel.sh
示例 7:陷阱与常见错误
bash
# 错误1:等待不存在或已退出的 PID
wait 99999
echo "退出码: $?" # 输出:127(进程不存在)
# 错误2:wait 只能等待当前 Shell 的子进程
# 如果在子 Shell 中启动进程,父 Shell 无法 wait
(
sleep 5 &
child_pid=$!
echo "子 Shell 中的 PID: $child_pid"
)
echo "在父 Shell 中等待:"
wait $child_pid 2>/dev/null
echo "退出码: $?" # 输出:127(无法等待非子进程)
# 错误3:忘记 wait 导致脚本提前退出
# ❌ 错误写法:
for i in {1..10}; do
some_long_task $i & # 后台任务刚启动
done
echo "所有任务已完成?" # ← 脚本立即执行到这里!
# 脚本结束,后台任务被终止
# ✅ 正确写法:
for i in {1..10}; do
some_long_task $i &
done
wait # ← 等待所有后台任务
echo "所有任务确实已完成!"
# 错误4:wait 不等待子 Shell 中的后台进程
(sleep 10 &)
wait # 立即返回!子 Shell 中的后台进程不属于当前 Shell
echo "done" # 立即输出
# 但 sleep 10 仍在运行!
⚠️ 注意
wait vs sleep vs 其他等待方式
bash
# === wait(基于进程状态)===
# 等待特定进程完成,精确可靠
long_task &
task_pid=$!
wait $task_pid
echo "任务确定完成"
# === sleep(基于时间)===
# 盲目等待固定时间,不精确
long_task &
sleep 10
# 问题:任务可能10秒内完成(浪费时间)
# 或10秒后仍未完成(时机不对)
# === 轮询检查(不推荐)===
long_task &
task_pid=$!
while kill -0 $task_pid 2>/dev/null; do
sleep 1
done
# 功能等价于 wait,但轮询消耗 CPU
# 且存在竞态条件(PID 可能被回收重用)
在 crontab 和 systemd 中的限制
bash
# crontab 中的后台任务
# crond 不会自动等待后台任务,任务结束时所有子进程被终止
# 如果需要后台任务在 cron 中存活,使用 nohup:
0 2 * * * nohup /path/to/long_running_script.sh &
# systemd 服务中的后台任务
# Type=forking:systemd 等待主进程 fork 后退出
# Type=oneshot:systemd 等待主进程完全退出
# RemainAfterExit=yes:即使主进程退出,服务也保持 active 状态
各 Shell 实现差异
bash
# Bash 4.3+ :支持 wait -n(等待任意一个)和 wait -p
# Bash 4.2- :仅支持基本的 wait [PID]
# zsh:支持 wait,语法类似 bash
# ksh93:支持 wait,行为略有不同
# dash(Debian /bin/sh):支持 POSIX 标准的 wait,不支持 -n/-p
# 检查当前 Shell
echo $SHELL
# 输出:/bin/bash
# 检查 Bash 版本
echo $BASH_VERSION
# 输出:4.2.46(2)-release
# 可移植的脚本应避免使用 -n 和 -p 选项
📝 总结
wait 是 Shell 编程中实现进程同步 和并行控制的核心内建命令:
- 无参数:等待所有后台进程完成 ------ 最简单的同步方式
- 指定 PID:等待特定进程并获取其退出码 ------ 精确控制
- 配合
$!:pid=$! && wait $pid是捕获后台任务状态的经典模式 -n(Bash 4.3+):等待任意一个完成 ------ 实现并行度控制- 局限 :只能等待当前 Shell 的直接子进程,不能等待孙进程或其他 Shell 的进程
wait + & 的组合是 Linux Shell 并行编程的基石,掌握它们意味着能用 Shell 脚本高效利用多核 CPU。
📚 相关命令
| 命令 | 说明 |
|---|---|
& |
后台运行操作符(将命令放到后台执行) |
$! |
配合使用 获取最后一个后台进程的 PID |
jobs |
列出当前 Shell 的后台作业 |
fg |
将后台作业切换到前台 |
bg |
将暂停的作业放到后台继续运行 |
kill |
向进程发送信号(终止、暂停等) |
sleep |
基于时间的延迟等待 |
nohup |
使进程忽略 HUP 信号,即使终端关闭也继续运行 |
disown |
从 Shell 的作业表中移除作业 |