Contents

杀死所有超过一定年龄的过程

1. 概述

在某些情况下,我们可能想要杀死已经运行了很长时间的进程。例如,我们可能期望一个进程在一定时间内完成它的任务。但是,如果该过程花费的时间比预期的要长,则可能是因为发生了错误。

在本教程中,我们将学习如何杀死超过某个时间的进程。为此,我们将学习三个替代方案。一个涉及使用killall命令,另一个涉及使用pkill命令,最后一个涉及使用ps命令。

2. 使用killall

杀死超过特定年龄的进程的一种简单方法是使用killall 命令。该命令将我们要杀死的进程的名称作为参数。

要仅杀死超过某个年龄的进程,我们必须添加-o*参数并指定所需的年龄。* -o参数将时间量作为参数,时间量可以采用不同的单位。时间单位可以是 s、m、h、d、w、M 或 y,表示秒、分钟、小时、天、周、月和年。

让我们杀死所有名为ping 超过 12 小时的进程:

$ killall -o 12h ping

当我们运行上面的命令时,killall会向所有运行超过 12 小时的ping命令发送SIGTERM信号。如果我们认为进程不会因SIGTERM信号而终止,我们可以使用其他信号 终止进程。例如,如果我们认为进程挂起并且没有响应命令,就会发生这种情况。

因此,我们可以使用参数*-SIGNAL* 更改信号。例如,要使用SIGKILL,我们必须使用参数*-SIGKILL*。让我们使用SIGKILL信号杀死所有名为netcat 的进程超过 1 天:

$ killall -o 1d -SIGKILL netcat

到目前为止,我们已经学会了如何杀死具有特定名称的进程。但是,如果我们添加*-r*参数, killall可以将名称解释为正则表达式。这允许我们杀死具有不同名称的进程。

假设我们有几个 bash 脚本正在运行,我们只想杀死那些运行超过 2 小时的脚本。然后,如果所有这些 bash 脚本都是以 .sh 结尾的文件,我们可以使用*.sh$*正则表达式。让我们尝试一下:

$ killall -o 2h -r '\.sh$'

这样,我们只杀死以*.sh*结尾且超过 2 小时的进程。

3. 使用pkill

我们也可以使用pkill 来终止长时间运行的进程。pkill命令类似于killall命令,但名称过滤器始终是正则表达式。由于pkill命令的行为方式与*pgrep 相同,因此我们可以使用pgrep并使用与pkill*相同的参数。

这样,我们可以在实际执行之前检查哪些进程将被杀死。*当我们使用pkill杀死进程时,我们可以添加-O参数后跟以秒为单位的年龄。通过这样做,我们只杀死比那个年龄更老的进程。**让我们杀死所有名为ping*的进程超过 43200 秒(12 小时):

$ pkill -x -O 43200 ping

请注意,我们使用了*-x参数。此参数告诉pkill杀死与名称“ ping ”完全匹配的所有进程。因此,该示例会杀死所有名为ping的进程,而不会杀死具有相似名称的其他进程,例如arpingping6*。与killall类似,pkill默认发送SIGTERM信号。但是,我们可以使用参数*-SIGNAL更改pkill发送的信号。**这次让我们使用SIGKILL*信号重复前面的示例:**

$ pkill -SIGKILL -x -O 43200 ping

最后,我们也可以使用正则表达式。在这种情况下,我们不必像使用killall那样添加任何额外的参数。让我们杀死所有以*.sh*结尾的超过 3600 秒(1 小时)的进程:

$ pkill -O 3600 '\.sh$'

请注意,这次我们不使用*-x参数,因为在这种情况下,pkill将匹配确切的模式“.sh”* ,而不是包含以*“.sh”*结尾的名称。

4. 使用ps

最后,我们还可以使用ps 命令结合awkkill 命令来杀死超过某个年龄的进程。在这种情况下,我们将首先使用ps列出进程,并使用awk根据它们的年龄和名称过滤它们。最后,我们将使用kill来终止它们。

