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