I am trying to invoke a shell command with a modified environment via the command env.
According to the manual
env HELLO='Hello World' echo $HELLO
should echo Hello World, but it doesn't.
If I do
HELLO='Hello World' bash -c 'echo $HELLO'
it prints Hello World as expected (thanks to this answer for this info).
What am I missing here?
It's because in your first case, your current shell expands the $HELLO variable before running the commands. And there's no HELLO variable set in your current shell.
env HELLO='Hello World' echo $HELLO
will do this:
expand any variables given, in this case $HELLO
run env with the 3 arguments 'HELLO=Hello World', 'echo' and '' (an empty string, since there's no HELLO variable set in the current shell)
The env command will run and set the HELLO='Hello World' in its environment
env will run echo with the argument '' (an empty string)
As you see, the current shell expanded the $HELLO variable, which isn't set.
HELLO='Hello World' bash -c 'echo $HELLO'
will do this:
set the variable HELLO='Hello World for the following command
run bash with the 2 arguments '-c' and 'echo $HELLO'
since the last argument is enclosed in single quotes, nothing inside it is expanded
the new bash in turn will run the command echo $HELLO
To run echo $HELLO in the new bash sub-shell, bash first expands anything it can, $HELLO in this case, and the parent shell set that to Hello World for us.
The subshell runs echo 'Hello World'
If you tried to do e.g. this:
env HELLO='Hello World' echo '$HELLO'
The current shell would expand anything it can, which is nothing since $HELLO is enclosed in single quotes
run env with the 3 arguments 'HELLO=Hello World', 'echo' and '$HELLO'
The env command will run and set the HELLO='Hello World' in its environment
env will run echo with the argument '$HELLO'
In this case, there's no shell that will expand the $HELLO, so echo receives the string $HELLO and prints out that. Variable expansion is done by shells only.
I think what happens is similar to this situation in which I was also puzzled.
In a nutshell, the variable expansion in the first case is done by the current shell which doesn't have $HELLO in its environment. In the second case, though, single quotes prevent the current shell from doing the variable expansion, so everything works as expected.
Note how changing single quotes to double quotes prevents this command from working the way you want:
HELLO='Hello World' bash -c "echo $HELLO"
Now this will be failing for the same reason as the first command in your question.
This works and is good for me
$ MY_VAR='Hello' ANOTHER_VAR='World!!!' && echo "$MY_VAR $ANOTHER_VAR"
Hello World!!!
Here is an easier way to confirm shell is working as expected.
env A=42 env
env
The first command sets A to 42 and runs env. The second command also runs env. Compare the output of both.
As nos noted, expansion of your variable passed to echo occurs before that variable gets set.
One alternative not yet mentioned in the earlier answers is to use:
$ a=abc eval 'echo $a'
abc
$ echo $a
<blank>
Note that you have to use the a=abc cmd syntax and not env a=abc cmd syntax; apparently env doesn't play nice with the built-in eval.
Related
I am using a shell script named script.sh that looks like that :
#!/bin/bash
STRING=$(cat my_string.txt)
${1}
In my_string.txt, there is only :
this_is_my_string
When I execute the commands :
$ STRING="not_my_string"
$ ./script.sh "echo $STRING"
The shell prints not_my_string instead of this_is_my_string and I don’t understand why.
Could you explain me ? And is there any way to force to print the value of the STRING variable which is defined inside the script ?
The variable $STRING is being expanded before the script is called, which is why not_my_string is being assigned.
To delay expansion until after the script is called you should replace "echo $STRING" with 'echo $STRING'. The single quotes cause the expansion to be delayed.
There is some discussion of delayed expansion here:
How to delay expansion of variable in bash if command should be executed on an other machine?
You will also need to replace ${1} in your script with eval ${1}, which will force the string to be executed and expanded.
$ STRING="not_my_string"
$ ./script.sh "echo $STRING"
During command execution bash will expand all variables to the values and actually the following command will be executed:
./script.sh "echo not_my_string"
You can use the following:
./script.sh 'echo $STRING' to send string as is and eval "${1} inside the script to execute argument
I'm a bit confused by the following:
/usr/bin/env V=hello echo $V somestring
outputs only
somestring
not hello somestring like I would expect from man env.
However, the tail of
/usr/bin/env V=hello printenv
is
_=/usr/bin/env
V=hello
Why would this be happening?
env sets the environment that's inherited by the child process that executes the command. But you're expanding the variable in the original shell, and passing the result as an argument to env.
Try this:
/usr/bin/env V=hello bash -c 'echo $V somestring'
The single quotes prevent the variable from being expanded in the original shell. Then you run a new shell process that expands the variable itself.
When I am trying echo variable inside 'env' command I got nothing, but I can see it using 'printenv' command:
root#devel:~# env xxx=23 echo $xxx
root#devel:~# env xxx=23 printenv | grep xxx
xxx=23
what's wrong here?
env xxx=23 echo $xxx
In the above, the shell evaluates $xxx before env is executed. Thus, nothing is echoed.
In more detail, the shell sees the four words env, xxx=23, echo and $xxx. It interprets env as a command name and xxx=23, echo, and $xxx as three arguments which will be passed to the command env. It evaluates $xxx before passing it to the command env.
By contrast, in the following, there are no shell variables for the shell to evaluate. Instead env is executed with two arguments, xxx=23 and printenv. env sets the environment variable xxx and then executes printenv:
$ env xxx=23 printenv | grep xxx
xxx=23
Similarly, observe:
$ env xxx=23 sh -c 'echo $xxx'
23
Since $xxx is inside single-quotes, the shell does not evaluate it. Instead is runs env with four arguments: xxx=23, sh, -c, and echo $xxx. After env sets environment variable xxx, it executes sh with arguments -c and echo $xxx. The $xxx is evaluated when sh is executed and hence it sees the variable xxx.
When you run env xxx=23 echo $xxx,
the variable xxx=23 becomes visible to the echo process during its execution.
But in echo $xxx the value of $xxx is not evaluated by echo,
it is evaluated by the currently executing shell.
And since the env ... invocation doesn't affect the current shell,
the value of $xxx is whatever it was before you executed this command (probably unset).
echo is not a good way to test the effect of env,
because you cannot make the echo command print a specific value defined in its environment.
Your example with printenv is better,
because it dumps the content of environment variables it knows about.
Another good test is what #john wrote in his answer,
invoking another shell and make the shell print a chosen environment variable.
Any program with the ability to print the content of environment variables would work.
When I execute var=blah echo -n $var, then nothing is printed which is an expected behavior because bash first expands $var with an empty string, then sets up a temporary environment and puts var=blah in it and finally echo runs with an empty string as an argument. On the other hand, when I execute IFS=. read a b <<< "k.l", then new value for IFS is taken into account. When is variable taken into account if variable statement is not followed by a semicolon?
An assignment or several in a simple command by themselves cause the variables to be set as variables in the shell. If they are exported, they'll be inherited to the environment of any commands the shell executes.
An assignment in a simple command with an actual command to run does not change the variable in the shell, but only sets it in the environment of the command being executed. (A "simple command" is the usual kind of command, as opposed to a pipeline or a compound command. See the standard for the definition.)
Let's compare a couple of situations with a test script:
$ cat test.sh
echo "var: $var" # print 'var' from the environment
echo "arg: $1" # print the first command line arg
$ unset var
Here, var is set in the environment of test.sh, but the shell doesn't have that variable, so the one on the command line expands to nothing:
$ var=foo sh test.sh "$var"
var: foo
arg:
Here, var is set in the shell, so the one on the command line is expanded, but it's not set in the test.sh's environment:
$ var=foo; sh test.sh "$var"
var:
arg: foo
If we export it, it goes to the environment, too:
$ export var; sh test.sh "$var"
var: foo
arg: foo
Conceptually, you could think of read as a program like any other, so IFS set on the same command line is inherited to it, and affects the how read works. Similarly to var above.
Though IFS and read are slightly exceptional in that Bash doesn't inherit IFS from the environment (dash does), but resets it, and IFS isn't exported by default but IFS=.; read a still causes read to use the changed IFS. Of course read is a builtin, so it sees the shell's variables, not just the exported ones. I can't think of any other shell builtin that would use IFS similarly, so I can't compare.
read always reads a single line of input, regardless of how many arguments it is given; once the line is read, read itself uses the value of IFS in its environment to split the string into enough words to populate the variables named by its positional arguments.
I'm confused by this behaviour:
$ FOO=42 echo "$FOO"
$ FOO=42 && echo "$FOO"
42
I'm accustomed to using VARNAME=value cmd to specify environment variables for other commands, so I was surprised to discover it doesn't work when cmd is echo. This might have something to do with echo being a Bash built-in. Is this the case? If so, is using && the best way to specify environment variables for Bash built-ins?
Update: && is no good, as it results in the environment variables being set permanently, which I'm trying to avoid.
It doesn't matter that echo is a built-in. The reason is that variables on the command line are evaluated before executing the command, they're not evaluated by the command. You could get the result you want with:
FOO=42 bash -c 'echo "$FOO"'
This starts a new shell to execute echo $foo. Since the argument to bash is in single quotes, the variable is not replaced by the original shell.
The replacement happens before the command is executed:
$FOO is replaced with its current value.
echo "" is executed with $FOO set to 42.
Try:
FOO=42 sh -c 'echo "$FOO"'