读取 Bash 中可能已删除的文件

Read potentially deleted file in Bash

在下面的代码中:

#!/bin/bash

if [ ! -f "$file" ]
then
    stat --printf="%s" "$file"
    cat "$file"
else
    echo -1
fi

$file 是可以随时删除的二进制文件的名称。

我最担心的是文件可能会在 [ ! -f "$file" ] 之后但在 cat "$file" 执行之前被删除,结果会不正确。

但是我也想知道如果在执行cat "$file"的过程中删除了文件会怎么样。是否会输出 fully/partially,如果 $file 在驱动器上被覆盖,是否有读取不相关字符的风险? man cat 没有解释。 编辑:

我如何保证输出是?

注意:文件的大小可能高达 5MiB,复制它会太慢。

编辑:文件是用 ffmpeg ... -window_size 5 -extra_window_size 0 -min_seg_duration 2000000 -f dash ... 创建的,在我的例子中,在特定目录中同时保留最多 5 个文件,它们从不重复使用相同的名称,并且它们遵循这个循环(完全由 ffmpeg 控制) : 1) 使用 .tmp 扩展名创建 2) 不使用 .tmp 重命名 3) (至少 10 秒后)删除

你不能在 bash 中保证(输出是整个文件以其大小为前缀,否则是 -1),因为,正如你提到的,两个命令之间可能会发生某些事情(和进程)。

顺便说一句,该文件可能会被其他进程截断(执行 ftruncate(2)...),因此您无法保证获得内容的 "totality"。

您可以考虑在 shell 脚本中使用 advisory locking (e.g. with flock(2) or lockf(3) ...; consider also flock(1)),只有当更改该文件的所有程序都同意该锁定时,它才能正常工作(所以你需要采用整个系统约定).

也许您想使用一些 RDBMS server providing ACID-身份保证。

But I also wonder what will happen if the file is deleted during the execution of cat "$file". Will it be fully/partially outputted, is there a risk to read unrelated characters if $file get overwritten on the drive?

没有。如果你有一些进程 运行 cat (可能是 /bin/cat 程序,请参阅 $file 上的 cat(1)) that process keeps an opened file descriptor 。所以只要数据不会被释放(或重写)因为某些打开的文件描述符引用了该文件。

也许你可以编写一个简单的 C 程序(它在 相同的 进程中运行,与某些 shell 脚本中的几个命令相反)打开文件,在打开的文件描述符上使用 fstat(2)(如果使用 stdio 函数,可能通过 fileno(3)),并循环复制其内容.这并不能保护您免受其他进程在该副本期间所做的敌对 ftruncate(2)

如果您不关心截断或覆盖,只关心过早的 rm(或 unlink(2)),您可以使用临时的附加硬盘 link。也许就这么简单:

 newhardlink=".newhardlink$$"
 ln "$file" "$newhardlink"
 stat --printf="%s" "$newhardlink"
 cat "$newhardlink"
 rm "$newhardlink"

如果你害怕不同的文件系统,你可以

 mydir=$(dirname "$file")
 newhardlink="$mydir/.newhardlink$$"

而不是 newhardlink=".newhardlink$$",您可以玩 trap 技巧以在所有情况下完成最终清理 rm "$newhardlink"

还要注意 inotify(7)(对于您的情况可能有点矫枉过正)

更好的是,更改 ffmpeg 的启动方式,以便它使用一些临时文件(参见 mktemp(1), mkstemp(3))...

或者使用 subshell 技巧,在 cat

之前的那个 subshell stat --printf="%s" -L /dev/stdin

解决方法是不检查文件是否存在;只需尝试打开它,并处理打开文件时出现的任何错误。如果可行的话,这是在子 shell 中最容易做到的:

(
    exec < foo || exit 1
    cat
)

如果你真的需要使用stat,那就有点棘手了。如果没有给出参数,BSD stat 将处理附加到标准输入的文件,但是 GNU stat(据我所知)必须给出一个现有的文件名。