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

Tro*_*fin 5 linux shell scripting

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

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

代码: 我有init.sh其中包含:

trap 'print TERM received;exit' 15
chmod +x command1.sh;
chmod +x command2.sh;
./command1.sh & ./command2.sh
Run Code Online (Sandbox Code Playgroud)

所以它会在后台触发。

然后我执行pkill init.sh并捕获 shell陷阱退出shell 脚本,但我得到 exitCode: 1, failed: true ,并且后台的命令仍执行 1 分钟,直到它们被另一个kill ${pid 杀死我在 pkill 之后执行。

F. *_*uri 3

杀死并行子任务

一些备注:

  • 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
Run Code Online (Sandbox Code Playgroud)

当然,在for cmd in和 之间;do,您可以输入任意数量commandXX.sh(只要您将行长度保持在所安装操作系统支持的最大长度内)

测试脚本:

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

#!/bin/bash

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

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

我已将其保存到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"
Run Code Online (Sandbox Code Playgroud)

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

chmod +x simpleParallel.sh

./simpleParallel.sh ./command1.sh ./command2.sh 
You have to: kill -TERM 741297
to end 2 tasks:  741298 741299
Run Code Online (Sandbox Code Playgroud)

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

Process 741297 killed.
Run Code Online (Sandbox Code Playgroud)

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

./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
Run Code Online (Sandbox Code Playgroud)

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

./simpleParallel.sh: 1: kill: No such process
Run Code Online (Sandbox Code Playgroud)

查看版本可以避免这个错误。

从另一个终端控制台遵循此操作

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

tty
/dev/pts/2
Run Code Online (Sandbox Code Playgroud)

然后在一个新的免费窗口中,您可以运行:

watch ps --tty pts/2
Run Code Online (Sandbox Code Playgroud)

同时再次使用另一个窗口运行kill命令。

与一些bashism,现在:

最近的bash下,有很多特色。

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

我的脚本可能会变成:

#!/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
Run Code Online (Sandbox Code Playgroud)

输出可能如下所示:

./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
Run Code Online (Sandbox Code Playgroud)

或者

./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
Run Code Online (Sandbox Code Playgroud)

如果你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
Run Code Online (Sandbox Code Playgroud)

如果你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
Run Code Online (Sandbox Code Playgroud)

或者

./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
Run Code Online (Sandbox Code Playgroud)

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