Parentheses for subshell don't work when store

2019-09-16 01:32发布

问题:

The command:

( echo 1 )

works fine when I input it in the command line but if I store it as a variable and call it, it gives the error:

(echo: command not found

Code:

input="( echo 1 )"
$input

Why doesn't it evaluate the parentheses the same way and put it into a subshell when I call it this way?

回答1:

This is discussed in full detail in BashFAQ #50.

An unquoted expansion goes through only two stages of shell parsing: Field-splitting, and glob expansion. Thus, ( echo 1 ) is first split into fields: (, echo, 1, and ); each is expanded as a glob (moot, as none of them are glob expansions); and then they're run as a command: ( is invoked, with the first argument echo, the second argument 1, and the third argument ).

The Right Way to store code is in a function:

# best-practices approach
input() ( echo 1; )
input

...or, if you want to make it more explicit to a human reader that you really want a subshell and weren't using parens rather than braces by error or habit:

# same, but more explicit about intent
input() { (echo 1); }
input

...if not possible, one can use eval (but be wary of the caveats given in BashFAQ #48):

# avoid this approach if at all possible
input="( echo 1 )"
eval "$input"

If the real reason you're building a command in a string is to parameterize its contents, use an array instead:

input_args=( 1 )                    # define an array
input() ( echo "${input_args[@]}" ) # use that array in a function (if needed)

# add things according to conditional logic as appropriate
if (( 2 > 1 )); then
  input_args+=( "possible argument here" )
fi

# call the function, or just use the array directly, such as: (echo "$(input_args[@]}" )
input


回答2:

The parentheses are shell syntax.

To reflect syntax which is stored in variable back into the shell for processing, you must use the eval command.

Merely interpolating the value of the variable into a command line doesn't trigger eval evaluation of the syntax. That would be an unwanted intrusion of eval, which would cause all sorts of problems.

For instance, consider this:

arg="(parenthesized)"
somecommand $arg

We just want somecommand to be invoked with the character string (parenthesized) as its argument; we don't want to run a subshell. That sort of implicit eval would turn harmless data into live code, creating a security hole, not to mention a coding nightmare to try to avoid it.

The rules for how $arg is treated are independent of position; the expansion happens the same way even if $arg is the first element in the command line:

$arg foo

Parameter substitution turns $arg into the text (parenthesized) and then that is tried as a command, not as a shell syntax.

To execute the piece of shell script stored in input, use:

eval "$input"

You could also do it like this:

input="echo 1"       # no parentheses

/bin/sh -c "$input"  # run in subshell explicitly

Of course, the subshell approach feeds the code fragment to the same shell which is executing the surrounding script, whereas here we chose the /bin/sh which could be different from the invoking shell, or have a different behavior even if it is a symlink to the same shell.