4.1. 在线进程

使用ps命令,我们可以选择要查看的列。首先,我们使用*-e参数选择所有进程。然后我们添加-o参数,后跟所需的列。**我们可以使用etimes列来获取自进程开始以来的时间(以秒为单位)。**让我们尝试选择列pid*、etimescmd,因此我们可以看到 PID、以秒为单位的年龄以及每个进程的命令:

$ ps -eo pid,etimes,cmd
  PID ELAPSED CMD
    1  148168 init [3]
...
 1703  148869 sshd: /usr/sbin/sshd 
 5223    4751 screen
 5239    4750 -/bin/bash
 6135    1942 cmus
23817   94840 cmus
25405  120136 /usr/lib64/firefox/firefox-bin
27613       0 ps -eo pid,etimes,cmd
29739  119782 cmus
...

当我们只想查看某些进程在不杀死它们的情况下运行了多长时间时,这也很有用。现在,我们可以将ps输出通过管道传输到awk 以过滤超过某个年龄的进程。我们必须比较第二列,看看它是否大于期望的年龄。

让我们使用awk仅列出超过 7200 秒(2 小时)的进程:

$ ps -eo pid,etimes,cmd | awk '$2 > 7200'
    1  148168 init [3]
...
 1703  148880 sshd: /usr/sbin/sshd 
23817   94851 cmus
25405  120147 /usr/lib64/firefox/firefox-bin
29739  119793 cmus
...

如我们所见,我们列出了所有运行超过 2 小时的进程。但是,我们不想杀死所有人。例如,我们不应该杀死像initsystemd这样的进程。

因此,我们可以向awk添加更多过滤器,以仅选择我们想要杀死的进程。我们可以通过比较第三列来做到这一点。让我们选择所有超过 7200 秒的名为“ cmus ”的进程:

$ ps -eo pid,etimes,cmd | awk '$2 > 7200 && $3 == "cmus"'
23817   94879 cmus
29739  119710 cmus

最后,我们可以编写一个名为filter_processes_older_than的函数 ,并通过参数接收年龄和名称。我们开始做吧:

$ filter_processes_older_than() {
    ps -eo pid,etimes,cmd | awk '$2 > '$1' && $3 == "'$2'"'
}
$ filter_processes_older_than 7200 cmus
23817   94883 cmus
29739  119724 cmus

4.2. 杀死进程

现在我们有了要杀死的进程列表,我们可以将xargskill一起使用。

首先,我们要得到每个进程的PID。因此,我们可以编写一个名为filter_pids_older_than的新函数来仅打印每个进程的 PID。

这样,我们可以保持我们的filter_processes_older_than函数完好无损,以验证哪些进程将被杀死。让我们编写filter_pids_older_than函数来仅打印第一列中的 PID:

$ filter_pids_older_than() {
    filter_processes_older_than "$1" "$2" | awk '{print $1}'
}
$ filter_pids_older_than 7200 cmus
23817
29739

现在,为了终止这些进程,我们将该输出通过管道传输到xargs kill

$ filter_pids_older_than 7200 cmus | xargs kill

这样,我们就杀死了所有运行超过 7200 秒的cmus进程。我们可以通过将*-SIGNAL参数添加到kill来更改用于终止进程的信号。因此,让我们编写一个名为kill_pids_older_than*的新函数,并使用第三个可选参数来指定信号:

$ kill_pids_older_than() {
    filter_pids_older_than "$1" "$2" | xargs kill -${3:-SIGTERM} 
}

我们可以注意到我们使用了 ${3:-SIGTERM}。这将仅在定义第三个参数时使用。如果未定义,则使用默认值SIGTERM

最后,我们可以一起测试这一切。

让我们首先验证哪些进程会被filter_processes_older_than函数杀死,然后我们使用SIGKILL信号通过kill_pids_older_than函数杀死它们:

$ filter_processes_older_than 7200 cmus
23817   94955 cmus
29739  119796 cmus
$ kill_pids_older_than 7200 cmus SIGKILL