使用后台进程捕获并退出大 shell 脚本

Trap and exit big shell script with background processes

我有 2 个 shell 包含 ffmpeg 命令的脚本(command1.sh 和 command2.sh)。 command2.sh 有大约 500 个 ffmpeg 命令,它们通过“;”一个接一个地触发,而 command1.sh 处理音频 ffmpeg 命令。

主要问题: 杀死整个脚本需要太多时间,它会占用我执行另一个脚本所需的 CPU 1-2 分钟的能量所以我白白失去了 CPU 能量,因为我无法立即杀死它。

代码: 我有 init.sh 包含:

trap 'print TERM received;exit' 15
chmod +x command1.sh;
chmod +x command2.sh;
./command1.sh & ./command2.sh

所以它在后台触发。

然后我执行 pkill init.sh 我抓住了 shell trapexit shell 脚本,但我得到 exitCode: 1, failed: true 并且后台命令仍然执行 1 分钟,直到它们被我在之后执行的另一个 kill ${pid} 杀死pkill.

终止并行子任务

一些备注:

  • chmod +x在这里没用。您完全可以 运行 sh command1.sh & 代替。
  • 你必须独立终止所有子任务
  • 由于这个问题被标记为,我的回答没有使用bashisms。这里的所有脚本都在 .
  • 下测试

类似于:

#!/bin/sh

for cmd in ./command1.sh ./command2.sh;do
    exec $cmd &
    PIDS="$PIDS $!"
done

trap "kill $PIDS;exit" 15
wait

当然,在 for cmd in;do 之间,您可以放置​​任意数量的 commandXX.sh(只要您将行长度保持在安装的 OS 支持的最大值])

测试脚本:

这是一个快速 测试脚本,它在 2.0 到 12.99 秒之间随机休眠,然后在退出前打印 done.:

#!/bin/bash

declare -i toSleep
case  in '' | *[!0-9]* ) toSleep='RANDOM%10+2' ;; * ) toSleep= ;; esac

exec {dummy}<> <(:)
read -t $toSleep.$RANDOM -u $dummy _
echo done.

我已将其保存到 command1.shchmod +x .. 并链接到 command2.sh...

更便携的包装器:

#!/bin/sh

for cmd in "$@";do
    exec $cmd &
    PIDS="$PIDS $!"
done

printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
    $$ $(echo $PIDS|wc -w) "$PIDS"

trap "kill $PIDS;echo 'Process $$ killed.';exit" 15
wait

echo "Process $$ running $@ ended normally"

您可以将此 脚本保存到名为 simpleParallel.sh 的文件中,作为示例,然后:

chmod +x simpleParallel.sh

./simpleParallel.sh ./command1.sh ./command2.sh 
You have to: kill -TERM 741297
to end 2 tasks:  741298 741299

那么如果你 kill -TERM 741297 来自其他地方,

Process 741297 killed.

但如果你不这样做,你可能会读到类似这样的内容:

./simpleParallel.sh ./command1.sh ./command2.sh 
You have to: kill -TERM 741968
to end 2 tasks:  741969 741970
done.
done.
Process 741968 running ./command1.sh ./command2.sh ended normally

注意:如果您在一个进程已完成时发送 kill 命令,您可能会看到如下错误消息:

./simpleParallel.sh: 1: kill: No such process

参见 版本可以避免这个错误。

从另一个 终端控制台

在 运行ning ./simpleParallel.sh 之前,您可以 运行 tty 不带参数:

tty
/dev/pts/2

然后在新的免费 window 中,您可以 运行:

watch ps --tty pts/2

同时使用另一个 window 再次执行 运行 kill 命令。

有一些 bash 主义 ,现在:

最近bash下,有很多特色

  • 您可以使用数组来存储后台任务的 PID。
  • 从 5.0 版本开始,有一个 $EPOCHREALTIME 变量,它扩展为自 Unix 纪元以来的秒数,粒度为微秒。
  • 从 5.0 版开始,关联数组允许下标包含空格。
  • 从5.1版本开始,wait有一个新的[-p VARNAME]选项,它存储wait -n返回的PIDwait 没有参数。

我的脚本可以变成:

#!/bin/bash
declare -A PIDS ENDED
started=$EPOCHREALTIME
for cmd; do
    exec "$cmd" &
    PIDS["$cmd"]=$!
    CMDS[$!]="$cmd"
done
printf "You have to: kill -TERM %d\nto end %d tasks: %s\n" \
    $$ ${#PIDS[@]} "${PIDS[*]}"

trapExit(){
    kill "${PIDS[@]}"
    printf 'Process %s + %d task killed: %s\n' $$ ${#PIDS[@]} "${!PIDS[*]}"
    showDone
    exit 1
}
trap trapExit 15
showDone() {
    for cmd in "${!ENDED[@]}";do
        read -r elap < <(bc -l <<<"${ENDED["$cmd"]}-$started")
        printf "Elapsed: %.4f sec for %s\n" "$elap" "$cmd"
    done
}
while ((${#PIDS[@]}));do  if wait -n -p pid ;then
        printf "Process %d done (%s).\n" "$pid" "${CMDS[pid]}"
        ENDED["${CMDS[pid]}"]=$EPOCHREALTIME
        unset PIDS["${CMDS[pid]}"] CMDS[pid]
fi; done
echo "Process $$ running $* ended normally"
showDone

输出可能如下所示:

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1309816
to end 2 tasks: 1309817 1309818
done.
Process 1309817 done (./command1.sh).
done.
Process 1309818 done (./command2.sh).
Process 1309816 running ./command1.sh ./command2.sh ended normally
Elapsed: 3.1644 sec for ./command1.sh
Elapsed: 4.2488 sec for ./command2.sh

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1310031
to end 2 tasks: 1310032 1310033
done.
Process 1310033 done (./command2.sh).
done.
Process 1310032 done (./command1.sh).
Process 1310031 running ./command1.sh ./command2.sh ended normally
Elapsed: 9.1868 sec for ./command1.sh
Elapsed: 3.2310 sec for ./command2.sh

如果你 kill 他们早:

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294577
to end 2 tasks: 1294578 1294579
Process 1294577 + 2 task killed: ./command1.sh ./command2.sh

如果你 kill 他们以后:

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294958
to end 2 tasks: 1294959 1294960
done.
Process 1294959 done (./command1.sh).
Process 1294958 + 1 task killed: ./command2.sh
Elapsed: 3.1779 sec for ./command1.sh

./simpleParallel.bash ./command{1,2}.sh
You have to: kill -TERM 1294971
to end 2 tasks: 1294972 1294973
done.
Process 1294973 done (./command2.sh).
Process 1294971 + 1 task killed: ./command1.sh
Elapsed: 6.9344 sec for ./command2.sh

并且没有关于试图杀死不存在的 pid 的错误消息。