2
  1. FAIL
find ${settings_base} -type f -exec source {} \;
find: ‘source’: No such file or directory
  1. PASS
for f in $(find ${settings_base} -type f); do
    source "${f}"
done

I just want to know the reason for FAIL.

F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
k_vishwanath
  • 1,326
  • 2
  • 20
  • 28
  • The reason is that `source` is a shell built-in. – Poshi Aug 26 '23 at 07:26
  • @Poshi, I'm not sure you're right. I tried recreating it with `.` and that runs, but I do get the same error with `source`. @k_vishwanath, try with `.`? – Uberhumus Aug 26 '23 at 07:54
  • 3
    Whether or not it gets an error, it can't really work. The point of `source` (and the near-equivalent `.` command) is to run a script *in the current shell* rather than as a subprocess like normal. But `find` itself is already a subprocess of the current shell, so even if it were able to parse and execute shell commands itself (which it can't do), it cannot execute them in what, from its point of view, is the parent shell. – Gordon Davisson Aug 26 '23 at 08:22
  • @GordonDavisson, so what causes the difference? And how would you achieve the desired result? – Uberhumus Aug 26 '23 at 08:47
  • 1
    [Shellcheck](https://www.shellcheck.net/) identifies several problems with the code snippets in the question. The Shellcheck reports include links to more information about the problems and how to fix them. It's a good idea to run [Shellcheck](https://www.shellcheck.net/) on all new and modified shell code. – pjh Aug 26 '23 at 10:43
  • 1
    Well, if you think that `source` is not a built-in but a real program, can you point me to the location in your disk of the corresponding binary image of that command? If you manage to do an `ls` of that command, I'll buy your hypothesis. Anyways, as others said, if you manage to overcome this "small issue", then you will find that your final environment is in the same state as before, so the code won't work for your purposes even if it were not failing. – Poshi Aug 26 '23 at 20:54

2 Answers2

3

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?

tripleee
  • 175,061
  • 34
  • 275
  • 318
1

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.

pjh
  • 6,388
  • 2
  • 16
  • 17