0

If I save my cat command into a string and then I execute it then I will get an error

linux# cmd="cat /data/test/test.tx* | grep toto"
linux# eval '$cmd'
cat: |: No such file or directory
cat: grep: No such file or directory
cat: toto: No such file or directory

even with

linux# $cmd
cat: |: No such file or directory
cat: grep: No such file or directory
cat: toto: No such file or directory

I know that

linux# eval "$cmd"

works, but in my script I want to use eval '$cmd'

how to execute the cat command when it's saved into a variable?

Now if the cmd="echo anymessage" then

linux# eval '$cmd'
linux# $cmd
linux# eval "$cmd"

all of them will work

MOHAMED
  • 41,599
  • 58
  • 163
  • 268

3 Answers3

6

Let's start with a simple command, which works either way, but for different reasons.

$ cmd="echo anymessage"
$ eval '$cmd'

eval '$cmd' has eval processing the string $cmd. It splits it into words, which is again simply the single word $cmd. It expands the parameter to echo anymessage and performs one more round of word-splitting to get echo and anymessage. Now, echo is the command and anymessage is the argument, and the echo command is executed.

$ eval "$cmd"

Here, eval is processing the string echo anymessage, because bash has performed parameter expansion on $cmd before executing eval. The string is again split into words, with echo recognized as the command.

Now, let's use a more complicated value for cmd.

$ cmd="cat /data/test/test.tx* | grep toto"
$ eval '$cmd'

Again, eval receives the literal string $cmd. That's the entire command line, after word-splitting, so it's a simple command: no pipeline, no redirections, etc. The parameter is expanded to the single string cat /data/test/test.tx* | grep toto. Next, word splitting produces the words cat, /data/test/test.tx*, |, grep, and toto. Pathname expansion occurs on the pattern; let's just say it expands to the single file name /data/test/test.txt. You now have 5 words with no more expansions to perform, so the shell identifies the word cat in command position, and runs that with the remaining 4 words as arguments. The error occurs when cat cannot find files named |, grep, and toto.

Finally, let's use double quotes

$ eval "$cmd"

This time, eval receives the expanded string cat /data/test/test.tx* | grep toto as the command line. After word splitting, it sees cat, /data/test/test.tx*, |, grep, and toto. This multi-word command line contains a pipe character, so eval processes it as a pipeline. Compare to the previous example, where eval's single argument only contained a single word $cmd. From here, you should be able to see how the pipeline is executed as expected.


In summary, you need to use double-quotes on the argument to eval if it is a single parameter containing a complex command line that you want to parse and execute.

chepner
  • 497,756
  • 71
  • 530
  • 681
1

You need to tell the shell to process the pipe normally, not as part of the cat command. One way to do so is to pass it a new instance of the shell.

sh -c "$cmd"

Note that this introduces all sorts of quoting issues, and it is best to not attempt to store shell metacommands in a variable at all.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

Also please note that this code will fail if there are no files in the folder. This is the error that is going to be thrown:

cat: *: No such file or directory

To fix that you can enable nullglob

shopt -s nullglob
Aleks-Daniel Jakimenko-A.
  • 10,335
  • 3
  • 41
  • 39