如何在后台运行的进程中退出大型shell脚本?

5

我有两个包含FFmpeg命令的Shell脚本(command1.sh和command2.sh)。其中,command2.sh包含大约500个可以使用';'一个接一个触发的FFmpeg命令,而command1.sh则负责处理音频FFmpeg命令。

主要问题:关闭整个脚本需要太长时间,且它会占用我需要运行另一个脚本所需的CPU资源,因此我没有必要损失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,但我得到了退出代码:1,失败为true,而在后台的命令仍然执行1分钟,直到被我执行的另一个 kill ${pid} 杀死。


你的脚本似乎没有“在后台同时触发”。看起来第一个是在后台运行,而第二个是在前台运行。如果是这样,信号将不会被处理,直到第二个退出。尝试使用 ./command1.sh&./command2.sh&wait - William Pursell
添加了“wait”命令,但仍然出现exitCode=1的问题,并且程序不能立即终止。 - Trofin
1
我在你的代码中没有看到 exitCode。如果你希望得到完整的答案,你需要展示完整的代码。你的陷阱也需要将信号传递给子进程,他们需要迅速响应信号。 - William Pursell
1个回答

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

当然,在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.

我已将此保存到command1.sh中,运行chmod +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

注意:如果你在一个进程已经完成的情况下发送了你的终止命令,你可能会看到错误消息,例如:
./simpleParallel.sh: 1: kill: No such process

看一下 的版本就能避免这个 bug。

从另一个终端控制台中跟进

在运行 ./simpleParallel.sh 之前,你可以运行不带参数的 tty 命令:
tty
/dev/pts/2

然后在一个新的空闲窗口中,您可以运行:
watch ps --tty pts/2

在使用另一个窗口运行kill命令时。

带有一些的,现在:

在最近的bash中,有很多特殊功能。

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

我的脚本可以变成:

#!/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

如果你早期“杀死”它们:
./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

如果你之后“杀”了他们:
./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

没有关于尝试杀死不存在的进程ID的错误消息。

@Trofin 抱歉回答晚了... - F. Hauri - Give Up GitHub
谢谢您的回答!我已经使用了trap,并按照您在此评论中展示的方式进行了修复。 - Trofin
@Trofin 这里有一个新的 [tag:bash] 版本,带有更多的 功能 - F. Hauri - Give Up GitHub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接