wait命令

wait 命令 #

wait命令用于等待指定的进程完成并返回退出状态。它主要在shell脚本中使用,用于同步进程执行,特别是在处理后台任务时。

语法 #

wait [选项] [ID...]

常用选项 #

选项 描述
-n 等待任何作业完成并返回其退出状态(Bash 4.3+)
-f 等待指定的PID完成,即使它不是当前shell的子进程(Bash 5.1+)
-p 变量 将最后等待的进程的PID存储在指定的变量中(Bash 4.3+)

参数 #

  • ID:要等待的进程ID或作业规范(如%1%2等)。如果未指定,则等待所有当前活动的子进程。

退出状态 #

  • 如果未指定ID,返回0
  • 如果所有指定的ID都成功完成,返回0
  • 如果任何指定的ID未成功完成,返回最后一个失败的命令的退出状态
  • 如果指定的ID不存在或无效,返回127
  • 如果wait本身被信号中断,返回128+信号编号

常见用法 #

1. 等待所有后台进程完成 #

#!/bin/bash
command1 &
command2 &
command3 &
wait
echo "所有命令已完成"

这将启动三个后台进程,然后等待它们全部完成后继续执行。

2. 等待特定进程完成 #

#!/bin/bash
long_running_command &
pid=$!
echo "进程 $pid 正在后台运行"
wait $pid
echo "进程 $pid 已完成,退出状态: $?"

这将启动一个后台进程,记录其PID,然后等待它完成。

3. 等待任何作业完成 #

#!/bin/bash
command1 &
command2 &
command3 &
wait -n
echo "一个命令已完成,退出状态: $?"

这将等待任何一个后台进程完成,然后继续执行。

4. 存储完成进程的PID #

#!/bin/bash
command1 &
command2 &
wait -n -p finished_pid
echo "进程 $finished_pid 已完成,退出状态: $?"

这将等待任何一个后台进程完成,并将其PID存储在变量finished_pid中。

5. 等待作业完成 #

#!/bin/bash
command1 &
command2 &
jobs
wait %1  # 等待第一个作业完成
echo "第一个作业已完成"

这将等待通过作业控制编号指定的作业完成。

实用示例 #

1. 并行处理文件 #

#!/bin/bash
for file in *.txt; do
    process_file "$file" &
done
wait
echo "所有文件处理完成"

这将并行处理所有文本文件,然后等待所有处理完成。

2. 超时处理 #

#!/bin/bash
command &
pid=$!
( sleep 10; kill $pid 2>/dev/null ) &
watchdog_pid=$!
wait $pid
kill $watchdog_pid 2>/dev/null  # 如果命令在超时前完成,取消watchdog

这将启动一个命令,如果它在10秒内没有完成,则终止它。

3. 收集多个进程的退出状态 #

#!/bin/bash
command1 &
pid1=$!
command2 &
pid2=$!
command3 &
pid3=$!

wait $pid1
status1=$?
wait $pid2
status2=$?
wait $pid3
status3=$?

echo "命令1退出状态: $status1"
echo "命令2退出状态: $status2"
echo "命令3退出状态: $status3"

if [ $status1 -eq 0 ] && [ $status2 -eq 0 ] && [ $status3 -eq 0 ]; then
    echo "所有命令成功完成"
else
    echo "一个或多个命令失败"
    exit 1
fi

这将启动三个后台进程,等待它们完成,并收集它们的退出状态。

4. 实现简单的并行限制 #

#!/bin/bash
max_parallel=4
count=0

for job in job1 job2 job3 job4 job5 job6 job7 job8; do
    # 如果达到最大并行数,等待任何一个作业完成
    if [ $count -ge $max_parallel ]; then
        wait -n
        count=$((count - 1))
    fi
    
    # 启动新作业
    echo "启动 $job"
    do_job $job &
    count=$((count + 1))
done

# 等待所有剩余作业完成
wait
echo "所有作业完成"

这将限制同时运行的作业数量,实现简单的并行控制。

5. 处理信号和清理 #

