檢查文件描述符是否引用了已刪除的文件(在 Bash 中)
我想檢查文件描述符指向的文件是否在 Bash (linux) 中被刪除。
我已經閱讀了Testing if a filedescriptor is valid和Testing 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 系統上,您可能擁有 GNUstat
,在這種情況下您應該能夠執行以下操作: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
這裡像前面的解決方案一樣在 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 -t0
inbash
)檢查它是否仍在執行。可以通過使用
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 版本。