4

While researching more in-depth information about Bash subshells, I ran into an interesting execution that I would like to understand why it works. The execution involves assigning a string to a variable that is then used when man is called, link to original example. I have already read about why the specific variable LESS is used in that example (man has less as its default pager in many distros), what I do not understand is how a variable assignment followed by a command execution works without any kind of separating metacharacters.

LESS=+/'OPTIONS' man man opens the man page for the man command directly under the OPTIONS section. It works with files opened directly with less too (which leads me to believe that the LESS variable is used by less the same way as doing a regex search within a less "session"). The LESS variable is not being exported, it's never saved in the current shell environment (executing echo $LESS returns nothing) so how is less capturing that value?

Why does that work, but not something like foo='hello' echo $foo? This case only works when a command separator (;) is included between the variable assignment and the command execution. I even tried foo=+'hello' echo $foo in case =+ did something I was not aware in Bash, but no changes in the output.

Any explanations or reading material are welcome!

1

1 Answer 1

7

This is a standard feature. You can set the variable when launching the command and then the variable will be set for the command. It also works in the example you show, foo='hello' echo $foo. The problem is that you are testing the wrong way. When you run this:

foo='hello' echo $foo

The shell will expand the variable before running the command. Since foo is not actually set when you launch it, that becomes echo (echo nothing). You can see this with set -x:

$ set -x
$ foo='hello' echo $foo
+ foo=hello
+ echo

Now, compare that to what happens if you instead use a little script so that the variable is not seen by your shell:

$ cat ~/scripts/foo.sh
+ cat /home/terdon/scripts/foo.sh
#!/bin/bash

echo "The value of foo is:$foo"

Use that to echo the variable and you get:

$ foo='hello' ~/scripts/foo.sh
+ foo=hello
+ /home/terdon/scripts/foo.sh
The value of foo is:hello

You can do the same thing if you call bash -c and enclose the command you give it in single quotes so the variable will be protected and not expanded by your current shell before calling bash -c:

$ foo='hello' bash -c 'echo $foo'
hello

While this fails because the double quotes mean that $foo is being expanded before bash -c ever sees it:

$ foo='hello' bash -c "echo $foo"

$

The behavior is documented in man bash in the "ENVIRONMENT" section:

   The environment for any simple command or  function  may  be  augmented
   temporarily  by  prefixing  it with parameter assignments, as described
   above in PARAMETERS.  These assignment statements affect only the envi‐
   ronment seen by that command.

It's also described in detail in the manual: 3.7.1 Simple Command Expansion

3
  • Therefore, the main reason why LESS=+/'OPTIONS' man man works "as intended" but foo='hello' echo $foo doesn't is because Bash expansions always occur first so when foo is expanded it hasn't been set yet (kind of like a race condition?).
    – AnthonyBB
    Commented Jan 8, 2021 at 17:42
  • 1
    @AnthonyBB pretty much, yes. The variables are expanded before launching the command so the command never sees the variable, it only ever sees its value. If you think about it, that is the only way variables could work. The whole idea is that if you have x=12, then x will always be treated as 12. Otherwise, what's the point of having variables in the first place.
    – terdon
    Commented Jan 8, 2021 at 17:50
  • Thank you very much for your explanation! Understanding the ****why**** of things is one of my main goals and pleasures as a sysadmin.
    – AnthonyBB
    Commented Jan 8, 2021 at 17:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .