That's because .
is a special builtin. That is a POSIX requirement. Most other POSIX shells also do it, but in the case of bash
and zsh
only when they're in their respective posix/sh/ksh mode (including when they're invoked as sh
).
To remove the specialness of a special builtin, the POSIX way is to prefix it with command
:
$ dash -c 'command . /x; echo here'
dash: 1: .: cannot open /x: No such file
here
That won't prevent the shell from exiting if the sourced file calls exit
or runs into fatal errors when interpreting its contents. In the case of dash (contrary to bash -o posix
for instance), failing special builtins in the sourced file don't cause the shell to exit though as if they were also prefixed with command
.
Beware that if the errexit
option is also enabled, the shell will still exit if the file to source can't be opened, but this time because command
returns with a non-zero exit status:
$ dash -o errexit -c 'command . /x; echo "$? here"'
dash: 1: .: cannot open /x: No such file
The work around is to handle the error:
$ dash -o errexit -c 'command . /x || echo "that is fine"; echo "$? here"'
dash: 1: .: cannot open /x: No such file
that is fine
0 here
Or:
$ dash -o errexit -c 'command . /x && : non-fatal; echo "$? here"'
dash: 1: .: cannot open /x: No such file
2 here
(Or calling command .
in the condition part of a if
/while
/until
statement).
In the case of dash
(current versions at least, contrary to bash -o posix
for instance), that doesn't cancel the effect of errexit
for the evaluation of code in the sourced file:
$ dash -ec 'command . ./file || echo fine; echo here'
before
$ bash -o posix -ec 'command . ./file || echo fine; echo x'
before
after
x
One of the many reasons errexit
is best avoided except maybe for the simplest of scripts.
Beware that zsh's command
predates POSIX' one and has different semantics. You can't use command .
there if not in sh emulation / POSIX mode.
To avoid the error, rather than trying and do a check beforehand which comes with TOCTOU race, you could open the file on some fd whilst stderr is discarded and source the corresponding /dev/fd/n
file:
$ dash -c '{ command . /dev/fd/3; } 4>&2 2> /dev/null 3< file 2>&4 4>&-; echo here'
here
When a redirection fails, the redirected command (here a command group) is not executed, so the shell doesn't attempt to execute that special builtin, avoiding the side effect of exiting the shell. We still add a command
prefix in case the OS is Linux or Cygwin where opening /dev/fd/x
is not like a dup(x)
and may still fail if the permissions of the file have changed since it was opened on fd 3.