简体   繁体   中英

How can I pass a complete argument list in bash while keeping mulitword arguments together?

I am having some issues with word-splitting in bash variable expansion. I want to be able to store an argument list in a variable and run it, but any quoted multiword arguments aren't evaluating how I expected them to.

I'll explain my problem with an example. Lets say I had a function decho that printed each positional parameter on it's own line:

#!/bin/bash -u
while [ $# -gt 0 ]; do
  echo $1
  shift
done

Ok, if I go decho ab "cd" I get:

[~]$ decho a b "c d"
a
b
c d

Which is what I expect and want. But on the other hand if I get the arguments list from a variable I get this:

[~]$ args='a b "c d"'
[~]$ decho $args
a
b
"c
d"

Which is not what I want. I can go:

[~]$ echo decho $args | bash
a
b
c d

But that seems a little clunky. Is there a better way to make the expansion of $args in decho $args be word-split the way I expected?

您可以使用:

eval decho $args

You can move the eval inside the script:

#!/bin/bash -u
eval set -- $*
for i; 
do 
  echo $i;
done

Now you can do:

$ args='a b "c d"'
$ decho $args
a
b
c d

but you'll have to quote the arguments if you pass them on the CL:

$ decho 'a b "c d"'
a
b
c d

It is fundamentally flawed to attempt to pass an argument list stored in a variable, to a command.

Presumably, if you have code somewhere to create a variable containing the intended args. for a command, then you can change it to instead store the args into an array variable:

decho_argv=(a b 'c d')  # <-- easy!

Then, rather than changing the command "decho" to accommodate the args taken from a plain variable (which will break its ability to handle normal args) you can do:

decho "${decho_argv[@]}"  # USE DOUBLE QUOTES!!!

However, if you are the situation where you are trying to take arbitrary input which is expected to be string fields corresponding to intended command positional arguments, and you want to pass those arguments to a command, then you should instead of using a variable, read the data into an array.

Note that suggestions which offer the use of eval to set positional parameters with the contents of an ordinary variable are extremely dangerous.

Because, exposing the contents of a variable to the quote-removal and word-splitting on the command-line affords no way to protect against shell metachars in the string in the variable from causing havoc.

Eg, imagine in the following example if the word "man" was replaced with the two words "rm" and "-rf" and the final arg word was "*":

Do Not Do This:

> args='arg1 ; man arg4'
> eval set -- $args
No manual entry for arg4
> eval set -- "$args"        # This is no better
No manual entry for arg4
> eval "set -- $args"        # Still hopeless
No manual entry for arg4

> eval "set -- '$args'"  # making it safe also makes it not work at all!
> echo "$1"
arg1 ; man arg4

Have you tried:

for arg in "$@"
do
        echo "arg $i:$arg:"
        let "i+=1"
done

Should yield something like:

arg 1: a
arg 2: c d

in your case.

Straight from memory, no guarantee :-)

hmmm.. eval decho $args works too:

[~]$ eval decho $args
a
b
c d

And I may be able to do something with bash arrays using "${array[@]}" (which works like "$@" ), but then I would have to write code to load the array, which would be a pain.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM