Bash source -- select the right function when two sourced files have the same function name?

5,101

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
Share:
5,101

Related videos on Youtube

Jarek
Author by

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, 2022

Comments

  • Jarek
    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 script file2.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 (if file2.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 sources file1.sh to use "foo" from file2.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 when file2.sh is not sourced by the main script.)

    • Stéphane Chazelas
      Stéphane Chazelas almost 11 years
      Why do your files have .sh extensions if they are bash scripts? Are they sh or bash scripts?
    • Jarek
      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
      Stéphane Chazelas almost 11 years
      If they are not written in sh syntax, I'd find using a .sh extension misleading. If using bash 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
    Jarek almost 11 years
    both of your suggestions are very helpful. I will experiment with both of them.
  • terdon
    terdon almost 11 years
    Isn'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
    Stéphane Chazelas almost 11 years
    @terdon, my understanding was that the OP had no control on the order the file1.sh and file2.sh were sourced and they could be sourced multiple time from scripts he could not modify (why we don't know).
  • daveloyall
    daveloyall over 9 years
    A 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.