Home > Enterprise >  how to keep `$@` arguments seperated when using them in a string?
how to keep `$@` arguments seperated when using them in a string?

Time:05-11

Hey I'm writing a wrapper script for git that applies one git command to all its submodules e.g.: supergit commit -m "change message" commits to all submodules.
The script essentially does:

function git_foreach () {
    git submodule foreach "git \"$@\" || : "
}

git_foreach "$@"

The problem is when the supergit call contains an argument with spaces (like in the commit message above) the space separated calls are interpreted as multiple arguments. I read in this answer that the way to do it is to use "$@" but that doesn't work within a string.

Is there a way to expand $@ to keep the quotes so that my function works as expected?

EDIT:
What I want is to pass the arguments to git submodule foreach, with supergit commit -m "commit message" I want to run:
git submodule foreach "git commit -m \"commit message\""

CodePudding user response:

If Your /bin/sh Is Provided By Bash

printf %q will generate a version of your data correctly escaped to be parsed by a shell.

printf '%q ' "$@" generates a string containing an individually shell-escaped word for each argument in your array, with spaces after each word.

Thus:

git_foreach() {
    local cmd_q
    printf -v cmd_q '%q ' "$@"
    git submodule foreach "git $cmd_q ||:"
}

...or, with bash 5.0 or newer (which adds a ${var@Q} expansion), we can make this one line:

git_foreach() { git submodule foreach "git ${@@Q} ||:"; }

With current implementations of printf %q, git_foreach commit -m "commit message" invokes git submodule foreach 'git commit -m commit\ message' or a semantic equivalent; the escaping isn't identical to how you would write it by hand, but the effect of the command is exactly the same.


If You Need Broader Compatibility

Unfortunately, both printf %q and the newer ${variable@Q} expansion are able to generate strings that use bash-only syntax (particularly if your string contains newlines, tabs, or similar). If you don't control which shell git starts to run the foreach commands, then we need to generate a string that's escaped for consumption by any POSIX-compliant shell.

Bash doesn't have a feature for doing that... but Python does!

posix_escape() {
  python3 -c 'import sys, shlex; print(" ".join([shlex.quote(s) for s in sys.argv[1:]]))' "$@"
}

git_foreach() {
  local cmd_q
  cmd_q=$(posix_escape "$@")
  git submodule foreach "git $cmd_q ||:"
}

CodePudding user response:

Well, it's been a while since I used submodules, but try single quotes inside the string. The $@ will be expanded into its component arguments as the string parsed, so what gets passed is the values, which will be preserved as a single string by the single quotes.

$: set -- a 1 b 2 c 3
$: echo "foo '$@' bar"
foo 'a 1 b 2 c 3' bar
  •  Tags:  
  • bash
  • Related