66

I need to allow several applications to append to a system variable ($PYTHONPATH in this case). I'm thinking of designating a directory where each app can add a module (e.g. .bash_profile_modulename). Tried something like this in ~/.bash_profile:

find /home/mike/ -name ".bash_profile_*" | while read FILE; do
source "$FILE"
done;

but it doesn't appear to work.

serenesat
  • 4,611
  • 10
  • 37
  • 53
Mike Bannister
  • 1,424
  • 1
  • 13
  • 16
  • 1
    A quick test script works for me. Can you be more specific as to what your files contain and what "doesn't appear to work" means? – Dennis Williamson Sep 14 '09 at 19:21
  • The files look like: export PYTHONPATH=/testpath/:$PYTHONPATH Problem is that it doesn't add /testpath/ to the PYTHONPATH. – Mike Bannister Sep 14 '09 at 19:35
  • 5
    Just to explain why the original script doesn't work: the files are sourced in a while loop in a pipeline; bash builtins used in pipelines execute in subshells, so anything defined by the sourced files disappears when the subshell exits. The solution (as in the answers below) is to eliminate the pipeline. – Gordon Davisson Sep 14 '09 at 23:49
  • Very simmilar to this question: http://stackoverflow.com/q/20796200/1695680 – ThorSummoner Oct 22 '15 at 17:04
  • This "didn't appear to work" for me, but it was working. One of my completion scripts was broken and was undoing the work of the earlier ones. So this is something to check for. Specifically the hub command's bash completions were overriding git's, and didn't work with the newest git version. – rjmunro Nov 23 '21 at 11:47

8 Answers8

106

Wouldn't

 for f in ~/.bash_profile_*; do source $f; done

be sufficient?

Edit: Extra layer of ls ~/.bash_* simplified to direct bash globbing.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • 5
    for f in ~/.bash_profile_*; do source $f; done should work too; bash can handle the globbing. – Emil Sit Sep 14 '09 at 19:35
  • This is a super way to handle homebrew's `/etc` folder, which contains bash completions for managed packages – New Alexandria Aug 08 '13 at 14:28
  • I have better solution that works with different shells and also that does not give errors if there are not files matching the pattern: http://stackoverflow.com/a/42986004/99834 – sorin Mar 23 '17 at 20:08
  • And then you can do `unset f` to get rid of this `f` variable. – Seninha May 21 '18 at 00:49
  • I don't know why, but this doesn't works for me. This one work > https://serverfault.com/a/41524/355902 – callmemath Jun 14 '18 at 05:01
  • 1
    this does not work when there are too many files or the concat string of filenames is larger than the command line permits. – Richard Sep 24 '19 at 18:26
23

Oneliner (only for bash/zsh):

source <(cat *)
gaRex
  • 4,144
  • 25
  • 37
  • This doesn't work for Bash. You get an error "name": Is a directory – Balaji Boggaram Ramanarayan Mar 06 '18 at 22:08
  • 2
    @BalajiBoggaramRamanarayan of cause if you have directories there! Question is about files. Find mask that will include only files. – gaRex Mar 07 '18 at 05:25
  • Isn't this going to run them all together as a single script? That seems like it could have unintended consequences. – mckeed May 06 '21 at 17:34
  • @mckeed Given that `source` executes the given file within the same shell anyway, there should not be difference between running individual `source` statements or a single one on the concatenated files. A problem can arise here, however, if some of the files do not end with a newline. So although that's a cute code-golf example, it's not 100% reliable. – KT. Sep 20 '21 at 00:11
21

I agree with Dennis above; your solution should work (although the semicolon after "done" shouldn't be necessary). However, you can also use a for loop

for f in /path/to/dir*; do
   . $f
done

The command substitution of ls is not necessary, as in Dirk's answer. This is the mechanism used, for example, in /etc/bash_completion to source other bash completion scripts in /etc/bash_completion.d

Cascabel
  • 479,068
  • 72
  • 370
  • 318
3
for file in "$(find . -maxdepth 1 -name '*.sh' -print -quit)"; do source $file; done

This solution is the most postable I ever found, so far:

  • It does not give any error if there are no files matching
  • works with multiple shells including bash, zsh
  • cross platform (Linux, MacOS, ...)
Edric
  • 24,639
  • 13
  • 81
  • 91
sorin
  • 161,544
  • 178
  • 535
  • 806
  • 1
    does not work with long filenames or long concat list – Richard Sep 24 '19 at 18:30
  • Add `-type f` to ensure the file is a file and not a directory called `foo.sh`! – mazunki Jul 15 '20 at 07:01
  • @mazunki if you add -type option and the folder is empty, an "not enough arguments" error is thrown. Without adding -type option no error is thrown like described in the original post by sorin. – Josef Glatz Nov 23 '20 at 04:37
2

You can use this function to source all files (if any) in a directory:

source_files_in() {
    local dir="$1"

    if [[ -d "$dir" && -r "$dir" && -x "$dir" ]]; then
        for file in "$dir"/*; do
           [[ -f "$file" && -r "$file" ]] && . "$file"
        done
    fi
}

The extra file checks handle the corner case where the pattern does not match due to the directory being empty (which makes the loop variable expand to the pattern string itself).

Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
1
str="$(find . -type f -name '*.sh' -print)"
arr=( $str )
for f in "${arr[@]}"; do
   [[ -f $f ]] && . $f --source-only || echo "$f not found"
done 

I tested this Script and I am using it. Just modifiy the . after find to point to your folder with your scripts and it will work.

-2

ok so what i ended up doing;

eval "$(find perf-tests/ -type f -iname "*.test" | while read af; do echo "source $af"; done)"

this will execute a source in the current shell and maintian all variables...

khr055
  • 28,690
  • 16
  • 36
  • 48
mikejonesey
  • 190
  • 1
  • 5
-7

I think you should just be able to do

source ~/.bash_profile_*

Yisrael Dov
  • 2,369
  • 1
  • 16
  • 13
  • 5
    I was thinking the same, but I tested and could not get it to work. It appears to me `source` only takes one argument (and ignores superfluous arguments). I'm pretty sure that kind of sucks. – bryn Jan 28 '14 at 21:03