1

That's what happens when a command is executed in a subshell environment:

  1. The command will run in a copy of the current shell execution environment

  2. "Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes" (quote)

Example:

#!/bin/sh

export TOTO=123
FOO=abc

(mycmd)

In this case mycmd will be able to read TOTO but not FOO, and every changement realized by mycmd to the values of those two variables will not be visible in the current shell.

But what happens when we do the same thing with a function?

Example:

#!/bin/sh

export TOTO=123
FOO=abc

function (){
     echo $TOTO
     echo $FOO
     TOTO=${TOTO}456
     FOO=${FOO}def
}

(function)
echo $TOTO
echo $FOO

result:

123
abc
123
abc

Reasonably a function executed in a subshell is not able to alter the contents of the variables defined in the parent shell, on the other hand it is able to read all the variables indiscriminately even if they are not being exported.

Can somebody please explain this behaviour in a better way? Links to some reference will be very appreciated since I couldn't find anything about it.

TTK
  • 223
  • 6
  • 21
  • 1
    Thank you. I know about that but what I would like to understand is why a function executed in a subshell is able to read all the variables even if they are not being exported. – TTK Nov 21 '15 at 10:49
  • 1
    When you call a function in a subshell, the function, along with its variables are executed in the subshell, now I see your question -- same rule - the subshell has access to `TOTO` and `FOO` that are function scope, and if executed in that subshell, should not effect the existing values of `TOTO` and `FOO` declared in the main body of the script. Even absent redeclaration, the same result would apply due to the subshell inheriting the environment of the parent. – David C. Rankin Nov 21 '15 at 10:52
  • So whenever a function is executed in a subshell it wiil receive all the variables of its scope? Is there a way to avoid it and execute it as it was a normal command? – TTK Nov 21 '15 at 11:00
  • Yes - along with a copy of the parents environment (including variables), but the function can redeclare variables, as you have done. If you do not redeclare the variables in the function, then when NOT run in a subshell, it will modify the variables in the main body, but when run in a subshell it is prevented from modifying anything in the parent environment -- including variables. (now it can still `return` an integer value). – David C. Rankin Nov 21 '15 at 11:03
  • 1
    Also note that subshells have their own rules for what environment they inherit, independent of whether they execute in a new process (which they typically, but are not required to, do): compare `echo $$ $BASHPID` with `(echo $$ $BASHPID)` in bash 4 or later. – chepner Nov 21 '15 at 14:39

2 Answers2

2

What you are observing has nothing to do with functions. Subshells get all the environment, even the un-exported variables. To illustrate, let's define two variables:

$ alpha=123
$ export beta=456

Observe that a subshell has access to both:

$ (echo $alpha $beta)
123 456

If we start a new process, though, it only sees the exported variable:

$ bash -c 'echo $alpha $beta'
456

Documentation

man bash documents subshells as follows:

(list)
list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list.

If we go look at the "COMMAND EXECUTION ENVIRONMENT", we find that it includes

shell parameters that are set by variable assignment or with set or inherited from the shell's parent in the environment.

In other words, it includes variables whether or not they have been exported.

If we read further, we find that this is in contrast to "a simple command other than a builtin or shell function." Such commands only receive the exported variables.

John1024
  • 109,961
  • 14
  • 137
  • 171
  • Thank you, that's very clear. I thought that the subshell environment didn't contain all the variables (as every normal not bultin child process). Then whatever is the (not builtin) command 'mycmd' (first example) the result would be the same if executed directly `mycmd` or in a subshell `(mycmd)`, am I right? – TTK Nov 21 '15 at 11:37
  • Yes, `mycmd`, whether executed directly as `mycmd` or in a subshell via `(mycmd)`, would see the same environment. – John1024 Nov 21 '15 at 19:55
1

Here is a short answer that will hopefully explain the differences. It has two function myfun and myfunlocal. In myfunlocal both toto and foo are declared to have local copies of toto and foo which when updated in the function are not reflected back in the main body of the script, while myfun makes no local redeclaration, and as a result, changes to toto and foo are reflected in the main body. However, if (myfun) is run in a subshell the changes to toto and foo are NOT reflected in the main body of the script:

#!/bin/bash

function myfun {

    printf "\n               myfun - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"

    toto=lizard
    foo=456

    printf "               myfun - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"
}

function myfunlocal {

    printf "\n          myfunlocal - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"

    local toto=lizard
    local foo=456

    printf "          myfunlocal - toto : %-6s  foo : %s\n" \
    "$toto" "$foo"
}

toto=dog
foo=123

printf "\n before myfunlocal   : toto : %-6s  foo : %s\n" "$toto" "$foo"

myfunlocal

printf "\n after myfunlocal    : toto : %-6s  foo : %s\n" "$toto" "$foo"

(myfun)

printf "\n after subshell myfun: toto : %-6s  foo : %s\n" "$toto" "$foo"

myfun

printf "\n after simple myfun  : toto : %-6s  foo : %s\n\n" "$toto" "$foo"

Output

$ bash functionsubshell.sh

 before myfunlocal   : toto : dog     foo : 123

          myfunlocal - toto : dog     foo : 123
          myfunlocal - toto : lizard  foo : 456

 after myfunlocal    : toto : dog     foo : 123

               myfun - toto : dog     foo : 123
               myfun - toto : lizard  foo : 456

 after subshell myfun: toto : dog     foo : 123

               myfun - toto : dog     foo : 123
               myfun - toto : lizard  foo : 456

 after simple myfun  : toto : lizard  foo : 456
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85