我想检查 filedescriptor 指向的文件是否在 Bash (linux) 中被删除。
我都读过测试文件描述符是否有效和测试文件描述符是否有效(对于输入)。但这些答案对这个略有不同的问题没有帮助。
我使用以下测试用例:
# 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<&-
该脚本令人惊讶地表明“数据可供读取”。但是,该文件不再存在。因此必须进行一些缓存/缓冲。也许还有另一种方法,或者避免缓冲区/缓存?
另一种可行的方法是:ls -l /proc/$$/fd/3
它将指示-> '/tmp/test.txt (deleted)'
.但我更愿意坚持使用纯 Bash 解决方案(不会产生太多新进程,或解析标准输出)。
请注意,在任何其他情况下,人们当然可以只使用[ -e /tmp/test.txt ]
检查。但是,我需要知道原始文件是否被删除,因为同时可能创建了一个具有完全相同文件名的新文件。
对于那些想知道为什么有人需要这个特定结果的人(XY问题),它可用于从子 shell(使用&
)安全地检查父脚本是否仍在运行,方法是打开一个额外的 fd 来/proc/$$/cmdline
防止与回收的 PID 发生冲突。
答案1
要测试文件描述符是否引用文件系统上任何目录中没有剩余链接的常规文件,您可以对其进行系统调用并检查返回结构中的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
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
如果您的操作系统内核是 Linux,则一种更可移植的方法(对于那些没有内核zsh
且核心实用程序不是来自 GNU 的操作系统),假设 proc 文件系统已挂载,/proc
则可以使用ls
on /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
这里像前面的解决方案一样将 fd 复制到 0 上,因此即使 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
看看fstat().st_nlink
上面如何返回 1 (这意味着文件仍然有一个目录的链接),而fd 上的cat
sread()
返回错误。这不是通常的文件系统语义。
在任何情况下,要检查你的父进程是否仍在运行,你可以调用它,getppid()
如果父进程死了,它将返回 1 或子 subreaper 的 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
在 中bash
,您可以改用ps -o ppid= -p "$BASHPID"
。
另一种方法是在父级和子级之间创建一个管道,并使用select
/ poll
(或read -t0
in bash
)检查它是否仍在运行。
coproc
可以通过使用 a (最近添加到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
其中给出:
$ bash ./that-script
parent still there
$ parent gone
基于您设想的方法,并再次假设 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
使用[[ file1 -ef file2 ]]
该检查太文件是否具有相同的 dev 和 inode 号(st_dev
并由st_ino
返回stat()
)。
这似乎适用于 5.6.0,但正如我们上面所看到的,它/proc
不遵守通常的文件系统语义,我不能保证它是无竞争的(PID 和 inode 号可能已被重用)或者它可以在未来的 Linux 版本。
答案2
您的原始文件完全没有改变。
一旦按名称打开文件,进程保存的文件描述符就被视为该文件的链接。在删除所有链接之前,系统不会释放文件或其空间:这些链接可以是任意数量的为其打开文件描述的进程,以及任意数量的硬链接。
您可以在文件打开时统计文件,并按名称统计当前文件。如果它们是不同的 inode 或不同的修改日期,则您有一个已删除的文件并且有一个新文件。或者您可能会发现您有一个已删除的文件,但不存在新文件。
答案3
在 bash 中测试文件描述符是否引用已删除的文件 usr /proc/pid/fd
。下面的例子。
$ ps -fp 52
UID PID PPID C STIME TTY TIME CMD
steve 52 7 0 18:07 tty1 00:00:00 tail -f x1.pdf
$ ls -l /proc/52/fd
total 0
lrwx------ 1 steve steve 0 Jun 13 18:07 0 -> /dev/tty1
lrwx------ 1 steve steve 0 Jun 13 18:07 1 -> /dev/tty1
lrwx------ 1 steve steve 0 Jun 13 18:07 2 -> /dev/tty1
lr-x------ 1 steve steve 0 Jun 13 18:07 3 -> /mnt/c/temp/x1.pdf (deleted)
$