9

I am trying to amend the behavior of git, using the approach outlined here, but I am stumbling on how to properly pass on the contents of $@ without losing the quotes of the original input.

Basically, I have this:

# foo.sh

#!/bin/bash
cmd=$1
shift
args=$@
if [ $cmd == "bar" ]; then args=('--baz' "${args[@]}"); fi
echo git $cmd ${args[@]}

But when I run ./foo.sh bar -a "one two three", it outputs (and thus would have run), ./foo.sh bar --baz -a one two, rather than ./foo.sh bar --baz -a "one two" as I would have needed.

I can't figure out how to properly pass on $@ to another command and preserve quoted arguments. Is it possible? How?

1 Answers1

15

When using "${args[@]}" you assume that args is an array, but it's not.

For args to be an array, you must assign to it with an array assignment, such as

args=( "$@" )

To have the elements of the array individually quoted when you expand ${args[@]}, you must double quote the expansion (just as with "$@"):

echo git "$cmd" "${args[@]}"

In this simple example, the separate args array is not really needed at all:

#!/bin/sh

cmd=$1
shift

if [ "$cmd" = "bar" ]; then
   set -- --baz "$@"
fi

echo git "$cmd" "$@"

Note that the quotes won't be outputted by the echo command above. But you can rest assured that echo gets exactly two arguments (git and $cmd) plus however many things are in $@ at that point. This is because the shell does quote removal on the command before executing it. You can compare this with what e.g. echo "one two" three outputs.

A better way to output the the command for visual inspection would maybe be

printf 'Arg: %s\n' git "$cmd" "$@"

This would print each separate argument to printf on its own line (prefixed by the string Arg:).

Your git command, git "$cmd" "$@" (or git "$cmd" "${args[@]}" if you use a separate array), would run correctly (given that the variables makes sense, ordinary git doesn't have a bar sub-command with a --baz option, for example).

Related:

Kusalananda
  • 333,661