Reputation: 43427
Here is an excerpt from the config I am editing. This is inside the [alias]
section of my .gitconfig
:
ignored = !git ls-files -v | grep "^S"
status-with-ignored = "!f() { git status; ignored=$(git ignored | cut -d ' ' -f 2 | sed -e s/^/z/ -e s/$/z/); [ -n \"$ignored\" ] && echo \"git skip-worktree (ignored):\n$ignored\"; };f"
I am basically trying to colorize the output of git ignored
as I stick it onto the end of the git status. What I show above works fine, but it does not work (fatal: bad config file line 18 in ~/.gitconfig
) if my sed
command has inside it any parentheses (which are quite useful for building regexes).
For example, I want to be able to write something having parens, such as sed s/^(.*)$/z\1z/
instead of sed -e s/^/z/ -e s/$/z/
.
Since i'm colorizing stuff I would actually be using \x1b[31m
, \x1b[m
, etc., but you get the idea.
I have tried one, two, three, and four backslashes to escape these parens but nothing works.
Upvotes: 2
Views: 2111
Reputation: 95634
There are separate problems here: One with your config escaping, one with parentheses in sed
, and one with process substitution like <(date)
in git aliases.
Firstly, the problem with bad config file
isn't command substitution, it's your newline character.
[YOURS] status-with-ignored = "!f() { git status; ignored=$(git ignored | cut -d ' ' -f 2 | sed -e s/^/z/ -e s/$/z/); [ -n \"$ignored\" ] && echo \"git skip-worktree (ignored):\n$ignored\"; };f"
[MINE] status-with-ignored = "!f() { git status; ignored=$(git ignored | cut -d ' ' -f 2 | sed -e s/^/z/ -e s/$/z/); [ -n \"$ignored\" ] && echo \"git skip-worktree (ignored):\\n$ignored\"; };f"
It looks like your \n
is being interpreted as applying to the config file; that's the part that needs escaping, not anything paren-related.
I got the above by letting bash
and git config
handle the escaping for me, and then substituting cut's " "
for ' '
:
git config alias.status-with-ignored '!f() { git status; ignored=$(git ignored | cut -d " " -f 2 | sed -e s/^/z/ -e s/$/z/); [ -n "$ignored" ] && echo "git skip-worktree (ignored):\n$ignored"; };f'
(If you want to get clever about escaping single quotes within bash
single quotes, you can, but to me it works actively against readability.)
Secondly, Bash and other shells can't handle unquoted parentheses. That's not specific to git aliases, that's just the parsing rules.
echo I am (Batman) # Doesn't work
echo I am Batman # Does work
The latter one works; Batman has no parens.
You can either escape the parentheses or quote the string:
echo "I am (Batman)" # Works
echo I am \(Batman\) # Also works
Which leaves your config file like this:
status-with-ignored = "!f() { git status; ignored=$(git ignored | cut -d ' ' -f 2 | sed -e \"s/^(.*)$/z\1z/\"); [ -n \"$ignored\" ] && echo \"git skip-worktree (ignored):\\n$ignored\"; };f"
Or your git config
statement like this:
git config alias.status-with-ignored '!f() { git status; ignored=$(git ignored | cut -d " " -f 2 | sed -e "s/^(.*)$/z\1z/"); [ -n "$ignored" ] && echo "git skip-worktree (ignored):\n$ignored"; };
Finally, it seems that git alias
just doesn't handle process substitution. From the git config
man page:
Arguments are split by spaces, the usual shell quoting and escaping is supported. A quote pair or a backslash can be used to quote them.
It looks like git is using sh
or its own parsing, and not calling out to bash
itself, so as a non-portable bash extension process substitution isn't supported natively. You could shell out to bash:
git config alias.sample '!cat <(seq 3)'; git sample
git config alias.sample '!bash -c "cat <(seq 3)"'; git sample
...but at that point you might as well just make a script named git-status-with-ignored
and add it to your path.
Upvotes: 6