我认为我对 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>
,神奇的符号链接例如,它们可以到达普通符号链接无法到达的地方,但是当打开它们时,经过路径解析阶段,它们的行为就像普通符号链接一样,您只需打开目标文件