Contents

什么是.pid文件?

1. 概述

有时需要保存 Linux 进程的进程标识号 (PID)。在本教程中,我们将介绍一种使用*.pid*文件存储 PID 的常用方法以及如何使用它的示例。

2. 什么是*.pid* 文件?

有时应用程序会将 PID 写入文件以便于访问。它只是一个仅包含进程 PID 的文本文件。没有关于创建或使用的特定规则。这只是一个有用的约定。

让我们从一个创建*.pid*文件的简短示例开始。

3. 创建 .pid 文件

现在让我们讨论 .pid文件的创建。

3.1. 初始文件创建

我们可以在脚本中创建 .pid文件的一种方法是将*$$*的输出通过管道 传输到文件中:

% echo $$ > myShell.pid
% cat myShell.pid
40276

*$$*是一个 Linux 变量,它返回调用它的进程的 PID。在这种情况下,它是 shell 的 PID。 现在,让我们从一个小脚本开始:

#!/bin/bash

# create file
pid_file="process.pid"
echo $$ > $pid_file
count=0
while [ $count -le 10 ]
do
  echo Going $count..
  sleep 1
  count=$(($count+1))
done

当我们运行脚本时:

% ./process.sh

我们会看到两件事。首先,我们将看到该过程的输出:

% ./process.sh
Going 0..
Going 1..
Going 2..
Going 3..
Going 4..
Going 5..
Going 6..
Going 7..
Going 8..
Going 9..
Going 10..

如果我们在另一个终端窗口中运行ls ,我们将看到process.pid文件:

% ls
process.pid	process.sh

它包含运行脚本的进程的 PID。我们可以通过在文件上使用cat 来验证这一点:

% cat process.pid
34876

3.2. .pid文件位置

虽然我们可以将*.pid*文件放在任何地方,但通常情况下,我们会让我们的进程将文件放在 /var/run 中。为了避免与其他进程发生冲突,我们可以更进一步,创建一个新目录 /var/run/myScript

% echo $$ > /var/run/myScript/myShell.pid

但是,在某些系统上,该目录可能归 root所有,在这种情况下,我们可能无法在其中写入 .pid 文件。第二个选项是主目录:

% $$ > ~/myScript/myShell.pid

4. 使用 .pid 文件终止进程

现在我们的进程有一个*.pid*文件,让我们想想我们可以用它做什么。

假设我们想在进程运行时终止它。如果有*.pid*文件,我们可以从文件中获取 PID,然后将其与xargs 和 kill 一起使用。这确保我们只需要知道 .pid文件的名称和位置,而不是实际的 PID 本身:

% cat process.pid | xargs kill

这里的主要好处是我们正在杀死我们想要杀死的进程。我们可以这样做:

ps -ef | grep process

但是,如果有多个应用程序实例正在运行,这样做可能会产生多个结果。它还会启动实际运行 grep的进程,例如:

% ps -ef | grep process
  501 40311 40276   0  7:00AM ttys001    0:00.00 grep process

在这种情况下,我们必须在杀死任何东西之前考虑意外匹配。

5. 确保应用程序的单一实例

我们还可以使用*.pid文件来确保应用程序在启动之前尚未运行。为此,我们的脚本需要进行两项更改。首先,我们需要在运行结束时删除.pid*文件:

# clean up file after we're done
rm $pid_file

其次,我们需要在开头添加一个检查*.pid*文件的存在:

if [ ! -f $pid_file ]; then
  echo "Creating .pid file $pid_file"
  echo $$ > $pid_file
else
  echo "Found .pid file named $pid_file. Instance of application already exists. Exiting."
  exit
fi

如果文件存在,我们将假定应用程序正在运行并退出而不运行脚本的其余部分。

所以现在,我们的脚本看起来像:

#!/bin/bash

# create file
pid_file="process.pid"
if [ -f $pid_file ]; then
  echo "Found existing .pid file named $pid_file. Exiting."
  exit
else
  echo "Creating .pid file $pid_file"
  echo $$ > $pid_file
fi
count=0
while [ $count -le 10 ]
do
  echo Going $count..
  sleep 1
  count=$(($count+1))
done
# clean up file after we're done
rm $pid_file

要查看实际情况,让我们打开两个终端窗口并在它们中运行我们的脚本:

% ./process.sh

第一个窗口将按预期运行:

% ./process.sh 
Creating .pid file process.pid
Going 0..
Going 1..
Going 2..

第二个窗口将检测到 .pid文件并退出而不运行:

% ./process.sh 
Found existing .pid file named process.pid. Exiting.

6. 处理陈旧的 .pid文件

我们在这个实现中可能遇到的一个问题是一个陈旧的*.pid*文件。假设应用程序没有到达清理 .pid文件的行就死了。如果我们重新启动应用程序,该文件将仍然存在,并且脚本将退出而不运行。

我们可以扩展我们的启动检查来处理这种情况。我们可以保证如果 .pid文件存在,那么里面的PID也是一个有效的进程。让我们尝试对 .pid文件的内容使用pgrep

pgrep -F process.pid

如果进程与 PID 匹配,这将返回 0 退出代码,如果没有与 PID 匹配的进程,则返回 1。 所以现在,我们的脚本将在开始时进行两项检查:

#!/bin/bash

# create file
pid_file="process.pid"
if [ -f $pid_file ]; then
  echo "Found existing .pid file named $pid_file. Checking."
  # check the pid to see if the process exists
  pgrep -F $pid_file
  pid_is_stale=$?
  old_pid=$( cat $pid_file )
  echo "pgrep check on existing .pid file returned exit status: $pid_is_stale"
  if [ $pid_is_stale -eq 1 ]; then
    echo "PID $old_pid is stale. Removing file and continuing."
    rm $pid_file
  else 
    echo "PID $old_pid is still running or pgrep check errored. Exiting."
    exit
  fi
else 
  echo "Creating .pid file $pid_file"
  echo $$ > $pid_file
fi
count=0
while [ $count -le 10 ]
do
  echo Going $count..
  sleep 1
  count=$(($count+1))
done
# clean up file after we're done
rm $pid_file

要查看“陈旧文件”逻辑的工作情况,请在命令行启动应用程序并立即使用 CTRL+c 将其终止。这将产生并留下一个陈旧的*.pid*文件。再次启动脚本,我们将看到:

./process.sh
Found existing .pid file named process.pid. Checking.
pgrep check on existing .pid file returned exit status: 1
PID 35975 is stale. Removing file and continuing.
Going 0..
Going 1..

我们可以通过在一个窗口中启动应用程序然后在另一个窗口中立即启动它来查看“仍在运行”的逻辑。我们将在第二个窗口中看到:

% ./process.sh
Found existing .pid file named process.pid. Checking.
35926
pgrep check on existing .pid file returned exit status: 0
PID 35926 is still running or pgrep check errored. Exiting.

所以现在,我们有一个使用*.pid*文件的脚本来确保我们一次只运行一个应用程序实例。