0

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?

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! Works great for my case. And yes I understand how there can be many dangers and pitfalls when using eval, good point. One more question: is it also possible to both display the output of test.sh and execute it, using a single command line?
@RocketNuts Printing it as well can be a little tricky; depending on your OS, you can probably do something like eval "$(./test.sh | tee /dev/stderr)" (which, as you might expect, sends a copy to standard error instead of standard output -- but they're usually both the terminal). Another possibility is cmd=$(./test.sh); echo "$cmd"; eval "$cmd".
0

You have an extra echo. Here is the code (test.sh):

#!/bin/bash
echo "hello"

Then run it like that: $(./test.sh) or "$(./test.sh)". It will launch hello command. If you want test.sh to run a script, use echo "./hello" in test.sh (with ./).

To display commands before execution, run this with bash -x test.sh

1 Comment

The first echo is for the script to output something. The 2nd echo is inside the output, the text I want to display, which is also a valid command on itself. Perhaps it's confusing that they are both 'echo', I will edit my question and put something else in there.

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.