如果重定向失败,Bash 程序不会执行

如果重定向失败,Bash 程序不会执行

在 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 中发生的事情是:

  1. 我们(外壳)fork();子进程从其父进程(shell)继承打开的文件描述符。
  2. 在子进程中,我们fopen()(扩展)“~root/log”,并将dup2()其设置为fd 1(以及close()临时fd)。如果fopen()失败,请致电exit()向家长报告错误。
  3. 还是在孩子中,我们exec()“./write_file.py”。该进程现在不再运行我们的任何代码(除非我们执行失败,在这种情况下我们exit()将错误报告给父进程)。
  4. 父进程将wait()让子进程终止,并处理其退出代码($?至少将其复制到 )。

因此,重定向必须发生在 和 之间的子级中fork()exec()它不能发生在之前,fork()因为它不能更改 shell 的标准输出,也不能发生在之后,exec()因为文件名和 shell 的可执行代码现在已被 Python 程序替换。父级无权访问子级的文件描述符(即使可以访问,也不能保证在exec()第一次写入 stdout 之间进行重定向)。

答案4

我很遗憾地告诉你,情况恰恰相反。 shell 需要首先打开它的 I/O,然后将控制权传递给程序。

tee在这种情况下可能会有所帮助:./write_file.py | tee -a ~root/log > /dev/null

相关内容