了解 pgrep 如何确定进程 ID

了解 pgrep 如何确定进程 ID

我目前正在重构一个脚本,如果直接在终端中执行,该脚本会运行良好,但如果从 crontab 执行,则会由于进程检查而提前退出。此提前终止是由使用 ps 命令通过管道传输到多个 grep/grep -v 命令的代码引起的。目的是检查进程是否已在运行,如果是,则不再执行此脚本。我知道此代码不起作用的原因是因为它试图捕获所有进程,但没有 grep -v 出crontab 始终用于最初调用脚本的进程。在重构此代码时,使用类似于通过管道传输到多个 grep 的/bin/sh -c <script name>东西是有意义的。pgrepps

这就是我的问题所在。我的 pgrep 代码可以工作,但我并不完全理解它为什么可以工作。比较输出时,发现pgrep pgrep_test.sh | grep -v $$pgrepps -ef | grep pgrep_test.sh命令似乎删除了其他进程。在我看来,pgrep 将多个 PID 组合在一起,就像它理解并遵循 PID/PPID 关系一样。问题是我在 pgrep 手册页中没有看到任何关于此内容的内容。

我认为要理解为什么 pgrep 在我的代码中起作用,我需要更好地理解 pgrep 如何分组 PID/PPID。这是我用来测试的代码,它通过 crontab 执行:

* * * * * user1 /tmp/inferencing/pgrep_test.sh 2>&1 >> /tmp/inferencing/test.log

代码本身:

#!/bin/bash

# Test how grepping for PIDs works when script is called from crontab

echo "+++++$(date +"%b %H:%M:%S") Beginning pgrep script+++++"

pgrep pgrep_test.sh | grep -v "$$" > /dev/null 2>&1
# RC=1 -- No additional processes running
# RC=0 -- Additional processes running
RETCODE=$?
echo "Return code: $RETCODE"
if [ $RETCODE -eq 0 ]; then
    echo "Additional test processes exist, exiting script"
    echo "$(ps -ef | grep pgrep_test.sh)" ; sleep 1
    echo "$(pgrep -a pgrep_test.sh | grep -v \"$$\")"
    exit 1
else
    echo "No additional processes found, continuing execution"
    echo "$(ps -ef | grep pgrep_test.sh)" ; sleep 1
    echo "$(pgrep -a pgrep_test.sh | grep -v \"$$\")"
    sleep 90
fi

我在代码中使用了 90 秒的休眠时间,以确保每分钟运行一次的 cronjob 每隔一次都会失败。以下是日志文件的样子,其中包含一些以注释形式出现的附加注释。

首先,没有运行任何附加进程:

"+++++Nov 17:04:01 Beginning pgrep script+++++"
# Sleeping every 90s means we should have alternating "no additional
# processes found" and "additional processes found" logs each execution
Return code: 1
No additional processes found, continuing execution

# ps -ef | grep pgrep_test.sh
# Initial /bin/sh -c call crontab executes
user1   12956 12954  0 17:04 ?        00:00:00 /bin/sh -c /tmp/inferencing/pgrep_test.sh 2>&1 >> /tmp/inferencing/test.log
# Child process spawned from 12956 (Shouldn't this be PID for $$?)
user1   12957 12956  0 17:04 ?        00:00:00 /bin/bash /tmp/inferencing/pgrep_test.sh
# What even is PID 12961? PPID 12957 is the /bin/bash call but these two commands are identical otherwise
# Can't be the pgrep or sleep as these have not executed yet
# Technically there's more than 1 process here now so pgrep should be giving a return code of 0 and exiting the script
# Why does it work correctly here? How does pgrep know to group these?
user1   12961 12957  0 17:04 ?        00:00:00 /bin/bash /tmp/inferencing/pgrep_test.sh
user1   12963 12961  0 17:04 ?        00:00:00 grep pgrep_test.sh

# pgrep -a pgrep_test.sh | grep -v $$
12957 /bin/bash /tmp/inferencing/pgrep_test.sh
12965 /bin/bash /tmp/inferencing/pgrep_test.sh

接下来有一个匹配的进程已经在运行:

"+++++Nov 17:05:01 Beginning pgrep script+++++"
# Since other process is still sleeping, we correctly get a return code of 0 and stop script execution
Return code: 0
Additional test processes exist, exiting script

