我使用 Bash 5.1.8。运行man
显示手册页但出现以下错误
man ps
sh: bat: line 10: syntax error near unexpected token `('
sh: bat: line 10: ` *.?(ba)sh)'
sh: error importing function definition for `bat'
我认为某些 shell ( sh
) 认为 Bashism 令人讨厌。如果我从以下位置删除 Bash-isms,这些错误就会消失~/.bashrc
:
function bat {
# lines snipped for brevity
case "$f" in
*.rs ) opt_syntax="--syntax=rust";;
*.?(ba)sh ) opt_syntax="--syntax=shellscript";;
*.?(m)m ) opt_syntax="--syntax=objc";;
esac
# lines snipped for brevity
}
export -f bat
我确信.bashrc
它本身没有问题,因为 Bash 启动时我没有看到任何错误或警告。进一步调试我注意到.profile
采购.bashrc
# source Bash customizations
[ -n "${BASH_VERSION}" ] && [ -r "${HOME}/.bashrc" ] && . "${HOME}/.bashrc"
这是我的~/.bashrc
开始方式
# If not running interactively, don't do anything
[[ "$-" != *i* ]] && return
问题:
- 为什么
man
必须.profile
在开始之前获取资源? - 尽管进行了两次检查,为什么上述代码是由非 Bash shell 解析的?
- 当不是 Bash 时检查
.profile
不获取源代码.bashrc
.bashrc
非交互时签入以停止进一步处理
- 当不是 Bash 时检查
从 @muru 的评论中,我意识到我不应该在导出的函数中包含 Bashism,因为存在被非 Bash shell 导入的风险。一个仍然存在的问题:为什么man
打电话sh
?。
答案1
man
不读取你的~/.profile
,但它会运行sh
来解释一些命令行(或者解释nroff
至少在我的系统上是一个sh
脚本包装器groff
),并且在你的系统上sh
恰好是bash
,进口bash
由's导出的函数export -f
(在自 shellshock 以来命名的变量中BASH_FUNC_funcname%%
),即使作为sh
.
在这里,您要导出一个函数,其语法取决于该extglob
选项。因此,当sh
(bash
在sh
模式下)启动并导入所有导出的函数时,它无法解析它,因为extglob
它不是默认启用的选项(sh
无论是否在模式下)。
IOW,导出函数意味着该函数将在所有调用bash
以及sh
使用sh
.bash
因此,您需要小心这些函数中的语法与默认设置中的bash
和sh
-as-兼容bash
,或者完全避免导出函数,因为即使这些函数永远不会最终被调用,这些函数中的代码也会被解析。
看:
$ env 'BASH_FUNC_f%%=() { case x in ?(x)) echo x; esac; }' bash -O extglob -c f
x
$ env 'BASH_FUNC_f%%=() { case x in ?(x)) echo x; esac; }' bash -c f
bash: f: line 0: syntax error near unexpected token `('
bash: f: line 0: `f () { case x in ?(x)) echo x; esac; }'
bash: error importing function definition for `f'
bash: line 1: f: command not found
如果您的函数需要像 一样影响语法解析的非默认选项extglob
,您可以将其定义为:
f() (
shopt -s extglob
eval '
function body here
'
)
export -f f
这里在子 shell 中运行代码,以便仅在函数执行期间设置选项(bash
,与选项相反zsh
或ksh
没有本地范围的选项(除了set
最近版本中设置的选项)),并使用eval
延迟解析直到调用该函数并extglob
设置选项。
在这里,你还可以这样做:
f() (
shopt -s extglob
pattern='*.?(ba)sh'
case ... in
($pattern)...
esac
)
不过,你也可以这样做:
f() {
case ... in
(*.sh | *.bash) ...
esac
}
这是sh
不需要extglob
.
答案2
不要导出函数,尤其是需要非默认 bash 配置的函数。
导出函数是 bash 功能,因此它们不会影响其他 shell。但它们会影响 bash 的所有调用。您的函数需要extglob
启用该选项,否则不仅运行该函数失败,甚至解析函数定义失败。当函数被导出时,bash 在开始执行任何事情之前解析函数定义。
shellsh
是unix 系统的粘合代码。它不仅用于交互使用,还在幕后广泛使用。程序调用sh
有多种目的。man
调用多个伴随程序,其中任何一个都可能调用 shell。例如,nroff
Linux 上的通常实现(这是将手册页的源代码转换为终端上格式化的文本的主要命令)是一个 shell 脚本,它通过一些选项调用groff
(的 GNU 实现nroff
)以使其与一个传统的nroff
.这会调用sh
,根据发行版的不同,它可能是 dash、bash(或者,在 Linux 上更罕见的是其他一些实现)。
对于交互式使用,bash 加载.bashrc
,因此在环境中拥有函数是没有意义的:将它们的定义放入.bashrc
,或源自.bashrc
.对于您自己的脚本,将函数定义放入您选择的文件中(不是.profile
or.bashrc
本身,因为它们包含不应为每个脚本执行的内容),并在脚本中放入source
or命令。.
导出函数定义几乎没有什么好处:因为您可以控制使用它们的脚本,所以您可以使它们获取定义所在的位置。如果 bash 在默认设置下无法解析该函数,则该函数不可能工作。