检查文件描述符是否引用已删除的文件(在 Bash 中)

Yet*_*eti 4 linux bash file-descriptors

我想检查文件描述符指向的文件是否在 Bash (linux) 中被删除。

我已经阅读了Testing if a filedescriptor is validTesting if a filedescriptor is valid (for input)。但是这些答案对这个略有不同的问题没有帮助。

我使用以下测试用例:

# create file
echo hello > /tmp/test.txt

# open read-only fd
exec 3< /tmp/test.txt

# delete file
rm /tmp/test.txt

# special zero-timeout to check if data available for reading
if read -u 3 -t 0
then
    echo "data available for reading"
else
    echo "no data available"
fi

# close fd (clean up)
exec 3<&-
Run Code Online (Sandbox Code Playgroud)

这个脚本出人意料地表明“数据可供阅读”。但是,该文件不再存在。所以必须进行一些缓存/缓冲。也许还有另一种方法,或者避免缓冲区/缓存?

一个有效的替代方法是:ls -l /proc/$$/fd/3这将指示 -> '/tmp/test.txt (deleted)'. 但我更愿意坚持使用纯 Bash 解决方案(不会产生太多新进程或解析标准输出)。

请注意,在任何其他情况下,当然可以仅用于[ -e /tmp/test.txt ]检查。但是,我需要知道原始文件是否被删除,因为同时可能已经创建了一个文件名完全相同的新文件

对于那些想知道为什么有人需要这个特定结果(XY 问题)的人来说,它可以&通过打开一个额外的 fd 来安全地从子shell(with )检查父脚本是否仍在运行,以/proc/$$/cmdline防止与回收的碰撞PID。

Sté*_*las 13

要测试文件描述符是否引用在文件系统的任何目录中没有剩余链接的常规文件,您可以对其进行fstat()系统调用并检查st_nlink返回结构中的链接数(字段)。

使用zsh,你可以使用它的stat内置函数来做到这一点:

zmodload zsh/stat
fd=3
if
  stat -s -H st -f $fd &&   # can be fstat'ed (is an opened fd)
    [[ $st[mode] = -* ]] && # is a regular file
    ((st[nlink] == 0))      # has no link on the filesystem
then
  print fd $fd is open on a regular file that has no link in the filessystem
fi
Run Code Online (Sandbox Code Playgroud)

bash(GNU shell) 没有等价物,但是如果您在 GNU 系统上,您可能拥有 GNU,stat在这种情况下您应该能够执行以下操作:

fd=3
if [ "$(LC_ALL=C stat -c %F:%h - <&"$fd")" = 'regular file:0' ]; then
  printf '%s\n' "fd $fd is open on a regular file that has no link in the filessystem"
fi
Run Code Online (Sandbox Code Playgroud)

如果你的操作系统内核是Linux,更便携的方式(对于没有这些操作系统zsh以及其中核心工具从GNU都没有),假设proc文件系统被安装在/proc可以使用ls/proc/self/fd/$fd

if
  LC_ALL=C TZ=UTC0 ls -nLd /proc/self/fd/0 <&"$fd" |
    LC_ALL=C awk -v ret=1 '
      NF  {if ($1 ~ /^-/ && $2 == 0) ret=0; exit}
      END {exit(ret)}'
then
  printf '%s\n' "fd $fd is open on a regular file that has no link in the filessystem"
fi
Run Code Online (Sandbox Code Playgroud)

这里像在前面的解决方案中一样在 0 上复制 fd,因此即使 fd 具有 close-on-exec 标志它也能工作(假设 fd 首先不是 0,但 fd 0 通常不会具有 close-on-exec旗帜)。

这种方法不适用于 Linux 的 procfs 假文件系统来检查打开的 fd 是否/proc/<some-pid>/cmdline指的是实时进程:

$ zsh -c 'zmodload zsh/stat; (sleep 1; stat -f0 +nlink; cat) < /proc/$$/cmdline &'
$ 1
cat: -: No such process
Run Code Online (Sandbox Code Playgroud)

看看fstat().st_nlink上面如何返回 1 (这意味着文件仍然有一个指向目录的链接),而fd 上的cat'sread()返回错误。这不是通常的文件系统语义。


在任何情况下,要检查您的父级是否仍在运行,您可以调用getppid()which 将返回 1 或如果父级死亡的子收割者的 pid 。在 中zsh,您将使用$sysparams[ppid](在zsh/system模块中)。

$ sh -c 'zsh -c '\''zmodload zsh/system
                    print $PPID $sysparams[ppid]
                    sleep 2; print $PPID $sysparams[ppid]
                '\'' & sleep 1'
14585 14585
$ 14585 1
Run Code Online (Sandbox Code Playgroud)

在 中bash,您可以ps -o ppid= -p "$BASHPID"改用。

另一种方法是在父子之间创建一个管道,并使用select/ poll(或read -t0in bash)检查它是否仍在运行。

可以通过使用coproc(最近才添加到bash)而不是&.

background_with_pipe() {
  coproc "$@" {PARENT_FD}<&0 <&3 3<&- >&4 4>&-
} 3<&0 4>&1

parent_gone() {
  local ignore
  read -t0 -u "$PARENT_FD" ignore
}

background_with_pipe eval '
  parent_gone || echo parent still there
  sleep 2
  parent_gone && echo parent gone
'

sleep 1
exit
Run Code Online (Sandbox Code Playgroud)

其中给出:

$ bash ./that-script
parent still there
$ parent gone
Run Code Online (Sandbox Code Playgroud)

以您设想的方法为基础,并再次假设 Linux 内核procfs安装在 上/proc,您还可以执行以下操作:

exec {PARENT_CANARY}< /proc/self/cmdline; PARENT_PID=$BASHPID
parent_gone() {
  ! [[ /proc/$PARENT_PID/cmdline -ef /proc/self/fd/$PARENT_CANARY ]]
}

(
   parent_gone || echo parent still there
   sleep 2
   parent_gone && echo parent gone
) &

sleep 1
Run Code Online (Sandbox Code Playgroud)

使用[[ file1 -ef file2 ]]该检查文件是否也有同样的开发和inode编号(st_devst_ino由返回stat())。

这似乎适用于 5.6.0,但正如我们在上面看到的那样,/proc它不符合通常的文件系统语义,我不能保证它是无竞争的(PID 和 inode 号可能已被重用)或者它可以在未来的 Linux 版本。

  • @Yeti,inode 编号只是*每个文件系统*,因此如果您想检查某个 fd 上打开的文件是否仍然与某个路径上的文件相同,则需要检查设备编号和 inode 编号(` `stat()`/`fstat()` 返回的结构中的 st_dev` 和 `st_ino`。 (2认同)

Pau*_*ant 7

您的原始文件完全没有改变。

一旦按名称打开文件,您的进程持有的文件描述符就被视为指向该文件的链接。在删除所有链接之前,系统不会释放文件或其空间:这些链接可以是任意数量的为其打开了文件描述的进程,以及任意数量的硬链接。

您可以在打开文件时统计文件,并按名称统计当前文件。如果它们是不同的 inode 或不同的修改日期,则您有一个已删除的文件,并且有一个新文件。或者您可能会发现您有一个已删除的文件,但没有新文件存在。