Bash – Which of the following shell operations are performed inside the function body when running a function definition and when calling a function

bashfunction

A function definition is a command. When a function definition is run, I thought that the function body would be kept intact, as if the function body is single quoted.

I knew I was wrong, when I understood the following from the Bash manual in G-Man’s answer
to my Alias and functions question:

Aliases are expanded when a function definition is read, not when the function is executed, ….

So alias expansion is performed in the function body, while parameter expansion is not, as shown by the following example:

$ alias myalias=cat
$ echo $var
3
$ myfunc() { myalias myfile; echo $var; }
$ declare -fp myfunc
myfunc () 
{ 
    cat myfile;
    echo $var
}

Similarly, a call to a function is also a command.

aliases defined in a function are not available until after that function is executed.

so alias definition is executed only when executing, i.e., calling the function, not when running the function definition.

alias expansions and alias definition execution are just two of the shell operations listed below.

My questions are:

  1. What shell operations are performed inside the function body, and
    what operations are not performed,

    • when running a function definition?

    • when calling a function, i.e., executing a function?

  2. Are there any shell operations which are performed during both
    running the definition of a function and calling the function?

    Or do running the definition of a function and calling the function
    perform non-overlapping sets of shell operations?

The possible operations are listed in the following quote from the Bash manual:

3.1.1 Shell Operation

The following is a brief description of the shell’s operation when it reads and executes a command. Basically, the
shell does the following:

  1. Reads its input from a file (see Section 3.8 [Shell Scripts], page 39), from a string supplied as an argument to the -c invocation option
    (see Section 6.1 [Invoking Bash], page 80), or from the user’s
    terminal.

  2. Breaks the input into words and operators, obeying the quoting rules described in Section 3.1.2 [Quoting], page 6. These tokens are
    separated by metacharacters. Alias expansion is performed by this step
    (see Section 6.6 [Aliases], page 88).

  3. Parses the tokens into simple and compound commands (see Section 3.2 [Shell Commands], page 8).

  4. Performs the various shell expansions (see Section 3.5 [Shell Expansions], page 21), breaking the expanded tokens into lists of
    filenames (see Section 3.5.8 [Filename Expansion], page 30) and
    commands and arguments.

  5. Performs any necessary redirections (see Section 3.6 [Redirections], page 31) and removes the redirection operators and
    their operands from the argument list.

  6. Executes the command (see Section 3.7 [Executing Commands], page 35).

  7. Optionally waits for the command to complete and collects its exit status (see Section 3.7.5 [Exit Status], page 38).

Best Answer

<rant>
“running a function definition” and “running the definition of a function” are not common idioms.  I expect that most people would refer to that simply as “defining a function”.  OK, that phrase might be interpreted to refer to

  1. conceptually specifying the characteristics and interfaces (parameters and other inputs, processing, and outputs) of a function (this is an activity performed by a person, or a group of people)
  2. choosing the implementation of a function; i.e., what commands to use in the function body.  This is an activity performed by a person, or a group of people, possibly using paper, whiteboards, and/or blackboards.
  3. typing the implementation of a function into a text editor or directly into an interactive shell (this, of course, is also an activity performed by a person, or possibly a group of people, or possibly a very intelligent and dexterous cat, dog or monkey)

If you fear confusion with the above, you might be better served by phrases like “reading a function definition” and “processing the definition of a function”.

You say, “A function definition is a command.”  It’s not clear what a claim like that even means — it’s a matter of semantics — but, at the semantic level, it’s debatable.  Section 3.2 of the Bash manual (Shell Commands) lists six kinds of shell commands: Simple Commands, Pipelines, Lists, Compound Commands, Coprocesses, and GNU Parallel.  Function definitions don’t fit well into any of those categories.
</rant>

Getting to your question, and looking at the seven steps / operations,

  1. Reading input obviously has to happen before anything else can happen.  It’s, again, a matter of semantics — you could say that reading input from a file or other stream text source is part of “reading a function definition” or that it is a prerequisite / prelude to “processing the definition of a function”.
  2. Breaking the input into words and operators is clearly part of “processing the definition of a function”.
    • Breaking the input into tokens is, of course, a prerequisite / prelude to parsing the tokens (step 3, below).
    • The manual says, “Alias expansion is performed by this step,” and you show in the question that you know that alias expansion happens when the function definition is read.
  3. Parsing the tokens.  This must at least start during the “processing the definition of a function” phase for the shell to realize that it is looking at a function definition and not a simple command.  Beyond that, we can do this simple experiment: type either of the following into a shell:

    myfunc1() {                     myfunc2() {
        <               or              ;
    }                               }
    

    It will fail with a “syntax error” before you get a chance to type the }.  (> and & produce the same effect.)  So clearly this step is part of processing the definition of the function.


  1. Performing the various shell expansions is part of calling the function.
    • You show in the question that you know that parameter expansion does not happen when the function definition is read.
    • It’s equally trivial to demonstrate that pathname / filename expansion does not happen when the function definition is read.  In your example, change echo $var (which should be echo "$var", but that’s another matter) to echo *.
      • If you look at the function definition, you’ll see that it still says echo * and has not been expanded.
      • Change directory (cd) and/or create file(s) and/or delete file(s) and/or rename file(s), and then call (execute) the function.  You’ll see that it outputs the current list of files1 in the current directory, and doesn’t reflect the contents of the directory where/when you defined the function.
  2. Performing redirections is part of calling the function.  This is also trivial to verify.  In your example, change echo $var to echo hello > myfile.
    • If you look at the function definition, you’ll see that it still says  > myfile.
    • Check the directory, and you’ll see that myfile has not been created (yet).
  3. Executing the command — do you really need to ask?
  4. Waiting for the command to complete can’t possibly happen until the command has been executed (step 6), so this obviously is performed only when calling the function.

In other words, exactly what @ilkkachu said.

The one exceptional special case I can think of is that steps 2 and 3 (lexical analysis and parsing) occur (again) when executing a function if the function contains eval statements — but I believe that that’s outside the scope of what your question is really asking.
____________
1 excluding hidden (dot-) files, unless you have said shopt -s dotglob

Related Question