When (or why) should you check for a file's existence before sourcing it?

In POSIX shells, . is a special builtin, so its failure causes the shell to exit (in some shells like bash, it's only done when in POSIX mode).

What qualifies as an error depends on the shell. Not all of them exit upon a syntax error when parsing the file, but most would exit when the sourced file can't be found or opened. I don't know of any that would exit if the last command in the sourced file returned with a non-zero exit status (unless the errexit option is on of course).

Here doing:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Is a case where you want to source the file if it's there, and don't if it's not (or is empty here with -s).

That is, it should not be considered an error (fatal error in POSIX shells) if the file is not there, that file is considered an optional file.

It would still be a (fatal) error if the file was not readable or was a directory or (in some shells) if there was a syntax error while parsing it which would be real error conditions that should be reported.

Some would argue that there's a race condition. But the only thing it means would be that the shell would exit with an error if the file is removed in between the [ and ., but I'd argue it's valid to consider it an error that this fixed path file would suddenly vanish while the script is running.

On the other hand,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

where command¹ removes the special attribute to the . command (so it doesn't exit the shell on error) would not work as:

  • it would hide .'s errors but also the errors of the commands run in the sourced file
  • it would also hide real error conditions like the file having the wrong permissions.

Other common syntaxes (see for instance grep -r /etc/default /etc/init* on Debian systems for the init scripts that haven't been converted to systemd yet (where EnvironmentFile=-/etc/default/service is used to specify an optional environment file instead)) include:

  • [ -e "$file" ] && . "$file"

    Check the file it's there, still source it if it's empty. Still fatal error if it can't be opened (even though it's there, or was there). You may see more variants like [ -f "$file" ] (exists and is a regular file), [ -r "$file" ] (is readable), or combinations of those.

  • [ ! -e "$file" ] || . "$file"

    A slightly better version. Makes it clearer that the file not existing is an OK case. That also means the $? will reflect the exit status of the last command run in $file (in the previous case, if you get 1, you don't know whether it's because $file didn't exist or if that command failed).

  • command . "$file"

    Expect the file to be there, but don't exit if it can't be interpreted.

  • [ ! -e "$file" ] || command . "$file"

    Combination of the above: it's OK if the file is not there, and for POSIX shells, failures to open (or parse) the file are reported but are not fatal (which may be more desirable for ~/.profile).


¹ Note: In zsh however, you can't use command like that unless in sh emulation; note that in the Korn shell, source is actually an alias for command ., a non-special variant of .


Maintainer of nvm's response:

it's easy to uninstall nvm by simply deleting the file; forcing additional work (to track down where the line(s) are that source nvm) doesn't seem particularly valuable.

My interpretation (combined with Stéphane’s excellent explanation and Kusalananda’s comment):

It’s simpler and safer.

It defends against POSIX shells exiting on startup due to a missing file (for various reasons). Those using non-POSIX (e.g. bash) shells can remove the conditional if they prefer.

Tags:

Shell Script