# crontab process for (now sleeping) original script call
user1   12956 12954  0 17:04 ?        00:00:00 /bin/sh -c /tmp/inferencing/pgrep_test.sh 2>&1 >> /tmp/inferencing/test.log
# Sleeping process
user1   12957 12956  0 17:04 ?        00:00:00 /bin/bash /tmp/inferencing/pgrep_test.sh
# New crontab process
user1   13733 13594  0 17:05 ?        00:00:00 /bin/sh -c /tmp/inferencing/pgrep_test.sh 2>&1 >> /tmp/inferencing/test.log
# New main bash process
user1   13734 13733  0 17:05 ?        00:00:00 /bin/bash /tmp/inferencing/pgrep_test.sh
# Second main bash process again -- this happens every time
user1   13738 13734  0 17:05 ?        00:00:00 /bin/bash /tmp/inferencing/pgrep_test.sh
# No grep -v grep, this doesn't show up to pgrep anyways
user1   13740 13738  0 17:05 ?        00:00:00 grep pgrep_test.sh

# pgrep -a pgrep_test.sh | grep -v $$
12957 /bin/bash /tmp/inferencing/pgrep_test.sh
13734 /bin/bash /tmp/inferencing/pgrep_test.sh
14105 /bin/bash /tmp/inferencing/pgrep_test.sh

为什么当我执行命令时pgrep,它似乎知道如何过滤掉与 $$ 相关的附加子进程,而通过管道传输到 greps 的 ps -ef 却无法做到这一点?

答案1

此提前终止是由代码使用 ps 命令通过管道传输到多个 grep/grep -v 命令引起的。目的是检查进程是否已在运行,如果是,则不再执行此脚本。

不要这样做。这是一种糟糕的方法,如果您甚至懒得检查“grep”是否与输入完全匹配,而不仅仅是子字符串,那就更糟糕了。例如,如果您保持打开vim pgrep_test.sh状态,您的脚本将认为它已在运行。

有更好的方法来制作单实例脚本:

  • 将脚本作为 systemd .service 运行(通过让你的 cronjob 调用“systemctl start”或者通过使用 systemd .timer 来调用它),因为同一个服务不能启动两次;

    [Service]
    Type=oneshot
    User=user1
    ExecStart=/tmp/inferencing/pgrep_test.sh
    
  • 或者通过 使用锁文件flock,它使用基于内核的排他锁来保证单个实例。

    * * * * * user1 flock -n /tmp/inferencing/lock /tmp/inferencing/pgrep_test.sh
    

PID 12961 到底是什么?PPID 12957 是 /bin/bash 调用,但这两个命令在其他方面是相同的

它是处理 中的命令的“子 shell” $( ... )。每次使用命令替换时,bash 都会生成一个子进程来处理它。如果替换的是简单命令,该子 shell 进程可能会直接就地“执行”该命令(例如 的情况$(ps -ef)),但如果替换的是整个管道,则不一定会发生这种情况。

虽然$$总是扩展到主 shell 进程的 PID(即,当 bash 生成子 shell 时,它的值被克隆),但你可以使用$BASHPID它来获取真实的当前解释器的进程 ID。例如:

$ echo $$, $BASHPID; ps $$ $BASHPID
208231, 208231
    PID TTY      STAT   TIME COMMAND
 208231 pts/3    Ss     0:00 bash

$ (echo $$, $BASHPID; ps $$ $BASHPID)
208231, 208287
    PID TTY      STAT   TIME COMMAND
 208231 pts/3    Ss     0:00 bash
 208287 pts/3    S+     0:00 bash

$ { echo $$, $BASHPID; ps $$ $BASHPID; }
208231, 208231
    PID TTY      STAT   TIME COMMAND
 208231 pts/3    Ss     0:00 bash

$ var=$(echo $$, $BASHPID; ps $$ $BASHPID); echo "$var"
208231, 208294
    PID TTY      STAT   TIME COMMAND
 208231 pts/3    Ss+    0:00 bash
 208294 pts/3    R+     0:00 ps 208231 208294

第 2 和第 4 个示例使用了子 shell(检测这一点的另一种简单方法是注意在子 shell 中设置的变量不会传播回主 shell),而第 1 和第 3 个示例则没有。

相关内容