1

I'm trying to create a shell script that concatenates multiple commands to a string and then execute the commands.

The following succeeds (Creates the 'y' and 'z' files and prints ls output):

$ /bin/touch /z && ls && /bin/touch /y

But the following fails (Creates the 'y' and 'z' files, but also a '&&' and 'ls' files)

$ A="/bin/touch /z && ls && /bin/touch /y"
$ $A

It seems that by executing the line using $A the binary touch gets the rest of the string as parameters and the && operator does not execute as intended. (I also tried switching && with ;, || and such but got the same result)

I found out the eval $A does the trick, but I'm still curious as to why this happens. (And possibly want to skip the need to eval)

Thanks!

Tals
  • 468
  • 4
  • 17
  • `&&` is interpreted before variable expansion so this won't ever work. – 123 Nov 14 '17 at 15:08
  • But doesn't `$A` just substitute the expression with the string, therefore the `&&` is interpreted exactly the same as running it without the string? – Tals Nov 14 '17 at 15:14
  • No that isn't how it works, there is an order of operations. – 123 Nov 14 '17 at 15:17
  • You have to use `eval`, but unless your goal is to write a program that can execute arbitrary user input, you should define a shell function instead. – chepner Nov 14 '17 at 15:18
  • @TalSkverer No additional *parsing* (which is when tokens like `&&` are recognized) is performed after parameter expansion occurs. – chepner Nov 14 '17 at 15:19
  • @123 Can you direct me to documentation detailing said order? – Tals Nov 14 '17 at 15:29
  • @chepner Parameter expansion is when parameters are passed to binary? – Tals Nov 14 '17 at 15:30
  • No, that's the first step of evaluation. Parameter expansion is when `$A` gets replaced with the contents of the variable. – chepner Nov 14 '17 at 15:49
  • 1
    This is also covered in detail in [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050). – Charles Duffy Nov 14 '17 at 16:00

1 Answers1

3

Broadly speaking, there are two stages to evaluating a command line: parsing and expansion.


When you enter

/bin/touch /z && ls && /bin/touch /y

the string is first parsed into a preliminary series of words separated by whitespace:

  • /bin/touch
  • /z
  • &&
  • ls
  • &&
  • /bin/touch
  • /y

At this points, it recognizes the &&s as separating a list of simple commands consisting of the following words:

  1. /bin/touch, /z
  2. ls
  3. /bin/touch, /y

Now, it applies a set of expansions to each word in an attempt to produce more words. In this case, there is nothing to expand, and each simple command is examined again to identify the command itself and its arguments:

  1. Command /bin/touch, argument /z
  2. Command ls, no arguments
  3. Command /bin/touch, argument /y

In the case of $A, parsing is simple: there is a single word $A in the preliminary series. That word next undergoes expansion; the only relevant one is parameter expansion, which produces the same list of words as identified in the parsing step of the first example. The difference here is that list does not undergo further expansion; it's not time to identify the command name and its arguments.

  1. Command /bin/touch, with arguments...

    1. /z
    2. &&
    3. ls
    4. &&
    5. /bin/touch
    6. /y

A string is practically never the correct way to bundle up a set of commands. What you want to do is define a function

a () {
  /bin/touch /z && ls && /bin/touch /y
}

which you then use as an ordinary command:

$ a
/z
$ ls
/z
/y
chepner
  • 497,756
  • 71
  • 530
  • 681