Bash 源 - 当两个源文件具有相同函数名称时选择正确的函数?

Bash 源 - 当两个源文件具有相同函数名称时选择正确的函数?

file2.sh我的 bash 脚本根据参数获取脚本文件(称为)。 (它要么有源,要么没有。)该脚本file2.sh包含一个函数“foo”(将其称为 foo 的修改或改进版本)。

该脚本还获取另一个文件 ( file1.sh),其中包含原始函数“foo”。这个 ( file1.sh) 始终是有来源的(并且它具有所需的其他功能)。

我需要函数“foo”来file2.sh覆盖 file1.sh 中的“foo”(如果file2.sh有源)。

此外,我需要在主脚本调用的任何脚本中执行此操作。其中几个称为脚本文件的源代码也是file1.sh.他们期待原始的“foo”功能。但我需要让他们调用改进的“foo”而不修改它们。

换句话说,如果我的主脚本包含file2.sh,我希望任何源脚本(由我的主脚本调用)file1.sh使用“foo”来file2.sh代替。除了我的主脚本之外,我无法更改任何其他文件。 (或者,如果我确实更改了它们,我需要它们file2.sh在主脚本未提供来源时正常工作。)

答案1

您可以在 file2.sh 中将函数标记为“只读”。

注意:当 file1.sh 稍后尝试定义(重新定义)该函数时,这将导致警告。

这些警告将出现在 stderr 上并可能引起麻烦。不知道是否可以禁用。

进一步注意:如果脚本正在检查函数定义的返回状态,这可能会导致脚本失败。我认为还可以设置一个 bash 选项,该选项会在任何地方导致非零返回状态以中止脚本的执行。祝你好运!

你能修改file1.sh吗?在定义函数之前简单地使用条件来检查函数是否已定义将是一个更可靠的解决方案。

这是“只读”的使用示例。

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$ 

答案2

如果这些脚本确实被解释bash(并且不是在sh模拟模式下),并且如果file1.sh定义但没有使用 foo,您可以以这样的方式重新定义source和命令,即在 source 时,它​​确保不会重新定义,例如通过将其别名为其他内容:.file1.shfile1.shfoo

$ 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

或者可能更合适:

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
}

即:仅file1.sh防止关于-定义foo

现在,如果调用的其他脚本main.sh被处决而不是成为来源,您需要将这些source.函数导出到main.sh

export -f source .

其实也是可以做到的没有修改main.sh,将其称为:

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

答案3

除了用于基本系统管理和自动化任务的小型 bash 脚本之外,我从未编写过任何其他内容,因此我对从其他文件中获取函数的经验很少。因此,这可能非常幼稚或不可移植或其他什么,但至少在我的系统和我所知道的大多数编程语言中,如果您重新定义某些内容,它就会保持重新定义。

换句话说,只要你确保你的来源file1.sh file2.shfile2.sh然后导出该函数,如果已加载,则应始终导出该函数 ,file1.sh如果尚未加载,则应始终导出该函数:

$ 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

任何调用的脚本(例如./run_function.sh)都将具有fname定义的内容最后的,因此他们将加载该资源(file2.sh如果已获取)和file.sh如果尚未获取:

$ ./foobar.sh
BBBBB
$ ./foobar.sh hello
CCCCC

答案4

我认为这里最简单的答案可能是从这些脚本文件中获取该函数。

#file[12]
#foo() { : removed; }
. ./foo.fn
#and rest of script

#foo.fn
foo() { : old version is always defined; }
[ -z "${NEWFOO:+1}" ] ||
foo() { : new version defined only if $NEWFOO; }

#your caller script
export NEWFOO=1
case $srcarg in (./file1) unset NEWFOO;; esac
#or however ^that part^ is usually done
. "$srcarg"
#and rest of script

相关内容