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

leg*_*s2k 7 bash 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'
Run Code Online (Sandbox Code Playgroud)

我认为某些 shell ( sh) 发现 Bashisms 令人讨厌。如果我从以下内容中删除 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
Run Code Online (Sandbox Code Playgroud)

我确信.bashrc它本身没有问题,因为在 Bash 启动时我没有看到任何错误或警告。进一步调试我注意到.profile采购.bashrc

# source Bash customizations
[ -n "${BASH_VERSION}" ] && [ -r "${HOME}/.bashrc" ] && . "${HOME}/.bashrc"
Run Code Online (Sandbox Code Playgroud)

这是我的~/.bashrc开始

# If not running interactively, don't do anything
[[ "$-" != *i* ]] && return
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 为什么man必须.profile在开始之前找到源?
  2. 尽管进行了两次检查,为什么上述代码会被非 Bash shell 解析?
    1. 检查.profile不要源.bashrc时,它不是猛砸
    2. .bashrc非交互式时签入以停止进一步处理

从@muru 的评论中,我意识到我不应该在导出的函数中使用 Bashism,因为它存在被非 Bash shell 导入的风险。一个仍然存在的问题:为什么man调用sh.

Sté*_*las 17

man不读书的~/.profile,但它将运行sh解释一些命令行(或解释nroff,至少在我的系统是一个sh脚本包装到groff),并在系统中sh恰好是bash,其进口功能通过出口bashexport -f(在命名变量BASH_FUNC_funcname%%,因为shellshock),即使作为sh.

在这里,您要导出一个语法取决于extglob选项的函数。因此,当sh( bashin shmode) 启动并导入所有导出的函数时,它无法解析它,因为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
Run Code Online (Sandbox Code Playgroud)

如果您的函数需要影响语法解析的非默认选项,例如extglob,您可以将其定义为:

f() (
  shopt -s extglob
  eval '
    function body here
  '
)
export -f f
Run Code Online (Sandbox Code Playgroud)

这里在子shell中运行代码,以便仅在函数执行期间设置选项(bash,与选项的本地范围相反zshksh没有本地范围(除了set最近版本中设置的选项)),并使用eval延迟解析直到调用函数并设置extglob选项。

在这里,您还可以执行以下操作:

f() (
  shopt -s extglob
  pattern='*.?(ba)sh'
  case ... in
    ($pattern)...
  esac
)
Run Code Online (Sandbox Code Playgroud)

不过,你也可以这样做:

f() {
  case ... in
    (*.sh | *.bash) ...
  esac
}
Run Code Online (Sandbox Code Playgroud)

这是sh不需要extglob.


Gil*_*il' 12

不要导出函数,尤其是需要非默认 bash 配置的函数。

导出的函数是 bash 功能,因此它们不会影响其他 shell。但它们会影响 bash 的所有调用。您的函数需要extglob启用该选项,否则不仅运行函数失败,甚至解析函数定义也会失败。当函数被导出时,bash 会在开始执行任何操作之前解析函数定义。

所述sh外壳是用于UNIX系统中的粘合代码。它不仅用于交互使用,在幕后也大量使用。程序sh出于多种目的而调用。man调用几个伴随程序,它们中的任何一个都可能调用一个 shell。例如,nroffLinux 上的(这是将手册页的源代码转换为终端格式的文本的主要命令)的通常实现是一个 shell 脚本,它调用groff( 的 GNU 实现nroff)和一些选项以使其与一个传统的nroff。这会调用sh,这取决于发行版可能是 dash、bash(或者,在 Linux 上很少见,其他一些实现)。

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