Bash source -- select the right function when two sourced files have the same function name?
Solution 1
You can flag your function as 'read only' in file2.sh.
Note: this will cause warnings when file1.sh later tries to define (redefine) the function.
Those warnings will appear on stderr and could cause trouble. I don't know if they can be disabled.
Further note: this MIGHT cause the scripts to fail, if they are checking the return status of the function definition. I think that there is also a bash option that can be set that would cause a non-zero return status anywhere to abort execution of the script. Good luck!
Can you modify file1.sh? Simply using conditionals to check if the function is defined before defining it would be a more robust solution.
Here is an example of usage of 'readonly'.
hobbes@metalbaby:~/scratch$ mkdir bash-source-test
hobbes@metalbaby:~/scratch$ cd bash-source-test
hobbes@metalbaby:~/scratch/bash-source-test$ cat > file2.sh
#!/bin/bash
fname(){
echo "file2"
}
readonly -f fname
hobbes@metalbaby:~/scratch/bash-source-test$ cat > file1.sh
#!/bin/bash
fname(){
echo "file1"
}
readonly -f fname
hobbes@metalbaby:~/scratch/bash-source-test$ cat >top.sh
#!/bin/bash
if [ $1 ]; then
source file2.sh
fi
source file1.sh
fname
hobbes@metalbaby:~/scratch/bash-source-test$ chmod +x *.sh
hobbes@metalbaby:~/scratch/bash-source-test$ ./top.sh
file1
hobbes@metalbaby:~/scratch/bash-source-test$ ./top.sh hello
file1.sh: line 4: fname: readonly function
file2
hobbes@metalbaby:~/scratch/bash-source-test$
Solution 2
I have never written anything but small bash scripts for basic sysadmin and automation tasks so I have very little experience with sourcing functions from other files. This may therefore be very naive or not portable or something but, at least on my system and in most programming languages I know, if you redefine something it stays redefined.
In other words, as long as you make sure you source file1.sh
before file2.sh
and export the function afterwards, you should always be exporting the function from file2.sh
if it has been loaded and from file1.sh
if it has not:
$ cat file1.sh
#!/bin/bash
fname(){
echo "BBBBB"
}
$ cat file2.sh
#!/bin/bash
fname(){
echo "CCCCC"
}
$ cat foobar.sh
#!/bin/bash
source file1.sh
if [ $1 ]; then
source file2.sh;
fi
export -f fname;
./run_function.sh
$ cat run_function.sh
#!/bin/bash
fname
Any scripts called (eg ./run_function.sh
) will have whichever fname
was defined last, so they will load the one from file2.sh
if it has been sourced and from file.sh
if it has not:
$ ./foobar.sh
BBBBB
$ ./foobar.sh hello
CCCCC
Solution 3
If those scripts are indeed interpreted by bash
(and not in sh
emulation mode), and if file1.sh
defines but doesn't use foo
, you could redefine the source
and .
commands in such a way that upon sourcing file1.sh
, it makes sure file1.sh
doesn't redefine foo
, for instance by aliasing it away to something else:
$ cat file1.sh
foo() { echo original foo; }
$ cat file2.sh
foo() { echo new foo; }
$ cat main.sh
file2_sourced=false
source() {
case ${1##*/} in
(file2.sh)
file2_sourced=true;;
(file1.sh)
if "$file2_sourced"; then
alias foo=original_foo
builtin source "$@"; local "ret=$?"
unalias foo
return "$ret"
fi;;
esac
builtin source "$@"
}
.() { source "$@"; }
source file2.sh
source file1.sh
foo
original_foo
$ bash main.sh
new foo
original foo
Or possibly more appropriate:
source() {
shopt -s expand_aliases
if [ "${1##*/}" = file1.sh ] && command -v foo > /dev/null 2>&1; then
# foo already defined, make file1.sh define original_foo this time
alias foo=original_foo
builtin source "$@"; local "ret=$?"
unalias foo
return "$ret"
else
builtin source "$@"
fi
}
That is: only prevent file1.sh
from re-defining foo
.
Now, if those other scripts that main.sh
calls are executed as opposed to being sourced, you'd need to export those source
and .
functions in main.sh
:
export -f source .
Actually, it's also possible to do it without modifying main.sh
, by calling it as:
env BASHOPTS=expand_aliases source='() {
if [ "${1##*/}" = file1.sh ] && command -v foo > /dev/null 2>&1; then
# foo already defined, make file1.sh define original_foo this time
alias foo=original_foo
builtin source "$@"; local "ret=$?"
unalias foo
return "$ret"
else
builtin source "$@"
fi
}' .='() { source "$@"; }' bash main.sh
Related videos on Youtube
Jarek
You may be interested in the story of SE moderator Monica Cellio and how she was unfairly treated by the corporate management of this site. More info here. An update is available. Let's hope we can cultivate a more fair environment for content creators and moderators going forward.
Updated on September 18, 2022Comments
-
Jarek over 1 year
My bash script sources a script file (call it
file2.sh
) according to an argument. (It is either sourced or not.) The scriptfile2.sh
contains a function "foo" (call it a modified or improved version of foo).The script also sources another file (
file1.sh
) which contains the original function "foo". This one (file1.sh
) is always sourced (and it has other functions that are required).I need the function "foo" in
file2.sh
to override the "foo" in file1.sh (iffile2.sh
is sourced).Furthermore, I need to do this in any scripts that my main script calls. Several of those called script files also source
file1.sh
. They are expecting the original "foo" function. But I need to make them call the improved "foo" without modifying them.In other words, if my main script includes
file2.sh
, I want any script (which is called by my main script) that sourcesfile1.sh
to use "foo" fromfile2.sh
instead. And I can't change any of the other files except my main script. (Or if I do change them, I need them to work correctly whenfile2.sh
is not sourced by the main script.)-
Stéphane Chazelas almost 11 yearsWhy do your files have
.sh
extensions if they arebash
scripts? Are theysh
orbash
scripts? -
Jarek almost 11 years@Stephane Chazelas: These are bash scripts. What is the right file extension? And what is wrong with the ".sh" extension?
-
Stéphane Chazelas almost 11 yearsIf they are not written in
sh
syntax, I'd find using a.sh
extension misleading. If usingbash
specific syntax, a.bash
extension would make more sense.bash
and shells in general don't care anyway how the files are named, it's just a cosmetic issue.
-
-
Jarek almost 11 yearsboth of your suggestions are very helpful. I will experiment with both of them.
-
terdon almost 11 yearsIsn't simply sourcing the files in order as in my answer enough? I have the sneaky suspicion that it isn't since both you and @daveloyall chose much more complex routes.
-
Stéphane Chazelas almost 11 years@terdon, my understanding was that the OP had no control on the order the
file1.sh
andfile2.sh
were sourced and they could be sourced multiple time from scripts he could not modify (why we don't know). -
daveloyall over 9 yearsA recent edit removed the bash warning message regarding the attempt to alter a readonly function. I rolled back the edit because the purpose of my answer is to demonstrate the mechanics of read-only functions.