My preferred solution is simply to have the prompt always begin with a newline. That way I always have a blank line between the last command output (except in this case, of course), which feels more natural to me. And the worst case is that instead of overwriting text like you are seeing, I don't get the blank line in between. Unless you try it out for a while and simply can't stand the blank line, I highly recommend it.
PS1='\n\[\033[1;32m\]\u@\h\[\033[0m\]:\[\033[1;34m\]\w\[\033[0m\]\$ '
An additional suggestion, if you're up for it: I also use a multi-line prompt to make my command line as long as possible before wrapping (or scrolling if your terminal or shell is configured for that). I also like to know the history line number. Those two together:
PS1='\n\[\033[1;32m\]\u@\h\[\033[0m\]:\[\033[1;34m\]\w\[\033[0m \!\]\n\$ '
Finally, I also like to get timestamps printed before and after running commands. A simplified version of what I use for that is:
export PROMPT_COMMAND="date +\"%Y-%m-%d %H:%M:%S (%s)\"; trap 'date +\"%Y-%m-%d %H:%M:%S (%s)\"; trap DEBUG' DEBUG"
Head spinning yet? :)
HELLO=world echo $HELLO
In the above, bash
does parameter expansion before the statement is executed. That means that $HELLO
is replaced with nothing (or whatever the current value of HELLO is) before the statement is executed (which would include setting HELLO to world
).
Observe:
$ HELLO=world bash -c 'echo $HELLO'
world
Notice that echo $HELLO
is inside single quotes. Single quotes prevent parameter expansion. That means that HELLO is not evaluated until the bash
subshell starts up and sees it as an argument. By that time, HELLO will have been set equal to world
.
By contrast, consider this line in which the single quotes have been replaced with double quotes:
$ HELLO=world bash -c "echo $HELLO"
Here nothing prints because double-quotes do not suppress parameter expansion. Thus, the bash
command is given two arguments -c
and echo
. Hence, nothing prints.
All the gory details
man bash
explains, in detail, what happens when commands such as the above are processed:
When a simple command is executed, the shell performs the
following expansions, assignments, and redirections, from
left to right.
1. The words that the parser has marked as variable
assignments (those preceding the command name) and
redirections are saved for later processing.
2. The words that are not variable assignments or redi‐
rections are expanded. If any words remain after
expansion, the first word is taken to be the name of
the command and the remaining words are the arguments.
3. Redirections are performed as described above under
REDIRECTION.
4. The text after the = in each variable assignment
undergoes tilde expansion, parameter expansion, com‐
mand substitution, arithmetic expansion, and quote
removal before being assigned to the variable.
Consider again the command:
HELLO=world echo $HELLO
The argument to echo
undergoes parameter expansion in step 2 while bash
does not even begin to assign the new value to HELLO until step 4.
Best Answer
Your first guess was right, but you used a bad example. When the shell reads the command
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; seebash(1)
or the Bash Reference Manual for details.) So you have the commandrunning 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:But
is the way to go. A couple of examples that work are:
and
(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:will display “bar”, because the evaluation of
$foo
is deferred. It is handled by theeval
(built-in) command, rather than the shell’s initial parsing pass. Or, create a text file called (for instance)showme.sh
. Put anecho $foo
(orecho "$foo"
) command into it, and sayThis 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
withfoo
equal tobar
, and then reverts to the previous value offoo
(if any) when it gets back to its primary input (the terminal).