为什么 man 抱怨 .profile 中定义的函数?

为什么 man 抱怨 .profile 中定义的函数?

我使用 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

问题:

  1. 为什么man必须.profile在开始之前获取资源?
  2. 尽管进行了两次检查,为什么上述代码是由非 Bash shell 解析的?
    1. 当不是 Bash 时检查.profile不获取源代码.bashrc
    2. .bashrc非交互时签入以停止进一步处理

从 @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选项。因此,当shbashsh模式下)启动并导入所有导出的函数时,它无法解析它,因为extglob它不是默认启用的选项(sh无论是否在模式下)。

IOW,导出函数意味着该函数将在所有调用bash以及sh使用sh.bash因此,您需要小心这些函数中的语法与默认设置中的bashsh-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,与选项相反zshksh没有本地范围的选项(除了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。例如,nroffLinux 上的通常实现(这是将手册页的源代码转换为终端上格式化的文本的主要命令)是一个 shell 脚本,它通过一些选项调用groff(的 GNU 实现nroff)以使其与一个传统的nroff.这会调用sh,根据发行版的不同,它可能是 dash、bash(或者,在 Linux 上更罕见的是其他一些实现)。

对于交互式使用,bash 加载.bashrc,因此在环境中拥有函数是没有意义的:将它们的定义放入.bashrc,或源自.bashrc.对于您自己的脚本,将函数定义放入您选择的文件中(不是.profileor.bashrc本身,因为它们包含不应为每个脚本执行的内容),并在脚本中放入sourceor命令。.导出函数定义几乎没有什么好处:因为您可以控制使用它们的脚本,所以您可以使它们获取定义所在的位置。如果 bash 在默认设置下无法解析该函数​​,则该函数不可能工作。

相关内容