- FAIL
find ${settings_base} -type f -exec source {} \;
find: ‘source’: No such file or directory
- PASS
for f in $(find ${settings_base} -type f); do
source "${f}"
done
I just want to know the reason for FAIL.
find ${settings_base} -type f -exec source {} \;
find: ‘source’: No such file or directory
for f in $(find ${settings_base} -type f); do
source "${f}"
done
I just want to know the reason for FAIL.
The -exec
predicate of find
runs a subprocess. But source
cannot be run as a subprocess.
Shell built-ins like source
(and cd
, etc) modify the environment of the currently running shell instance. They don't exist as external commands (there is no /bin/source
or /usr/bin/source
etc) because they cannot work as external commands. Recall that a subprocess cannot modify the environment of its parent process, for reasons of security as well as for architectural reasons (see also e.g. Global environment variables in a shell script for background). So as a matter of fact, even if find
could somehow execute source
, it could only modify the environment of the find
process, and then when find
exits, any effects of that would be lost, and your currently running shell instance would be back where it was before you ran find
.
The workaround you already discovered is the way to do it, though for complete robustness, you would also have to guard against whitespace or other shell metacharacters in the find
output. https://mywiki.wooledge.org/BashFAQ/020 has a detailed discussion, but I won't repeat all the nuances and corner cases here; click through to the linked page for that.
while IFS= read -r -d '' f; do
source "$f"
done < <(find $settings_base -type f -print0)
(Notice that -print0
is a GNU extension, and so not properly portable to various non-Linux platforms.)
Depending on what $settings_base
contains, it should probably be double-quoted; if it contains multiple values, put them in an array and use "${settings_base_array[@]}"
. See also When to wrap quotes around a shell variable?
If you are using Bash 4.3 (released in 2014) or later you can do what you want without using find
:
#! /bin/bash -p
settings_base=$1
shopt -s dotglob globstar
for f in "$settings_base"/**; do
[[ -f $f ]] && source -- "$f"
done
shopt -s ...
enables some Bash settings that are required by the code:
dotglob
enables globs to match files and directories that begin with .
. find
processes such files by default.globstar
enables the use of **
to match paths recursively through directory trees. This option was introduced with Bash 4.0 (released in 2009) but it is dangerous to use in versions before 4.3 (released in 2014) because it follows symlinks.See glob - Greg's Wiki for more information about the globstar
and dotglob
settings.