查找在Linux中使用文件的进程
1. 简介
有时当我们尝试访问文件时,我们可能会遇到文件忙的消息。这意味着系统正在运行一个正在使用该文件的进程,使其保持打开状态以进行读取或写入。发生这种情况时,有时我们会想要发现使用该文件的进程。
在本教程中,我们将了解如何找到正在使用文件的进程。
2. 查找进程的方法和命令
有几个命令可以帮助我们找到对文件进行操作的进程,所以我们将从那里开始。这些命令从 Linux 内核收集数据,因为它负责运行进程、文件系统等。此外,我们将直接读取内核表以获取所需的信息。
2.1. fuser器命令
让我们从列出使用文件或套接字的进程的fuser 命令开始。它也可以用来杀死一个进程。我们可以将它与*-v*参数一起使用来获得详细的输出:
$ fuser -v text.txt
USER PID ACCESS COMMAND
/home/john/text.txt:
john 22829 f.... less
正如我们所看到的,在这种情况下,less 的进程正在访问文件。** fuser命令返回*PID *、调用进程的用户和文件状态**。
运行带有*-k选项的命令将终止 它找到的进程。让我们尝试使用SIGKILL 杀死less*进程,使用 PID 24815:
$ fuser -k text.txt
/home/john/text.txt: 24815
假设vi 正在访问同一个文件。当我们运行相同的命令时,不会返回任何内容,因为vi打开文件,将其内容读入内存,然后关闭它。内核已经完成了它的工作,所以关于文件的信息不可用。
但是,我们可以尝试通过分析和猜测fuser -cv text.txt的输出来找到过程。响应是访问同一文件系统上的文件的所有进程的列表。在这种情况下,输出的最后一行是我们正在寻找的进程。然而,情况可能并非总是如此。
$ fuser -cv text.txt
USER PID ACCESS COMMAND
/home/john/text.txt:
root kernel mount /home
...
john 24807 F.c.. vi
运行带有*-k*选项的命令可能会杀死 所有使用指定文件或目录的进程,因此请谨慎使用。
2.2. lsof命令
lsof 命令可以返回打开文件的列表。为了缩小结果范围并保留标题行,我们将它与*head 和grep 命令一起使用。假设vi*仍在运行,让我们试一试:
$ lsof | { head -1 ; grep text.txt ; }
COMMAND PID TID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME
vi 24807 john 4u REG 8,3 12288 3147621 /home/john/.text.txt.swp
** lsof命令返回进程名称、PID和运行该进程的用户**。如果进程有线程,我们将使用 task 命令查看它们的标识号TID 。FD 字段可以包含三个部分:文件描述符(在我们的例子中为4)是第一部分,模式字符是第二部分(u表示文件可读写),锁定字符是第三部分。
让我们看看当less 命令访问文件而不是vi时的输出:
$ lsof | { head -1 ; grep text.txt ; }
COMMAND PID TID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME
less 28423 john 4r REG 8,3 75 3146117 /home/john/text.txt
在这种情况下,我们看到文件已打开以供读取,其中FD = 4r。
但是,就像fuser一样,如果我们使用vi来编辑文件,lsof不会将其显示为正在使用。
该命令有很多选项。例如,-t只给出没有标头的进程标识符,这有助于编写脚本。
lsof与fuser非常相似,只是它不能终止进程。然而,因为lsof也给了我们PID,我们可以用kill命令加入它:
$ kill -TERM `lsof -t text.txt`
要使用目录及以下目录中的文件发现所有进程的*PID ,我们可以使用 *+D 递归扫描它。
此外,lsof经常用于查找由给定(由PID)进程打开的所有文件:
$ lsof -p <PID>
2.3. 直接从内核获取信息
另一种检测正在使用的文件进程的方法是直接访问内核。内核将数据保存在/proc 下。有关进程的信息位于目录*/proc/pid_the_process*中。它包含进程文件打开的所有内容的条目,由其文件描述符命名,链接到实际文件。
因此,我们只需要使用ls 命令:
ls -l /proc/*/fd
让我们使用仅打印PID的脚本来改进这些结果:
#!/usr/bin/bash
for pid in /proc/{0..9}*; do
i=$(basename "$pid")
for file in "$pid"/fd/*; do
link=$(readlink -e "$file")
if [ "$link" ]; then
echo "PID $i: $link"
fi
done
done | grep $1
对于dir /proc/pid中的每个PID,我们正在挖掘目录,然后挖掘到子目录fd。然后我们阅读链接。如果这个链接是一个文件,我们打印它的名字。最后,使用给定的文件名 grep 结果。
如果我们将此脚本保存为mylsof并允许它执行(chmod u+x mylsof),我们可以使用它来解决我们的问题:
$ ./mylsof text.txt
PID 30069: /home/john/text.txt
从内核获取信息总是有效的,即使在带有BusyBox 的系统上也是如此。如果系统中既没有lsof也没有fuser,它会有所帮助。