Bash program not executed if redirection would fail

It's not really a question of ordering checks, simply the order in which the shell sets things up. Redirections are set up before the command is run; so in your example, the shell tries to open ~root/log for appending before trying to do anything involving ./write_file.py. Since the log file can't be opened, the redirection fails and the shell stops processing the command line at that point.

One way to demonstrate this is to take a non-executable file and attempt to run it:

$ touch demo
$ ./demo
zsh: permission denied: ./demo
$ ./demo > ~root/log
zsh: permission denied: /root/log

This shows that the shell doesn't even look at ./demo when the redirection can't be set up.


From the bash man page, section REDIRECTION (emphasis by me):

Before a command is executed, its input and output may be redirected using a special notation interpreted by the shell.

...

A failure to open or create a file causes the redirection to fail.

So the shell tries to open the target file for stdout, which fails, and the command isn't executed at all.


It's worth observing that the shell must establish redirections before starting the program.

Consider your example:

./write_file.py >> ~root/log

What happens in the shell is:

  1. We (the shell) fork(); the child process inherits the open file descriptors from its parent (the shell).
  2. In the child process, we fopen() (the expansion of) "~root/log", and dup2() it to fd 1 (and close() the temporary fd). If the fopen() fails, call exit() to report the error to the parent.
  3. Still in the child, we exec() "./write_file.py". This process is now no longer running any of our code (unless we failed to execute, in which case we exit() to report the error to the parent).
  4. The parent will wait() for the child to terminate, and handle its exit code (by copying it into $?, at least).

So the redirection has to happen in the child between fork() and exec(): it can't happen before fork() because it must not change the shell's stdout, and it can't happen after exec() because the filename and the shell's executable code have now been replaced by the Python program. The parent has no access to the file descriptors of the child (and even if it did, it couldn't guarantee to redirect between exec() and the first write to stdout).