为什么这个 Bash 函数在 git 别名中执行两次,为什么添加 `exit` 可以修复它?

Why is this Bash function within a git alias executing twice, and why does adding `exit` fix it?

如果我没有明确调用exit for certain function-based Bash scripts then there are additional unexpected executions for some functions. What is causing this? The behavior was first noticed while making a git alias as part of answering 。该别名由以下脚本组成(其中 运行 函数两次而不是一次 ):

#!/usr/bin/env bash

github(){
        echo github;            
};

twitter(){ 
        echo twitter;            
};

facebook(){ 
        echo facebook;
};

if [[ $(type -t "") == "function" ]];
then 
        "";
else
        echo "There is no defined function for ";
fi;

但是这个稍微修改过的脚本按预期执行(运行函数只执行一次):

#!/usr/bin/env bash

github(){
        echo github;            
};

twitter(){ 
        echo twitter;            
};

facebook(){ 
        echo facebook;
};

if [[ $(type -t "") == "function" ]];
then 
        "";
        exit 0;
else
        echo "There is no defined function for ";
        exit 1;
fi;

这正是我通过 git 别名 运行 这些脚本时发生的事情(添加 set 命令仅用于调试目的 ):

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

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

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

echo github 替换为 echo "I am echo in github" 作为排除 echo 命令作为第二个函数执行源的方式的输出:

$ 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

以下是 alias/script 的最简单版本,它给出了双重执行的不良行为:

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

这是执行简化的 alias/script 的结果输出( 执行两次 的行为不正确):

$ git config --global alias.encrypt-for '!g(){ echo "once";};;'
$ git encrypt-for g
once
once

这是因为 git 处理别名的方式:

给定别名

[alias]
    myalias = !string

其中 <em>string</em> 是表示某些代码的任何字符串,当调用 git myalias <em>args</em> 其中 <em>args</em> 是一个(可能为空的)参数列表,git 将执行:

    sh -c 'string "$@"' 'string' <em>args</em>

例如:

[alias]
    banana = !echo ",,SNIP "

并打电话

git banana one 'two two' three

git 将执行:

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

所以输出将是:

one,two two,SNIP one two two three

在你的情况下,

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

并打电话

git encrypt-for g

git 将执行:

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

为清楚起见,让我以等效形式重写:

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

我只将 'g(){ echo "once";};;' 部分(将是 sh[=29=] 的位置参数,在这里不会发挥任何作用)替换为伪参数 -.应该很清楚,就像执行:

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

所以你会看到:

once
once

补救:不要使用参数!只需使用:

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

现在,如果您真的要使用参数,请确保根本不执行给定的尾随参数。一种可能是像这样添加尾随注释字符:

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

对于您的完整示例,更简洁的方法还可以是将所有内容包装在一个函数中:

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

希望您了解 git 使用别名的幕后黑手!它实际上将 "$@" 附加到别名字符串,并使用此字符串和给定的参数调用 sh -c

这个问题已经被 gniourf_gniourf so I have created a version of the simplified alias/script which works as I originally intended. Since this is technically an answer and not really part of the question, I have added this as an answer. This answer supplements the other answer by gniourf_gniourf 回答了,并不是要剥夺他正确答案的功劳。

此简化脚本的固定版本要么执行找到的函数,要么根本不输出任何内容,Git 将 $@ 放在脚本末尾的事实已由在脚本末尾添加注释。这是简化脚本的固定版本(它给出了执行一次的正确执行行为):

g(){
    echo "once";
};

if [[ $(type -t "") == "function" ]];
then
;
fi;
#

这是此简化版 alias/script 的更正版本的输出( 具有正确的行为:执行一次并且对于未知输入不显示任何内容):

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

最重要的是,由于 Git 处理别名的方式(参见 答案的解释),您必须解决 $@ 将作为后缀的事实alias/script.

结束