0

I am having issues defining a bash function inside a bash script and getting to use it in the same script when I try to chaining it after the script command.

A minimal working example is this. I have file called my_script.sh, containing

#!/bin/bash

my_function () {
  echo "My output"
}

my_function

script my_log.log -c my_function

Which when run returns

My output
Script started, output log file is 'my_log.log'.
bash: line 1: my_function: command not found
Script done.

I do not understand why my_function is recognized alone, but not when chained after script.

Can someone explain, and perhaps offer a solution?

2
  • Does it work if you first run export -f my_function and then script my_log.log -c my_function?
    – terdon
    May 18, 2022 at 13:40
  • @terdon, It does! Thank you! If you add this as answer, I'll gladly accept!
    – Rasmus
    May 18, 2022 at 13:53

2 Answers 2

3

The problem here is that your function only exists in your script, but then you run the script command which invokes a new shell (the one whose path is stored in $SHELL or /bin/sh otherwise). That new shell has no knowledge of your function unless you also define it in there.

The bash shell supports exporting a function to the environment. Then all other bash invocations (even those running as sh) run in that environment would import those definitions.

To export a function, you do:

export -f my_function

See help export in a bash shell:

$ help export 
export: export [-fn] [name[=value] ...] or export -p
    Set export attribute for shell variables.
    
    Marks each NAME for automatic export to the environment of subsequently
    executed commands.  If VALUE is supplied, assign VALUE before exporting.
    
    Options:
      -f    refer to shell functions
      -n    remove the export property from each NAME
      -p    display a list of all exported variables and functions
    
    An argument of `--' disables further option processing.
    
    Exit Status:
    Returns success unless an invalid option is given or NAME is invalid.

Then all you need to do is make sure script runs bash to interpret that command line passed after -c for which you need to pass it the right value for the $SHELL variable:

SHELL=$BASH script -c myfunction
2
  • @stéphane I saw your edit (thanks!) removed the terms "subshell" and "child shell". I know I was sloppy with "subshell", but was "child shell" also wrong? Won't the PPID of the script process be the PID of the shell script?
    – terdon
    May 18, 2022 at 15:24
  • The fact that it runs a child process (like in a subshell) is not the problem here, subshells inherit everything including functions. It's that the shell executes a separate command (script, in a child process) upon which its memory is wiped, and which itself executes a shell in a grandchild process (which may not be even the same unless you set $SHELL to make sure it is). May 18, 2022 at 16:10
2

To complement @terdon's answer, with Korn-like shells other than bash that support typeset -f (ksh, mksh, zsh), or with bash as well if only to avoid the pitfalls of the function export feature (see below), I suggest some alternatives.

For the shell started by script to have the same function defined, you can either include the definition of that function before the shell code you're passing to script -c, which you can obtain with typeset -f:

SHELL=/same/shell/as/running/the/current/script script -c "
  $(typeset -f myfunc)
  myfunc"

Where script will start a new shell and that shell will start by defining the function and then invoke it.

Or pass the body of the function in an environment variable¹, and have the shell started by script evaluate the contents of that variable:

SHELL=/same/shell/as/running/the/current/script \
  MYFUNC_DEFINITION="$(typeset -f myfunc)" script -c '
  eval "$MYFUNC_DEFINITION"
  myfunc'

(that one has the advantage of not exposing the body of the function in the command line arguments for everybody to see in the output of ps).

The advantages over export -f (other than not being bash-specific) are that:

  • then that function is only exposed to that shell where you need it in, and not the other shells that may be started in that environment.
  • bash will fail to import a function if that function uses extglob operators unless the extglob option was enabled before the function is imported. With our approaches, we get to choose when we import the function definition and can then add a shopt -s extglob before. But with functions exported with export -f, the only way we can set the extglob option early enough is with bash -O extglob which is not an option here with script. Even env BASHOPTS=extglob script -c ... (which is otherwise dangerous as it affects all bash invocations) doesn't work.

¹ actually, that's what bash does internally with export -f. It even used to do it in a very unsafe fashion which was the root of a very nasty vulnerability that made the news headlines a few years ago..

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .