Contents

在Bash中给定超时后杀死子进程

1. 概述

在本教程中,我们将介绍在 Bash 中特定超时后杀死子进程的各种方法。许多用例都需要此功能,例如重新启动在一段时间后崩溃的行为不端的进程。

2. Bash 中的子进程

正如我们已经知道的那样,Bash 有一些不需要额外进程的内置命令,例如echoprintf 。 但是,在 Bash 中启动的所有外部进程,例如curl,都是 shell 的子进程。 子进程从父 shell 继承所有环境变量:

$ export PARENT_VARIABLE="Hello"
$ bash -c 'echo Parent variable: $PARENT_VARIABLE'
Parent variable: Hello

在这里,我们将 Bash 作为子进程启动并访问父 shell 的环境变量。

*此外,我们可以使用Bash 中的&*运算符生成多个子进程。这对于运行守护进程 或并行化任务很有用。我们可以从$! 环境变量:

$ sleep 5 &
$ echo $! # Check PID
18695

但是,如果我们只是在 Bash 脚本中生成子进程,则脚本可能会在子进程完成之前退出。我们可以使用wait 命令等待子进程退出:

$ sleep 5 &
$ wait; echo Slept
Slept
[1]+  Done                       sleep 5

3. 超时后杀死一个子进程

由于各种原因,我们可能希望在给定超时后终止子进程,例如重新启动行为不端的程序。让我们介绍一下我们可以为此目的使用的各种命令。

3.1. 使用timeout命令

要在给定超时后终止子进程,我们可以使用timeout 命令。它运行传递给它的命令,并在给定的 timeout 之后使用SIGTERM 信号终止它。如果我们想向进程发送一个不同的信号,比如SIGINT,我们可以使用*–signal*标志。

让我们编写一个每秒打印一条消息的脚本作为示例:

$ cat ./script 
counter=0
echo "Starting sleep"
while sleep 1; do
    : $((counter+=1)) # Increment counter
    echo "Slept $counter time(s)"
done
$ timeout 5 ./script 
Starting sleep
Slept 1 time(s)
Slept 2 time(s)
Slept 3 time(s)
Slept 4 time(s)
Terminated

我们可以看到进程在打印 5 行后终止,因为我们传递了 5 秒的超时值。

3.2. 使用纯 Bash 功能

timeout命令是 Bash 中未内置的外部命令。我们可以使用procfs文件系统创建一个纯 Bash超时实现:

# First argument: PID
# Second argument: Timeout
my_timeout() {
    # Get process start time (Field 22) to check for PID recycling
    start_time="$(cut -d ' ' -f 22 /proc/$1/stat)"
    sleep "$2"
    # Make sure that the PID was not reused by another process
    # that started at a later time
    if [ "$(cut -d ' ' -f 22 /proc/$1/stat)" = "$start_time" ]; then
        # Kill process with SIGTERM
        kill -15 "$1"
    fi
}

首先,我们使用cut 命令从*/proc/PID/stat的第 22 个字段中获取进程的开始时间。然后,我们sleep指定的超时时间并使用SIGTERM信号kill*进程。此外,**我们确保进程的开始时间在终止之前没有改变。这可以防止我们杀死一个重用原始进程 PID 的新进程。**如果原始进程在指定的超时之前意外死亡并且新进程重用其 PID,则可能会发生这种情况。

让我们用 2 秒的超时时间来测试它:

$ ./script & my_timeout $!2
Starting sleep
Slept 1 time(s)
$