Why is this Bash function inside the git alias executed twice, and why fix the addition of `exit`?

If I cannot explicitly call exit for certain Bash functional scripts, then for some functions there are additional unexpected executions. What is the reason for this? Behavior was first seen when creating git alias as part of an answer https://stackoverflow.com/a/212618/ This alias consisted of this script (which executes the function twice, not once):

 #!/usr/bin/env bash github(){ echo github; }; twitter(){ echo twitter; }; facebook(){ echo facebook; }; if [[ $(type -t "$1") == "function" ]]; then "$1"; else echo "There is no defined function for $1"; fi; 

But this slightly modified script is executed as expected (it runs the function only once):

 #!/usr/bin/env bash github(){ echo github; }; twitter(){ echo twitter; }; facebook(){ echo facebook; }; if [[ $(type -t "$1") == "function" ]]; then "$1"; exit 0; else echo "There is no defined function for $1"; exit 1; fi; 

Here's what happens when I run these scripts using the git alias (set was added for debugging purposes only):

 $ git config --global alias.encrypt-for '!set -evu -o pipefail;github(){ echo github;};twitter(){ echo twitter;};facebook(){ echo facebook;};if [[ $(type -t "$1") == "function" ]];then "$1"; exit 0; else echo "There is no defined function for $1"; exit 1; fi;' $ git encrypt-for "github" type -t "$1" github $ git config --global alias.encrypt-for '!set -evu -o pipefail;github(){ echo github;};twitter(){ echo twitter;};facebook(){ echo facebook;};if [[ $(type -t "$1") == "function" ]];then "$1"; else echo "There is no defined function for $1"; fi;' $ git encrypt-for "github" type -t "$1" github github 

Exiting set -x :

 $ git encrypt-for "github" ++ type -t github + [[ function == \f\u\n\c\t\i\o\n ]] + github + echo github github + github + echo github github 

The result of replacing echo github with echo "I am echo in github" as a way to exclude the echo command as the source of the second function execution:

 $ git encrypt-for "github" ++ type -t github + [[ function == \f\u\n\c\t\i\o\n ]] + github + echo 'I am echo in github' I am echo in github + github + echo 'I am echo in github' I am echo in github 

The following is the simplest version of the / script alias that gives undesired double-execution behavior:

 g(){ echo "once"; }; $1; 

And this is the result of executing a simplified alias / script (which has the wrong execution behavior twice):

 $ git config --global alias.encrypt-for '!g(){ echo "once";};$1;' $ git encrypt-for g once once 
+8
git bash shell alias exit
source share
2 answers

What due to the way git handles aliases:

Given an alias

 [alias]
     myalias =! string

where string is any string representing some code when calling git myalias args , where args is a (possibly empty) argument list, git will do:

     sh -c 'string "$ @"' 'string' args

For example:

 [alias] banana = !echo "$1,$2,SNIP " 

and challenge

 git banana one 'two two' three 

git will do:

 sh -c 'echo "$1,$2,SNIP " "$@"' 'echo "$1,$2,SNIP "' one 'two two' three 

and therefore the output will be:

 one,two two,SNIP one two two three 

In your case

 [alias] encrypt-for = "!g(){ echo \"once\";};$1;" 

and challenge

 git encrypt-for g 

git will do:

 sh -c 'g(){ echo "once";};$1;"$@"' 'g(){ echo "once";};$1;' g 

For clarity, let me rewrite this in equivalent form:

 sh -c 'g(){ echo "once";};$1;"$@"' - g 

I replaced only the part of 'g(){ echo "once";};$1;' (which will be <positional) sh $0 and will not play any role here) dummy argument - . It should be clear that this is similar to doing:

 g(){ echo "once";};g;g 

so you see:

 once once 

To fix this: do not use the parameters! just use:

 [alias] encrypt-for = "!g(){ echo "once";};" 

Now, if you really want to use the parameters, make sure that the specified trailing parameters are not executed at all. One possibility is to add the following comment character like this:

 [alias] encrypt-for = "!g(){ echo "once";};$1 #" 

In your complete example, wrapping everything in a function could also be a cleaner way:

 [alias] encrypt-for = "!main() {\ case $1 in \ (github) echo github;; \ (twitter) echo twitter;; \ (facebook) echo facebook;; \ (*) echo >&2 \"error, unknown $1"\; exit 1;; \ esac \ }; main" 

I hope you understand what git does under the hood with aliases! it really adds "$@" to the alias line and calls sh -c with that line and given arguments.

+11
source share

The answer to this question was gniourf_gniourf , so I created a version of the simplified alias / script that works the way I originally planned. Since this is technically the answer, and not part of the question, I added this as an answer. This answer complements the other answer of gniourf_gniourf and is not intended to receive credit from its correct answer.

This fixed version of a simplified script either executes the found function or doesn’t output anything, and the fact that Git places #@ at the end of the script is fixed by adding a comment at the end of the script. This is a fixed version of a simplified script (which gives the correct execution of execution of execution once):

 g(){ echo "once"; }; if [[ $(type -t "$1") == "function" ]]; then $1; fi; # 

Here is the result of this patched version of the simplified alias / script (which has the correct behavior: execute once and display nothing for unknown input):

 $git config --global alias.encrypt-for '!g(){ echo "once";};if [[ $(type -t "$1") == "function" ]];then $1; fi;#' $ git encrypt-for g once $ git encrypt-for github $ git encrypt-for facebook $ exit 

The bottom line is that because of the way Git handles aliases (see gniourf_gniourf's answer for an explanation of this), you have to do with the fact that $@ will suffix at the end of your alias / script.

+2
source share

All Articles