-1

Saying that I have a bash script /home/me/test/sh:

#!/bin/bash

# cmd                              # here is the output of the cmd
pwd echo 'aaa'                     # /home/me
val=pwd echo 'aaa'                 # aaa
val=pwd echo 'aaa'$val             # aaa

val=pwd echo 'aaa' && echo $val    # aaa <and an empty line>

I can't quite understand how these bash codes seperated by a space works.

BTW, I pose this question because I just installed ROS and a bash script named setup.sh was generated, and I'm reading this script. There is a line as below:

CATKIN_SHELL=bash /opt/ros/kinetic/_setup_util.py >> file.log
Yves
  • 3,291
  • 1
    Commands cannot be separated by just spaces in the shell. The only one of your examples that contains more than one command is the last, where the two commands are separated by &&. – Gordon Davisson Jul 26 '18 at 06:36
  • @GordonDavisson There is only one single command in the code, the first pwd (with too many arguments). The rest is a syntax error due to the unterminated command substitution (unbalanced backticks). (Now edited by Filipe Brandenburger). – Kusalananda Jul 26 '18 at 06:45
  • I too cannot understand your bash code ... – pLumo Jul 26 '18 at 06:50
  • @RoVo Install ROS and read its setup.sh, you will get what I posed here... Seriously I got no idea why it wrote such a code. – Yves Jul 26 '18 at 07:11

2 Answers2

9

So I think what is tripping you here is the command format where the first word(s) in the command are a variable assignment (which the shell determines based on the presence of a =.)

In those cases, the shell will evaluate those assignments, but it will only export those variables in the command that it is running. In particular, the shell itself won't have access to those variables.

This is useful for running commands or scripts that are affected by environment variables, in which case you don't need to export them in a shell and can just set them as "one-offs" for that single execution.

Let's break down your commands and explain them one by one:

pwd echo 'aaa'                     # /home/me

Ok, so this is running the pwd command and passing it two arguments, echo and aaa. But pwd doesn't take arguments, so it's just ignoring those two and printing the local directory.

val=pwd echo 'aaa'                 # aaa

So as I explained, this performs the variable assignment, setting $val to contain the string pwd and then executes the echo command with the aaa argument. The $val variable is available only to the echo command and not to the shell itself! The echo command doesn't really do anything with that variable, so it simply goes ahead and prints aaa. After this command is finished, the $val variable is not defined in the shell.

val=pwd echo 'aaa'$val             # aaa

So, again, similar to the command above. This one gets tricky, since you might expect the final $val to get expanded. But variables are expanded by the shell, not by the commands. And, as explained, the shell doesn't really see $val which is set to pwd, only the command (in this case, echo) does, so $val gets expanded to an empty string, and this is exactly equivalent to the above.

val=pwd echo 'aaa' && echo $val    # aaa <and an empty line>

And one more time, $val is not available after the first echo command completes, so the second echo $val will print nothing (just the blank line), as $val was never really set in the shell itself throughout this execution.

If you want to set $val in the shell, then you need to make that assignment a standalone, without any commands. For instance, this behaves differently from the above:

$ val=pwd; echo 'aaa' && echo $val
aaa
pwd

Since val=pwd is followed by a ;, then the shell considers it a command on its own and will set $val in the current shell, so the echo $val will work as you expect.

(Note that there's one more difference here, since with val=pwd; ..., the variable $val is not really exported, so echo will not see that variable in its environment, while with val=pwd echo ... it would get exported, but then in that case, it is not available in the shell.)

And considering you're using pwd, I wonder if what you wanted was to store the output of that command in the variable... In which case, you need to use shell command substitution, either using backticks, or better yet, surrounding the command with $( and ).

So one final example here:

$ val=$(pwd); echo 'aaa' && echo $val
aaa
/home/me

Or:

$ val=$(pwd) && echo 'aaa' && echo $val
aaa
/home/me

(There's a subtle difference here, where the latter will break execution if the command inside the substitution, in this case pwd, fails and exits with a non-zero status, in which case the following commands will not be executed. When using ; that's not the case, the following commands are executed even if the first one fails.)

I hope this explains what you weren't understanding about these commands!

filbranden
  • 21,751
  • 4
  • 63
  • 86
0

the code below may help to understand how this construct works:

$ cd tmprun/
$ cat run.sh 
echo $a
$ ./run.sh

$ a=bla ./run.sh bla

  • It probably does, but I still have the feeling that a few words of explanation might help those who are very unfamiliar with the concept (such as, e.g., the OP) ... – AdminBee Nov 03 '21 at 15:55