#!/bin/bash
# 设置清理函数
cleanup() {
    echo "接收到信号,清理并退出"
    kill $(jobs -p) 2>/dev/null
    exit 1
}

# 捕获信号
trap cleanup SIGINT SIGTERM

# 启动后台进程
command1 &
command2 &
command3 &

# 等待所有进程完成
wait

echo "所有命令完成"

这将启动多个后台进程,并在收到中断信号时清理它们。

6. 实现简单的进度报告 #

#!/bin/bash
total_jobs=10
completed=0

for i in $(seq 1 $total_jobs); do
    (
        echo "作业 $i 开始"
        sleep $((RANDOM % 5 + 1))  # 模拟工作
        echo "作业 $i 完成"
    ) &
    pids[$i]=$!
done

# 等待所有作业完成并报告进度
while [ $completed -lt $total_jobs ]; do
    wait -n
    completed=$((completed + 1))
    echo "进度: $completed/$total_jobs ($(( completed * 100 / total_jobs ))%)"
done

echo "所有作业完成"

这将启动多个后台作业,并在每个作业完成时报告进度。

7. 并行执行命令并收集输出 #

#!/bin/bash
# 创建临时目录存储输出
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT

# 启动后台命令并重定向输出
command1 > "$tmp_dir/output1" 2> "$tmp_dir/error1" &
pid1=$!
command2 > "$tmp_dir/output2" 2> "$tmp_dir/error2" &
pid2=$!

# 等待命令完成
wait $pid1 $pid2

# 处理输出
echo "命令1输出:"
cat "$tmp_dir/output1"
echo "命令1错误:"
cat "$tmp_dir/error1"
echo "命令2输出:"
cat "$tmp_dir/output2"
echo "命令2错误:"
cat "$tmp_dir/error2"

这将并行执行命令,收集它们的标准输出和错误输出,然后在所有命令完成后处理这些输出。

8. 实现简单的作业队列 #

#!/bin/bash
# 创建命名管道
pipe=$(mktemp -u)
mkfifo $pipe
exec 3<>$pipe
rm $pipe

# 设置最大并行作业数
max_jobs=4
for ((i=1; i<=max_jobs; i++)); do
    echo >&3
done

# 处理作业队列
for job in job1 job2 job3 job4 job5 job6 job7 job8 job9 job10; do
    # 等待一个槽位可用
    read -u 3
    
    # 在后台处理作业并在完成时释放槽位
    (
        echo "处理 $job"
        sleep $((RANDOM % 5 + 1))  # 模拟工作
        echo "完成 $job"
        echo >&3  # 释放槽位
    ) &
done

# 等待所有作业完成
wait
exec 3>&-  # 关闭管道

echo "所有作业完成"

这实现了一个简单的作业队列,限制同时运行的作业数量。

与其他命令的比较 #

命令 功能 区别
wait 等待进程完成 内置命令,适用于shell脚本中的进程同步
sleep 暂停执行指定时间 基于时间而不是进程状态
fg 将后台作业移到前台 交互式命令,不适用于脚本
jobs 列出活动作业 仅显示信息,不等待完成
kill -0 检查进程是否存在 不等待进程完成
timeout 在指定时间后终止命令 基于时间限制,而不是等待完成

提示 #

  • wait命令只能等待当前shell的子进程,除非使用-f选项(Bash 5.1+)
  • 在脚本中使用wait可以确保所有后台任务完成后再继续执行或退出
  • 使用$!变量可以获取最近启动的后台进程的PID
  • 在处理大量并行任务时,考虑使用作业队列或限制同时运行的任务数量
  • 结合trap命令可以确保在脚本退出时清理所有后台进程
  • 使用wait -n可以实现"完成一个处理一个"的工作流
  • 在循环中启动后台进程时,注意变量作用域,可能需要使用子shell或局部变量
  • 对于需要收集输出的并行任务,考虑将输出重定向到文件
  • 在处理信号时,确保在trap处理程序中调用wait以避免留下僵尸进程
  • 使用jobs -p可以获取所有当前后台作业的PID列表