6

I'm reading this book on the bash architecture and one of the bash maintainers says:

There are different variable scopes for shell function calls and temporary scopes for variables set by assignment statements preceding a command. When those assignment statements precede a command that is built into the shell, for instance, the shell has to keep track of the correct order in which to resolve variable references, and the linked scopes allow bash to do that.

I can't figure out how to do this. This prints out a blank newline:

foo="bar" echo $foo

This prints out "bar" but the foo variable is still defined after the command finishes so I don't consider it to be "command scope":

foo="bar"; echo $foo

How do I define a variable with command scope?

6

Your first guess was right, but you used a bad example.  When the shell reads the command

foo=bar echo $foo

the first thing it does (or, at least, one of the first things) is to look for variable references (like $foo) and evaluate them.  Then it processes what’s left of the line.  (This may be a bit of an oversimplification; see bash(1) or the Bash Reference Manual for details.)  So you have the command

echo (nothing)

running with foo=bar; but it’s too late; there’s nothing left to look at the value of $foo.  You can demonstrate this with the following sequence:

$ foo=oldvalue
$ foo=bar echo "$foo"
oldvalue

But

foo=bar command

is the way to go.  A couple of examples that work are:

foo=bar sh -c 'echo "$foo"'

and

foo=bar env

(You may want to pipe that through grep foo.)

I just now noticed the sentence that you quoted, “When those assignment statements precede a command that is built into the shell, for instance, …”, suggesting that built-in commands (such as echo) represent a special case.  It is intuitive (to me, at least) that this “command scope” you’re looking for would have to work by setting an environment variable in a child process, and it’s not clear how it would work for a built-in command, which doesn’t run in a child process.  Not many built-in commands access the environment directly, and their interaction with shell variables is sometimes arcane.  But I came up with a couple more examples that might be more what you’re looking for:

foo=bar eval 'echo $foo'

will display “bar”, because the evaluation of $foo is deferred.  It is handled by the eval (built-in) command, rather than the shell’s initial parsing pass.  Or, create a text file called (for instance) showme.sh.  Put an echo $foo (or echo "$foo") command into it, and say

foo=bar . showme.sh

This is probably what your book is talking about when it says, “… the shell has to keep track of the correct order in which to resolve variable references ….”  Somehow the shell runs the commands in showme.sh with foo equal to bar, and then reverts to the previous value of foo (if any) when it gets back to its primary input (the terminal).

  • Are you saying it doesn't work because echo is a built-in command? If so, I think you should emphasize that differently. I'm not sure if that's the main takeaway. – Daniel Kaplan Aug 23 '14 at 0:34
  • 1
    No, I'm saying the same thing grawity is saying – it doesn't work because the $foo gets "expanded" before anything else happens. I updated my answer with more thoughts on built-in commands. – G-Man Says 'Reinstate Monica' Aug 23 '14 at 1:19
  • 1
    @DocSalvager: That's what I was referring to when I said, "Not many built-in commands access the environment directly," but I guess I could have said it more clearly. – G-Man Says 'Reinstate Monica' Oct 10 '14 at 13:44
  • 1
    echo '$foo' outputs the string $foo. cat '$foo' and ls -l '$foo’ look for a file called $foo. grep '$foo' .bashrc searches for the string $foo in your .bashrc file. But the shell, unlike those other programs, treats $ specially; sh -c '$foo' looks for an environment variable called foo. For example, foo=date sh -c '$foo' will run a date command. (Compare to the fact that cat '\0107' and ls -l '\0107’ look for a file called \0107, and grep '\0107' searches for the string \0107, but echo -e '\0107' outputs the string G, … (Cont’d) – G-Man Says 'Reinstate Monica' Mar 14 '18 at 21:43
  • 1
    (Cont’d) …  because echo treats \ specially if the -e option is provided.) – G-Man Says 'Reinstate Monica' Mar 14 '18 at 21:43
5

The problem here is that variable expansion ($foo → value) happens before evaluating the entire command (put foo=bar to the environment, run echo ...).

It would work for commands that access the environment themselves:

foo=bar env | grep ^foo

foo=bar declare -p foo

Bash doesn't really have a proper scope for what you're looking; you will need to use the "subprocess" trick to get a new process for the command. (Of course it means the scope's read-only, no changes will reach the parent...) Use ( ... ) to create a subshell:

(foo=bar; echo $foo)

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.