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

mrg*_*gnw 5 shell shell-script function

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

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

文件foo

#!/bin/bash
echo "Running foo"

function x {
    echo "x"
}
Run Code Online (Sandbox Code Playgroud)

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

当我输入foo
Running foo出现时(所以文件在 PATH 中并且是可执行的)

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

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

mik*_*erv 8

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

当您. ./somescript.sh (或source在某些 shell 中,例如bashzsh当前 shell 将脚本读入当前环境并执行内容时,就像从提示符发出一样 -x() { ... ; }在当前 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
Run Code Online (Sandbox Code Playgroud)

以同样的方式( : 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.
Run Code Online (Sandbox Code Playgroud)

...这与您所做的几乎完全一样,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
Run Code Online (Sandbox Code Playgroud)

这种行为不同于( : subshells )在提示符下运行或作为当前 shell 的子进程运行的行为,因为它们会自动继承其父进程的环境,而 - 如上所述 - 执行的子进程需要显式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.
Run Code Online (Sandbox Code Playgroud)

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

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

eval "$(cat ./script)"
Run Code Online (Sandbox Code Playgroud)

但这实际上与. ./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.
Run Code Online (Sandbox Code Playgroud)

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

. /usr/bin/foo
Run Code Online (Sandbox Code Playgroud)

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


Wil*_*ard 5

mikeserv 的回答对于“幕后”发生的事情的细节很有帮助,但我觉得这里的另一个答案是有道理的,因为它不包含对确切标题问题的简单可用的答案:

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

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

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

if [ -d ~/.bash_functions ]; then
  for file in ~/.bash_functions/*.sh; do
    . "$file"
  done
fi
Run Code Online (Sandbox Code Playgroud)