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!

9
  • && is interpreted before variable expansion so this won't ever work. Commented Nov 14, 2017 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? Commented Nov 14, 2017 at 15:14
  • No that isn't how it works, there is an order of operations. Commented Nov 14, 2017 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. Commented Nov 14, 2017 at 15:18
  • 1
    This is also covered in detail in BashFAQ #50. Commented Nov 14, 2017 at 16:00

1 Answer 1

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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.