Search code examples
bashshellterminalcommandexecution

Running the output of a bash shell script as a shell command?


I have this shell script called 'test.sh' :

#!/usr/bin/env bash

echo "echo 'hello';"

When I run ./test.sh it obviously gives this output:

echo 'hello';

Now this output is also a valid shell statement itself. If I copy & paste this line on the shell and press enter, it says

hello

So far so good.
But I want to do this in one step, so I tried this:

$(./test.sh)

However I now get:

'hello';

In case it matters, I'm using macOS 10.15.6 Catalina which uses the zsh shell by default. I tried the same on Linux using bash, same result.

I also tried $('./test.sh') or $("./test.sh") just in case, but obviously that made no difference.

There's probably some simple explanation and solution to this, but I fail to see it. What am I doing wrong?


(EDIT) Maybe the two echoes are confusing, suppose my test.sh script contains this:

echo "ls *.txt;"

If I now do ./test.sh I'm getting

ls *.txt;

And if I copy and paste this on the shell it shows me all .txt files as expected.

However if I do $(./test.sh) I get:

ls: *.txt;: No such file or directory

What is the right syntax to have whatever the test.sh script outputs, to be executed as a shell command line?


Solution

  • The problem is that command substitution (the $( ) thing) is done partway through the process of parsing the command line -- after it's parsed and applied quotes, semicolons, and a bunch of other things. As a result, by the time those quotes, the semicolon, etc are part of the command line, it's too late for them to do anything useful, and they're just ordinary characters with no special meaning.

    This is one of those few cases where you actually want to use eval:

    eval "$(./test.sh)"
    

    What eval does is re-run the entire parsing process on its arguments, so any shell syntax in them (quotes, semicolons, whatever) gets fully parsed. The double-quotes are there to prevent that little bit of parsing that would've happened after the command substitution but before the result got passed to eval, which can cause weird effects.

    BTW, eval has a generally bad reputation because it can cause things you didn't expect to be treated as shell syntax (like filenames, etc) to be treated as shell syntax. But in this case, that seems to be exactly what you want and expect, so it's just the thing.