4

From Bash Manual:

Note that shell functions and variables with the same name may result in multiple identically-named entries in the environment passed to the shell’s children. Care should be taken in cases where this may cause a problem.

How can bash distinguish "shell functions and variables with the same name" ?

$  func () { return 3; }; func=4; declare -p func; declare -f func;
declare -- func="4"
func () 
{ 
  return 3
}

When does "multiple identically-named entries in the environment passed to the shell’s children" happen?

What "care" should be taken for what problem?

4

The general story: separate namespaces

Generally shells distinguish between variables and functions because they're used in different contexts. In a nutshell, a name is a variable name if it appears after a $, or as an argument to builtins such as export (without -f) and unset (without -f). A name is a function name if it appears as a command (after alias expansion) or as an argument to export -f, unset -f, etc.

Variables can be exported to the environment. The name of the environment variable is the same as the shell variable (and the values are the same too).

With older bash: confusion due to function export

Bash, unlike most other shells, can also export functions to the environment. Since there's no type indication in the environment, there's no way to recognize whether an entry in the environment is a function or not, other than by analyzing the name or the value of the environment variable.

Older versions of bash stored a function in the environment using the function's name as the name, and something that looks like the function definition as the function's value. For example:

bash-4.1$ foobar () { echo foobar; }
bash-4.1$ export -f foobar
bash-4.1$ env |grep -A1 foobar
foobar=() {  echo foobar
}
bash-4.1$ 

Note that there's no way to distinguish a function whose code is { echo foobar; } from a variable whose value is () { echo foobar␤} (where is a newline character). This turned out to be a bad design decision.

Sometimes shell scripts get invoked with environment variables whose value is under control of a potentially hostile entity. CGI scripts, for example. Bash's function export/import feature allowed injecting functions that way. For example executing the script

#!/bin/bash
ls

from a remote request is safe as long as the environment doesn't contain variables with a certain name (such as PATH). But if the request can set the environment variable ls to () { cat /etc/passwd; } then bash would happily execute cat /etc/passwd since that's the body of the ls function.

With newer bash: confusion mostly alleviated

This security vulnerability was discovered by Stéphane Chazelas as one of the aspects of the Shellshock bug. In post-Shellshock versions of bash, exported functions are identified by their name rather than by their content.

bash-4.3$ foobar () { echo foobar; }
bash-4.3$ export -f foobar
bash-4.3$ env |grep -A1 foobar
BASH_FUNC_foobar%%=() {  echo foobar
}

There is no security issue now because names like BASH_FUNC_foobar%% are not commonly used as command names, and can be filtered out by interfaces that allow passing environment variables. It's technically possible to have a % character in the name of an environment variable (that's what makes modern bash's exported functions work), but normally people don't do this because shells don't accept % in the name of a variable.

The sentence in the bash manual refers to the old (pre-Shellshock) behavior. It should be updated or removed. With modern bash versions, there is no ambiguity in the environment if you assume that environment variables won't have a name ending in %%.

  • @Tim Holy bold overflow! You really need to learn a full paragraph where half the text isn't bolded. – Gilles Aug 5 '17 at 10:59
  • "Variables can be exported to the environment. The name of the environment variable is the same as the shell variable (and the values are the same too)." When a shell variable is exported to the environment, is it only to assign the export attribute of the shell variable, and the shell variable and its corresponding environment variable are the same object instead of different copies? By "the name of the environment variable is the same as the shell variable (and the values are the same too)", do you mean the shell variable and its corresponding environment variable are separate copies? – Tim Aug 5 '17 at 11:06
  • I really think that my reorganization of your reply make it more readable to me. – Tim Aug 5 '17 at 11:08
2

A similar thing happens in Emacs Lisp. It has two namespaces, one for functions and one for variables. If you dereference a symbol in function context ((var)) it will call the function, if you defreference it in variable context (var i.e. without the brackets) it will give you the variable. For example:

(defun myvar (myvar)
  "adds 3 to MYVAR"
  (+ 3 myvar))
(setq myvar 7)
(message (myvar myvar))

Will execute the function myvar with the argument 7 which is the dereference of the variable myvar.

This can become very confusing if you are not used to it.

After looking at your question and making the tests for bash I'm surprised that it presents the same behaviour. Translating the ELisp from above into bash:

[grochmal@phoenix ~]$ myvar () { echo $(($1+3)); }
[grochmal@phoenix ~]$ myvar=7
[grochmal@phoenix ~]$ myvar $myvar
10

Bash is a little less confusing in this than ELisp because you need the $ to mark the variable. Still, this may look like a declare of a single name containing two things. See:

[grochmal@phoenix ~]$ declare -p myvar
declare -x myvar="7"
[grochmal@phoenix ~]$ declare -f myvar
myvar () 
{ 
    echo $(($1+3))
}

(P.S. Once you get used to the existence of two namespaces, e.g. you program in ELisp for a while, this stops becoming confusing)

  • 1
    You've completely missed the case the manual refers to, which is when variables are communicated through the environment. In all fairness, that case no longer applies to modern versions of bash (post-Shellshock), which encode functions differently. But even so the reference to Lisp is hard to follow even for people who are familiar with Lisp. It would be much better to explain this in an autonomous way. – Gilles Jul 30 '17 at 22:54
  • @Gilles - It is not that I missed it, I simply did not know it :) . I missed that part of Shellshock altogether and then attempted to test this on a post-shellshock bash only. I actually need to thank you for clearing that up. – grochmal Jul 31 '17 at 9:41

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.