在 bash 中,我注意到如果使用重定向的命令失败,则在该命令之前运行的任何程序都不会运行。
例如,该程序打开文件“a”并向文件“a”写入 50 个字节。但是,运行此命令并重定向到权限不足的文件 (~root/log) 不会导致“a”的文件大小发生变化。
$ ./write_file.py >> ~root/log
-bash: /var/root/log: Permission denied
cdal at Mac in ~/experimental/unix_write
$ ls -lt
total 16
-rw-rw-r-- 1 cdal staff 0 Apr 27 08:54 a <-- SHOULD BE 50 BYTES
人们会认为程序会运行,捕获任何输出(但也会写入文件“a”),然后无法将任何输出写入 ~root/log。相反,该程序永远不会运行。
为什么会这样?bash 如何选择在执行程序之前执行“检查”的顺序?是否也进行其他检查?
ps 我试图确定在 cron 下运行的程序在重定向到“权限被拒绝”文件时是否实际运行。
答案1
这实际上并不是检查顺序的问题,而只是 shell 设置的顺序问题。重定向是在命令运行之前设置的;因此,在您的示例中,shell~root/log
在尝试执行任何涉及./write_file.py
.由于无法打开日志文件,因此重定向失败,并且 shell 此时停止处理命令行。
演示这一点的一种方法是获取一个不可执行的文件并尝试运行它:
$ touch demo
$ ./demo
zsh: permission denied: ./demo
$ ./demo > ~root/log
zsh: permission denied: /root/log
这表明 shell 甚至不考虑./demo
何时无法设置重定向。
答案2
来自bash 手册页,重定向部分(我强调的):
前执行命令时,可以使用 shell 解释的特殊符号来重定向其输入和输出。
...
打开或创建文件失败会导致重定向失败。
因此 shell 尝试打开 的目标文件stdout
,但失败了,并且命令根本没有执行。
答案3
值得观察的是外壳必须在启动程序之前建立重定向。
考虑你的例子:
./write_file.py >> ~root/log
shell 中发生的事情是:
- 我们(外壳)
fork()
;子进程从其父进程(shell)继承打开的文件描述符。 - 在子进程中,我们
fopen()
(扩展)“~root/log”,并将dup2()
其设置为fd 1(以及close()
临时fd)。如果fopen()
失败,请致电exit()
向家长报告错误。 - 还是在孩子中,我们
exec()
“./write_file.py”。该进程现在不再运行我们的任何代码(除非我们执行失败,在这种情况下我们exit()
将错误报告给父进程)。 - 父进程将
wait()
让子进程终止,并处理其退出代码($?
至少将其复制到 )。
因此,重定向必须发生在 和 之间的子级中fork()
:exec()
它不能发生在之前,fork()
因为它不能更改 shell 的标准输出,也不能发生在之后,exec()
因为文件名和 shell 的可执行代码现在已被 Python 程序替换。父级无权访问子级的文件描述符(即使可以访问,也不能保证在exec()
第一次写入 stdout 之间进行重定向)。
答案4
我很遗憾地告诉你,情况恰恰相反。 shell 需要首先打开它的 I/O,然后将控制权传递给程序。
tee
在这种情况下可能会有所帮助:./write_file.py | tee -a ~root/log > /dev/null