7

I use Bash 5.1.8. Running man shows the manual page but with the following errors

man ps

sh: bat: line 10: syntax error near unexpected token `('
sh: bat: line 10: ` *.?(ba)sh)'
sh: error importing function definition for `bat'

I think some shell (sh) is finding Bashisms obnoxious. These errors vanish if I remove Bash-isms like the following from ~/.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

I'm sure .bashrc itself has no problems, as I see no errors or warnings when Bash starts. Debugging further I noticed .profile sourcing .bashrc

# source Bash customizations
[ -n "${BASH_VERSION}" ] && [ -r "${HOME}/.bashrc" ] && . "${HOME}/.bashrc"

Here's how my ~/.bashrc starts

# If not running interactively, don't do anything
[[ "$-" != *i* ]] && return

Questions:

  1. Why does man have to source .profile just before starting?
  2. Despite two checks why is the aforementioned code parsed by a non-Bash shell?
    1. Check in .profile to not source .bashrc when it's not Bash
    2. Check in .bashrc to stop further processing when non-interactive

From @muru's comments I realize that I should NOT have Bashisms in a function that's exported since there's a risk of it being imported by a non-Bash shell. A question that still remains: why man calls sh?.

6
  • 1
    "error importing function definition"? Is the function exported?
    – muru
    Jul 21, 2021 at 6:02
  • Yes, I do export -f bat. Aah... so it can't have Bash-isms after all?!
    – legends2k
    Jul 21, 2021 at 6:03
  • Bash invoked as sh will try to import function definitions, but some behaviour will change. See gnu.org/software/bash/manual/bash.html#Bash-POSIX-Mode
    – muru
    Jul 21, 2021 at 6:07
  • Removing the export does solve the issue. Will read about Bash's POSIX mode; thanks @muru! However, I still don't see why .profile is sourced by man.
    – legends2k
    Jul 21, 2021 at 6:14
  • 2
    I don't see .profile being sourced. If it was, the error would have mentioned the file being sourced. I think it's only the exported functions being imported by sh. Not sure why man is running sh, though. Maybe for paging?
    – muru
    Jul 21, 2021 at 6:16

2 Answers 2

17

man is not reading your ~/.profile but it will run sh to interpret some command lines (or to interpret nroff which at least on my system is a sh script wrapper to groff), and on your system sh happens to be bash, which imports functions exported by bash's export -f (in variables named BASH_FUNC_funcname%% since shellshock), even when running as sh.

Here, you're exporting a function whose syntax depends on the extglob option. So, when sh (bash in sh mode) starts and imports all the exported functions, it fails to parse it as extglob is not an options that is enabled by default (in sh mode or not).

IOW, exporting a function means that function will be available in all bash invocations and all sh invocations on systems where sh is implemented with bash. So you need to be careful that the syntax in those functions be compatibles with both bash and sh-as-bash in their default settings, or avoid exported functions altogether as the code in those functions will be parsed even if the functions will never end up being invoked.

See:

$ 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

If your function needs a non-default option that affects syntax parsing like extglob, you can define it as:

f() (
  shopt -s extglob
  eval '
    function body here
  '
)
export -f f

Here running the code in a subshell for the option to only be set during the execution of the function (bash, contrary to zsh or ksh has no local scope for options (other than the ones set with set in recent versions)), and using eval delays the parsing until the function is invoked and the extglob option set.

Here, you could also do:

f() (
  shopt -s extglob
  pattern='*.?(ba)sh'
  case ... in
    ($pattern)...
  esac
)

Though, you could also do:

f() {
  case ... in
    (*.sh | *.bash) ...
  esac
}

Which is standard sh syntax that doesn't require extglob.

4
  • When sh-as-bash is run, how'd it treat shopt -s extglob inside f()?
    – legends2k
    Jul 21, 2021 at 7:52
  • 1
    @legends2k, from a syntax point of view, shopt -s extglob is a simple command with 3 arguments, there's no shell that would fail to parse that command. If an sh invocation ended up running that f function, it would invoke the shopt command. Only bash imports those functions exported in BASH_FUNC_f%% variables, and shopt is a builtin of that shell, so it would not have any trouble running it even if running in posix mode. Jul 21, 2021 at 8:15
  • @StéphaneChazelas Do you believe it is a good idea to use opening case label brackets in example code? Opening brackets for case labels are permitted by POSIX, but no corectly working shell implementation needs them.
    – schily
    Jul 23, 2021 at 10:42
  • 1
    @schily, yes, I much prefer that syntax, it also helps for vi % matching. The asymmetric ones should be relegated to history IMO. Jul 23, 2021 at 12:44
12

Don't export functions, especially functions that require a non-default bash configuration.

Exported functions are a bash feature, so they don't affect other shells. But they affect all invocations of bash. Your function requires the extglob option to be enabled, otherwise not only does running the function fail, but even parsing the function definition fails. And when the function is exported, bash parses the function definition before it starts executing anything.

The sh shell is the glue code for unix systems. It's not just for interactive use, it's also used a lot behind the scenes. Programs invoke sh for many purposes. man invokes several companion programs, and any of them might invoke a shell. For example, the usual implementation of nroff (which is the main command to convert a man page's source code into text formatted on a terminal) on Linux is a shell script that invokes groff (the GNU implementation of nroff) with a few options to make it compatible with a traditional nroff. This invokes sh, which depending on the distribution might be dash, bash (or, far more rarely on Linux, some other implementation).

For interactive use, bash loads .bashrc, so there's no point in having functions in the environment: put their definition in .bashrc, or a file sourced from .bashrc. For your own scripts, put function definitions in a file of your choice (not .profile or .bashrc themselves, because they contain things that should not be executed for every script) and put a source or . command in the script. There's very little to be gained by exporting function definitions: since you control the scripts where they're used, you can make them source the place where the definition is located. And it can't possibly work if the function can't be parsed by bash in its default settings.

3
  • Thanks for this answer! I've used groff years ago for writing man pages myself, never knew it's calling sh behind the scenes until today. I agree with the admonition of not exporting functions but the flip side is to do it without Bashisms, I believe. Other than not gaining much, are there any other reasons to avoid exporting functions?
    – legends2k
    Jul 21, 2021 at 17:25
  • @legends2k Bashisms aren't a problem since only bash will import the functions anyway. The problem is using a non-default bash feature that affects the parser. Jul 21, 2021 at 17:36
  • I understood that part (of avoiding non-default bash options in exported functions), but my comment/question was on why avoid function exporting in general? I get it that there's little to gain from exporting them, but are there other reasons?
    – legends2k
    Jul 21, 2021 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.