Lew*_*wis 23 bash io-redirection
我以为我对 bash 文件重定向有很好的处理能力,并且通常我会尽量避免“无用使用 cat ”,但是我在脚本中遇到了一些意外行为,我想了解为什么会发生这种情况。
在 bash 脚本中,我执行:
somecommand < file1 > file2
Run Code Online (Sandbox Code Playgroud)
我的期望是 file1 是安全的并且以只读方式打开。在实践中,我发现 file1 可以被覆盖。如何/为什么会发生这种情况,有没有办法在不诉诸于的情况下防止它cat?
如果它像我想象的那样工作(该过程最终会得到一个直接的 rw 文件描述符?),那么以这种方式重定向文件似乎应该被认为是危险的,但我之前从未见过这种行为。
从我的案例中添加一些细节:有问题的命令是sops,它在后台做一些 GPG 的东西。提示符GPG密码有时†被写入到用于输入文件,覆盖它。我使用的完整命令是:
sops --input-type json --output-type json -d /dev/stdin < ./secrets/file.json > ./secrets/file-decrypted.json
Run Code Online (Sandbox Code Playgroud)
从那以后我切换到了cat file1 | sops.. > file2,一切都按预期工作。我会说这是“对猫的无用使用” - 但它似乎不再那么无用了!
Sté*_*las 31
这是由于/dev/stdin(实际上/proc/self/fd/0)在 Linux(和 Cygwin,但通常不是其他系统)上实现的方式。
在 Linux 上打开/dev/stdin不像做 a dup(0),它只是重新打开与在 fd 0 上打开相同的文件。它不共享fd 0 所指的打开文件描述(使用只读模式),而是获取一个完全不相关的新打开文件描述,其模式与open().
因此,如果以 read+write 模式sops -d /dev/stdin打开/dev/stdin并且 fd 0 以只读方式打开 on /some/file,/some/file则将以 read+write 方式打开。
实际上,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
Run Code Online (Sandbox Code Playgroud)
它可能会变得更糟。如果它使用 O_TRUNCATE 打开,文件将被截断。如果 fd 0 指向管道的读取端并/dev/stdin以只写模式打开,您将获得管道的另一端。
但是使用:
cat file | cmd /dev/stdin
Run Code Online (Sandbox Code Playgroud)
将防止cmd覆盖,file因为所有人cmd都会看到管道。即使它确实以只写模式打开,它也无法返回到file,它只会到达管道的写入端,而读取端的唯一文件描述符将是cmd的标准输入。
其他操作系统没有这个问题,因为/dev/stdin在那里打开就像做一个dup(0),所以你得到相同的打开文件描述,如果你用不兼容的模式打开,open()系统调用就会失败。
¹从技术上讲,正如@user414777 在评论中所指出的,/proc/<pid>/fd/<fd>是神奇的符号链接,例如它们可以到达普通符号链接无法到达的地方,但是当打开它们时,经过路径解析阶段,它们就像普通符号链接一样你只需打开目标文件