bash: get literal parameters passed to a script an

2019-07-05 18:56发布

Goal and context

I am writing a bash script (called foreach_repo) that should execute the passed parameters as a shell command, e.g.:

 foreach_repo hg status

should execute the command hg status. (It does this on a complicated nested structure of repositories, I have no control over this structure, but I need to batch operate on them quite often).

This type of command is similar to sudo and other 'higher-order' commands; for example sudo hg status would in turn execute hg status with superuser rights.

Internally, my script does the following (given a feed of repository paths on stdin, created by another part of the script - irrelevant to this question):

while read repo do
    container="$repo/.."
    cd $base/$container
    $@
done

Where $@ is meant to interpret the passed arguments as the command to be executed in the repository.

Execution

This approach works fine for simple commands, for example

foreach_repo hg status

will correctly return the status list of every repository in the structure. However, more complicated commands (with escapes, quotes ...) are messed up. For example, when I try

foreach_repo hg commit -m "some message"

I get

abort: message: no such file or directory

Because the quotes were stripped off, the actual command executed was:

hg commit -m some message

Attempted solutions

Manually escaping the quotes, or the entire command to be passed, has no effect, neither has using $* instead of $@. However, commands like sudo can handle this type of situation, that is:

sudo hg commit -m "some message"

would actually (and correctly) execute

hg commit -m "some message"

How can I accomplish the same behavior?

1条回答
放荡不羁爱自由
2楼-- · 2019-07-05 19:30

You are on the right track, and almost got it. You just need to use "$@" instead of $@.

Here's a summary of what $* and $@ do, with and without quotes:

  • $* and $@ paste in the positional arguments, then tokenise them (using $IFS) into separate strings.
  • "$*" pastes in the positional arguments as one string, with the first character of $IFS (usually a space) inserted between each.
  • "$@" pastes in the positional arguments, as a string for each argument.

Examples:

$ set foo bar "foo bar:baz"
$ printf "%s\n" $*
foo
bar
foo
bar:baz
$ printf "%s\n" $@
foo
bar
foo
bar:baz
$ printf "%s\n" "$*"
foo bar foo bar:baz
$ printf "%s\n" "$@"
foo
bar
foo bar:baz

Here's what changes when you set $IFS:

$ IFS=:
$ printf "%s\n" $*
foo
bar
foo bar
baz
$ printf "%s\n" $@
foo
bar
foo bar
baz
$ printf "%s\n" "$*"
foo:bar:foo bar:baz
$ printf "%s\n" "$@"
foo
bar
foo bar:baz
查看更多
登录 后发表回答