如何在 Linux 中使用 lsof(附实例)

有没有想过如何发现系统上当前打开和使用的文件? Linux lsof 命令列出打开的文件并提供大量额外信息。通过这些实际示例了解如何使用 lsof。
什么是 lsof?
lsof
命令可在任何 Linux 操作系统中本地使用,提供打开文件的列表。但是,输出可能有点含糊和冗长,尤其是在给定系统上使用许多应用程序时。让我们看一下基本的 lsof
输出。我们将以 root 身份运行 lsof
。
要以 root 身份登录,您可以打开一个终端窗口并键入命令,如 sudo su
或 su
以接收 root 身份验证提示。或者,您可以执行 sudo lsof
。请注意,虽然您也可以以普通非根用户身份运行 lsof
,但您可能会发现输出不完整。
示例:基本 lsof 输出
sudo su
lsof | head -n10

在这里,我们使用 lsof
命令启动 lsof 工具,并使用管道 (|
) 捕获输出的前十行以发送lsof
输出到辅助 head -n10
命令的信息,该命令仅捕获顶部(头部)十行。
lsof
命令列出了各个列。首先,我们看到 COMMAND 列,其中列出了持有打开文件/打开文件锁的二进制文件。接下来我们看到进程 ID 列 PID,这在调试各种应用程序问题(包括错误持有的文件锁)时非常方便。在下一节中,我们将查看一个示例,说明如何结合使用 lsof
和 kill
来(破坏性地)终止持有不正确文件锁的应用程序。
如果您的操作系统支持,TID 列可以帮助您了解给定行是进程还是任务。如果输出为空白,如我们的示例(运行在 Linux Mint 上,支持 TID 列输出的操作系统)所示,给定的行/命令是一个进程,一个非-任务。例如,如果您在操作系统中启动计算器应用程序,即任务,则此列将由任务/线程标识号填充。
TASKCMD 列包含任务命令名称。同样,它仅在给定行是任务而不是进程时才可见。这通常与第一个 COMMAND 列中显示的命令/进程相同,但是,例如,Linux 允许任务更改其命令名称,因此它可以包含有关该任务的附加信息。 USER 列列出了启动进程/任务的用户。
接下来我们有一个重要的列 FD(文件描述符),它可以列出 文件描述符编号 或指示这是什么类型的文件描述符的特定文本字符串。例如,如果您在此列中看到 cwd
,它代表 当前工作目录,表示给定的进程或任务在当前工作目录上持有打开的锁该工作目录列在 NAME 列下。
要获得所有可能的 FD
字符串的完整列表,请在命令提示符下键入 man lsof
,然后键入 /FD
(斜线和后面有 3 个空格)在 FD 之前)一旦进入手册,然后按 ENTER 搜索 FD 部分。
说到常规文件,你可能会把 FD 列想象成一个计数器,或者一个唯一的 ID 计数器,从 0 开始增加到系统上常规打开文件的总数,随着由 ulimit -n 设置等定义的最大打开文件数。
查看常规文件时,FD 列将显示 102u
或 13w
。这两个例子分别表示系统上第102个打开的文件,在混合读/写访问模式下,如u
指示器/标签和第13个打开系统上的文件,仅在写访问模式下。
TYPE 列是不言自明的;它指示是否持有常规文件或目录打开锁。此处可以显示各种其他标签,在 man lsof
中搜索 /TYPE
(如上所述)将提供完整列表。
DEVICE 列通常列出大多数文件类型的设备编号,以逗号分隔。 SIZE/OFF
列是文件的大小,或者以字节为单位的文件偏移量,NODE列一般显示本地文件的文件节点或者NFS文件inode号.当给定的锁是打开的 Internet 连接时,它还可以列出例如 TCP 或 UDP。
示例:将 lsof 与 kill 结合使用
将 lsof
与 grep
、awk
和 kill
结合使用,可以在给定系统中搜索特定文件,随后终止进程(使用 PID 或进程标识符,如前所述)。
请注意,只有在有意义的情况下才应这样做。例如,您可能有一个多线程 Bash 脚本,该脚本启动许多不同的子 shell,每个子 shell 都保存给定的文件,并且您预计其中一个子进程挂起。您知道挂起进程的打开文件名,但不知道该进程/任务的 PID 是什么。
在这种情况下,我们可以执行以下命令:
sudo su
lsof | grep 'some_file_descriptor' | awk '{print $2}' | xargs -I{} kill -9 {}
让我们把它放到一个实际的例子中。假设我们在 /tmp
中创建了以下脚本 test.sh
:
rm -Rf workspace
mkdir workspace
cd workspace
sleep 36000
执行此脚本时,将创建一个名为 workspace
的目录,使用 cd
将目录更改为该目录,然后 sleep
10 小时。乍一看这似乎不会打开任何文件,请记住前面讨论的 cwd
;该脚本有一个正在使用的当前工作目录,即 /tmp/workspace
!
让我们看看实际效果如何。首先,我们定义(使用您喜欢的文本编辑器,如 vi
),然后在一个终端会话中启动脚本:

接下来我们跳转到一个辅助/新的终端会话并执行 lsof
,寻找脚本的工作目录,即 workspace
:

正如我们所见,lsof
不仅能够找到我们的测试脚本 test.sh
,它在 / 上持有
,但我们也看到 cwd
锁tmp/workspacesleep
,从脚本内部开始,在同一文件上保存着一个 cwd
打开文件(或更好的目录)描述符目录。
我们还可以看到 test.sh
脚本和 sleep
命令的 PID。让我们暂时假设我们的 test.sh
脚本挂了,我们想破坏性地完全终止它,即使用 kill -9
这是最具破坏性的终止方式一个没有任何级别的正常关闭的进程,这基于脚本持有的任何打开的文件(或者在我们的例子中是打开的目录)。我们从辅助终端发出以下命令:
lsof | grep "workspace" | awk '{print $2}' | xargs -I{} kill -9 {}

此命令将采用先前的输出并进一步解析它。 awk {print $2}
基本上抽取第二列(进程 ID),xargs
命令会将第二列传递给 kill -9
(命令中的 {}
将替换为 xargs
收到的任何输入)。
我们还可以预期 lsof
有一个包含 PID
的标题行,并且此文本也将传递给 kill。虽然它不会造成问题,但更好的整体命令应该是 lsof | grep \工作区\ | grep -v \PID\ | awk {打印 $2} | xargs -I{} kill -9 {}
,或其他一些完全过滤第一行的方法。
我们的第一个/主要终端会话的结果是我们的脚本立即被杀死:

如果您想了解有关 xargs 的更多信息,您可以阅读将 xargs 与 bash -c 结合使用来创建复杂命令。对于 Bash 多线程编程,请参阅如何在 Bash 脚本中使用多线程处理。
包起来
在本文中,我们探索了使用 lsof
、list open files 命令,以及其默认输出中的每一列代表的信息。我们还查看了一个实际用例,其中我们将 lsof
与 grep
、awk
和 kill
结合使用以查找一个特定的文件(或在我们的例子中是一个目录)被脚本保持打开状态,随后终止该脚本,从而关闭打开的目录。
如果您喜欢阅读本文,请查看我们的断言、错误和崩溃:有何区别?文章。