在 bash 中,如何写入重定向输入的文件,并且可以阻止这种情况吗?

在 bash 中,如何写入重定向输入的文件,并且可以阻止这种情况吗?

我认为我对 bash 文件重定向有很好的处理能力,通常我会尽量避免“对猫的无用利用”,但我在脚本中遇到了一些意外行为,我想了解为什么会发生这种情况。

在 bash 脚本中,我执行:

somecommand < file1 > file2

我的预期是 file1 是安全的并以只读方式打开。在实践中,我发现 file1 可以被覆盖。这种情况是如何/为什么发生的,有没有办法在不诉诸cat?

如果它按照我的想象工作(该过程最终以直接 rw 文件描述符结束?),似乎以这种方式重定向文件应该被认为是危险的,但我以前从未见过这种行为。

从我的案例中添加一些细节:有问题的命令是标准操作规程,它在后台做一些 GPG 的事情。 GPG 密码提示有时会写入用于输入的文件中,并覆盖它。我使用的完整命令是:

sops --input-type json --output-type json -d /dev/stdin < ./secrets/file.json > ./secrets/file-decrypted.json

从那以后我就切换到了cat file1 | sops.. > file2,一切都按预期进行。我本来会说这是“猫的无用用途”——但它似乎不再那么无用了!


似乎是 gpg-agent 未运行且第一次提示时。

答案1

这是由于/dev/stdin(实际上/proc/self/fd/0)在 Linux(和 Cygwin,但通常不是其他系统)上实现的方式。

在 Linux 上打开/dev/stdin并不像执行 a 那样dup(0),它只是重新打开与在 fd 0 上打开的文件相同的文件。它不共享打开文件描述fd 0 引用(使用只读模式),但得到一个完全不相关的新值打开文件描述,模式如 中指定open()

因此,如果以读+写模式sops -d /dev/stdin打开/dev/stdin,并且 fd 0 以只读方式打开/some/file/some/file则将以读+写模式打开。

实际上,cmd /dev/stdin < file与 相同cmd file < file。您会发现这/dev/stdin只是一个符号链接file

/tmp$ namei -l /dev/stdin < file
f: /dev/stdin
drwxr-xr-x root     root     /
drwxr-xr-x root     root     dev
lrwxrwxrwx root     root     stdin -> /proc/self/fd/0
drwxr-xr-x root     root       /
dr-xr-xr-x root     root       proc
lrwxrwxrwx root     root       self -> 73569
dr-xr-xr-x stephane stephane     73569
dr-x------ stephane stephane   fd
lr-x------ stephane stephane   0 -> /tmp/file
drwxr-xr-x root     root         /
drwxrwxrwt root     root         tmp
-rw-r--r-- stephane stephane     file

情况可能会变得更糟。如果使用 O_TRUNC 打开,文件将被截断。如果 fd 0 指向管道的读取端并且/dev/stdin以只写模式打开,您将获得管道的另一端。

但使用:

cat file | cmd /dev/stdin

会防止cmd覆盖,file因为所有人cmd都会看到管道。即使它确实以只写模式打开,它也无法返回到file,它只会到达管道的写入端,而读取端上的唯一文件描述符将是cmd的 stdin 。

其他操作系统没有这个问题,因为打开/dev/stdin它就像执行 a 一样dup(0),所以你会得到相同的结果打开文件描述如果您以不兼容的模式打开,open()系统调用就会失败。


1 从技术上讲,正如 @user414777 在评论中指出的那样/proc/<pid>/fd/<fd>神奇的符号链接例如,它们可以到达普通符号链接无法到达的地方,但是当打开它们时,经过路径解析阶段,它们的行为就像普通符号链接一样,您只需打开目标文件

相关内容