Bash Expression Evaluation Order on Command Line

2019-05-31 22:14发布

Background:

I'm working on quickly calling bash command line expressions inside of SGE's job submission program qSub in parallel. While doing so, I was attempting to submit an expression (as an argument) to be ran inside of another script like so:

./runArguments.sh grep foo bar.txt > output.txt

runArguments.sh looks like this:

#!/bin/bash  
${1} ${2} ${3} etc....to 12

The idea is that I want "grep foo bar.txt > output.txt" to be evaluated in the script...NOT ON THE COMMAND LINE. In the example above, "grep foo bar.txt" will evaluate during runArguments.sh execution, but the output redirection would be evaluated on the command line. I eventually found a working solution using "eval" that is shown below, but I do not understand why it works.

Question(s)

1) Why does

./runArguments.sh eval "grep foo bar.txt > output.txt"

allow the eval and the expression to be taken as arguments, but

./runArguments.sh $(grep foo bar.txt > output.txt)

evaluates on the command line before the script is called? (the output of $(grep...) is taken as the arguments instead)

2) Is there a better way of doing this?

Thanks in advance!

1条回答
Bombasti
2楼-- · 2019-05-31 23:00

Your first question is a bit hard to answer, because you've already answered it yourself. As you've seen, command substitution (the $(...) or `...` notation) substitutes the output of the command, and then processes the result. For example, this:

cat $(echo tmp.sh)

gets converted to this:

cat tmp.sh

So in your case, this:

./runArguments.sh $(grep foo bar.txt > output.txt)

runs grep foo bar.txt > output.txt, grabs its output — which will be nothing, since you've redirected any output to output.txt — and substitutes it, yielding:

./runArguments.sh

(so your script is run with no arguments).

By contrast, this:

./runArguments.sh eval "grep foo bar.txt > output.txt"

does not perform any command substitution, so your script is run with two arguments: eval, and grep foo bar.txt > output.txt. This command inside your script:

${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10} ${11} ${12}

is therefore equivalent to this:

eval grep foo bar.txt '>' output.txt

which invokes the eval built-in with five arguments: grep, foo, bar.txt, >, and output.txt. The eval built-in assembles its arguments into a command, and runs them, and even translates the > and output.txt arguments into an output-redirection, so the above is equivalent to this:

grep foo bar.txt > output.txt

. . . and you already know what that does. :-)


As for your second question — no, there's not really a better way to do this. You need to pass the > in as an argument, and that means that you need to use eval ... or bash -c "..." or the like in order to "translate" it back into meaning output-redirection. If you're O.K. with modifying the script, then you might want to change this line:

${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10} ${11} ${12}

to this:

eval ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10} ${11} ${12}

so that you don't need to handle this in the parameters. Or, actually, you might as well change it to this:

eval ${@}

which will let you pass in more than twelve parameters; or, better yet, this:

eval "${@}"

which will give you slightly more control over word-splitting and fileglobbing and whatnot.

查看更多
登录 后发表回答