如何加载此脚本文件的函数而不必每次都获取它?
foo
我创建了一个包含我想要运行的脚本函数的文件。它位于/usr/bin
PATH 中。
文件富:
#!/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 中,例如bash
和zsh
)当前 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 )
因为它们自动继承其父级的环境,而 - 如上所述 - 执行的子级需要显式export
ed。
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