如何加载此脚本文件的函数而不必每次都获取它? “找不到命令”(Bash/脚本基础知识)

如何加载此脚本文件的函数而不必每次都获取它? “找不到命令”(Bash/脚本基础知识)

如何加载此脚本文件的函数而不必每次都获取它?

foo我创建了一个包含我想要运行的脚本函数的文件。它位于/usr/binPATH 中。

文件:

#!/bin/bash
echo "Running foo"

function x {
    echo "x"
}

x但是,当我在终端中输入函数名称时:
x: command not found

当我输入foo:
Running foo显示时(因此该文件位于 PATH 中并且可执行)

输入后source foo,我可以输入x运行函数 x

我知道,这是一个非常基本的问题。我只是想抽象我的脚本,以便它们更易于管理(与将所有内容转储到 .profile 或 .bashrc 相比)。

答案1

您的问题是.../bin目录中的脚本exec在另一个 shell 环境中进行了编辑 - 它的环境无法在执行后保留下来,因此定义x() { ... ; }在完成时不会保留在当前的 shell 环境中。

当你. ./somescript.sh (或者source在某些 shell 中,例如bashzsh当前 shell 将脚本读入当前环境并执行内容,就像从提示符发出一样 - sox() { ... ; }是在当前 shell 环境 - 您的交互式 shell 中定义的。

基本上这个问题可以这样证明:

sh <<\HEREDOC_CMD_FILE #runs sh shell with heredoc input file
    { #begins current shell compound expression
        ( #begins subshell compound expression
            x() { echo 'I am function x.' ; } #define function x
            x #x invoked in subshell
        ) #ends subshell compound expression
        x #x invoked in current shell
    } #ends current shell compound expression
#v_end_v
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.
sh: line 6: x: command not found

同样,( : subshell )上例中定义的环境不会在完成后继续存在,您执行的脚本中定义的环境也不会。类似地,当sh读入时,HEREDOC_CMD_FILE它执行与您相同的功能source ../file,并在当前 shell 执行环境中运行其内容 - 尽管它的环境是发出运行它的提示符的 shell 的子 shell 子级,因此它的环境中没有任何内容也从那里完成。不过,您可以让它这样做,例如:

. /dev/fd/0 <<\HEREDOC_CMD_FILE && x
    x() { echo 'I am function x.' ; }
HEREDOC_CMD_FILE

###OUTPUT###
I am function x.

...这与您所做的几乎完全相同,source ${script}只是这里我们.dot获取的是磁盘上文件的内容stdin,而您获取的是磁盘上文件的内容

不过,执行脚本和执行脚本之间的主要区别( : subshell )在于执行脚本的执行环境是什么样的。当您执行脚本时,会提供一个新的操作环境 - 因此当前 shell 中声明的任何变量都不会转移到其环境中,除非在其命令行上显式导出或声明。这种行为可以这样证明:

{   x() { printf "$FMT" "$0" "$var2" x ; } #describe env
    export FMT='argv0: %s\t\tvar2: %s\t\tI am function %s.\n' \
        var1=val1 #var1 and FMT are explicitly exported
    var2=shell_val2 #var2 is not
    cat >./script && #read out stdin to >./script
        chmod +x ./script && #then make ./script executable
        var3=val3 ./script #then define var3 for ./script's env and execute
} <<\SCRIPT ; x ; y #send heredoc to block's stdin then call {x,y}()
#!/usr/bin/sh
#read out by cat to >./script then executed in a separate environment
y() { printf "$FMT" "$0" "$var2" y ; } #describe env
echo "${var1:-#var1 is unset or null}" #$var1 if not unset or null else :-this} 
echo "${var2:-#var2 is unset or null}"
echo "${var3:-#var3 is unset or null}"
export var2=script_val2
x ; y #run x() ; y() in script
#v_end_v                                                                                                                                                                                          
SCRIPT

###OUTPUT###
val1
#var2 is unset or null
val3
./script: line 8: x: command not found
argv0: ./script         var2: script_val2               I am function y.
argv0: sh               var2: shell_val2                I am function x.
sh: line 18: y: command not found

此行为与在提示符下运行或以其他方式作为当前 shell 的子级运行的行为不同,( : subshells )因为它们自动继承其父级的环境,而 - 如上所述 - 执行的子级需要显式exported。

var1=val1 ; export var2=val2
x() { echo 'I am function x.' ; }
( 
    printf '%s\n' "$var1" "$var2" 
    x
)

###OUTPUT###
val1
val2
I am function x.

我要注意的最后一件事是,没有可移植的方法将函数从父 shell 导出到执行的脚本,而无需像在当前 shell 中.dot那样在执行的脚本中获取包含函数定义的文件。source基本上,你不能像export function使用变量那样进行移植。不过,我想你可能会export fn_def='fn() { : fn body ; }'eval "$fn_def"你执行的脚本中。当然,任何子环境(执行或以其他方式执行)都会随子环境一起消亡。

因此,如果您想要脚本中定义的函数,但不想获取脚本本身,则必须将该函数读取为某个命令及其输出eval

eval "$(cat ./script)"

但这实际上是一样的. ./script——只是效率较低。您不妨直接获取它。

执行此操作的最佳方法是从脚本本身中删除函数定义,并将其放入自己的文件中,然后在需要时从脚本和当前 shell 中获取它。像这样:

{
    echo 'x() { echo "I am function x and my argv0 is ${0}." ; }' >./fn_x                                                                                                                                                         
    echo '. ./fn_x ; x' >./script                                                                                                                                                                                                 
    chmod +x ./script && ./script                                                                                                                                                                                                 
    . ./fn_x ; x                                                                                                                                                                                                                  
}
###OUTPUT###
I am function x and my argv0 is ./script.
I am function x and my argv0 is sh.

要使其在交互式 shell 中始终可用,请将 a 添加. /path/to/fn_x到 shell 的ENV文件中。例如,对于包含位于中的bash函数的脚本,您可以将此行添加到:foo/usr/bin~/.bashrc

. /usr/bin/foo

如果在启动期间脚本由于某种原因在该位置不可用,您的 shell 仍会ENV按预期读入并获取文件的其余部分,但会打印一条诊断消息,stderr让您知道获取函数时出现问题定义文件。

答案2

迈克塞夫的回答对于“幕后”发生的事情的细节很有好处,但我觉得这里的另一个答案是有必要的,因为它不包含对确切标题问题的简单可用答案:

如何加载此脚本文件的函数而不必每次都获取它?

答案是:从您.bashrc或您的那里获取它.bash_profile,以便在您运行的每个 shell 中都可用。

例如,我的 中有以下内容.bash_profile

if [ -d ~/.bash_functions ]; then
  for file in ~/.bash_functions/*.sh; do
    . "$file"
  done
fi

相关内容