1

Why does the following script work as expected (prints hello)

#!/bin/bash

foo=$(bash -c 'echo hello')
echo $foo

while this script:

#!/bin/bash

cmd="bash -c 'echo hello'"
foo=$($cmd)
echo $foo

gives the following error:

hello': -c: line 0: unexpected EOF while looking for matching `''
hello': -c: line 1: syntax error: unexpected end of file

2 Answers2

4

In,

cmd="bash -c 'echo hello'"
$cmd

You're not running the bash -c 'echo hello' command, you're running the $cmd simple command.

That unquoted $cmd means invoking the split+glob operator. Here, with the default value of $IFS, the content of $cmd is split into bash, -c, 'echo and hello'. So, you're running bash with those 4 arguments, it's as if you had typed:

bash -c "'echo" "hello'"

And that 'echo code has a missing closing quote (the hello' argument goes into the $0 of that inline script).

If you want to evaluate the content of $cmd as shell code, it's

eval "$cmd"

So:

cmd="bash -c 'echo hello'"
foo=$(eval "$cmd")
echo "$foo"

Though you could also use your split+glob operator differently:

cmd='bash,-c,echo hello'
IFS=, # split on comma
set -f # disable glob
foo=$($cmd)
echo "$foo"
1

Lets take a look into what is being executed inside $($cmd):

$ cmd="bash -c 'echo hello'"
$ foo=$(printf '<%s>' $cmd)
$ echo "$foo"
<bash> <-c> <'echo> <hello'>

As you can see, the command line executed is:

$ foo=$( "bash" "-c" "'echo" "hello'")

One single quote is on one side of the expresion: "'echo", that is parsed as the command to be executed by bash -c and it reports (correctly) that a closing single quote `'' is missing.

One solution is to promote the correct parsing of the command with eval:

$ foo=$(eval "printf '<%s>' $cmd"); echo "$foo"
<bash><-c><echo hello>

This works:

$ foo=$(eval "$cmd"); echo "$foo"
hello

But the correct idea is that: “variables store data, functions store code”.

$ cmd(){ bash -c 'echo hello'; }
$ foo=$(cmd); echo "$foo"
hello