Linux

檢查文件描述符是否引用了已刪除的文件(在 Bash 中)

  • June 15, 2020

我想檢查文件描述符指向的文件是否在 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<&-

這個腳本出人意料地表明“數據可供閱讀”。但是,該文件不再存在。所以必須進行一些記憶體/緩衝。也許還有另一種方法,或者避免緩衝區/記憶體?

另一種可行的方法是:ls -l /proc/$$/fd/3這將指示 -> '/tmp/test.txt (deleted)'. 但我更願意堅持使用純 Bash 解決方案(不會產生太多新程序或解析標準輸出)。

請注意,在任何其他情況下,當然可以只使用[ -e /tmp/test.txt ]檢查。但是,我需要知道原始文件是否被刪除,因為同時可能已經創建了一個具有完全相同文件名的新文件

對於那些想知道為什麼有人需要這個特定結果(XY 問題)的人,它可以用於從子shell(with &)安全地檢查父腳本是否仍在執行,方法是打開一個額外的 fd 以/proc/$$/cmdline防止與回收的碰撞PID。

您的原始文件完全沒有改變。

一旦通過名稱打開文件,您的程序所持有的文件描述符將被視為文件的連結。在刪除所有連結之前,系統不會釋放文件或其空間:這些連結可以是任何數量的為其打開文件描述的程序,以及任何數量的硬連結。

您可以在打開文件時統計文件,並按名稱統計目前文件。如果它們是不同的 inode 或不同的修改日期,則您有一個已刪除的文件並且有一個新文件。或者您可能會發現您有一個已刪除的文件但不存在新文件。

要測試文件描述符是否引用文件系統上任何目錄中沒有剩餘連結的正常文件,您可以對其進行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可以使用lson /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

這裡像前面的解決方案一樣在 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

看看上面是如何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

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

其中給出:

$ bash ./that-script
parent still there
$ parent gone

建立在您設想的方法上,並再次假設 Linux 核心已procfs安裝 on /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 版本。

引用自:https://unix.stackexchange.com/questions/592720