为什么我要创建一个创建函数的别名?

29

我偶尔会看到这种模式,特别是在有关Bash提示符自定义的问题中。

alias f='_ () { useful code; }; _'

我完全看不出在这里创建别名的理由。显而易见的重构

f () { useful code; }

避免完全声明别名,而是一次性定义函数,似乎更简单、更易理解、更不脆弱、更高效。(如果不明显的话,每次调用别名时都会重新声明函数。)例如,如何制作一个带参数的Bash别名?有几个答案展示了这种技术。bash脚本运行lftp和ftp路径是一个关于实际功能内部函数的问题,OP没有解释为什么,尽管我轻轻地提示过。这只是一个反模式,还是有实际原因要这样做?在什么情况下,这种设计是有意义的?这与别名后面有空格或代码混淆无关(我找到的例子通常是完全可读的,除了这个令人困惑的技巧)。

1
@anishsane 不错的假设,但是为什么要混淆你自己的Bash提示符呢?而且,如果你真的想让代码难以阅读,这并不是一个很好的混淆方式。 - tripleee
1
这可能是一个可能的答案,尽管在我看来,缺点远远超过了实际的好处。 - tripleee
1
@AdamKatz function不会引起任何更高的优先级。 function f() { echo foo; }; f () { echo bar; }; f 仍然输出 bar ,因为第二个函数替换了第一个。 - chepner
2
在 https://dev59.com/4HA75IYBdhLWcg3wYYBQ#3322412 中提到了 git 内部的 alias 的技巧。也许有不同的情况,可能有人从中得到了错误的灵感。 - Walter A
1
kubectl Cheat Sheet 也使用了这种模式。我在 这个问题 中向他们询问了澄清。 - EndlosSchleife
显示剩余9条评论
5个回答

16
以下是我的个人意见和对这个话题的理解。在使用别名和函数时,有一定程度上是开发者个人喜好的问题。我将添加一些两种方法之间的区别,这也可能影响使用别名与函数的个人偏好。
有时候,大部分我想做的事情都可以使用别名来完成,但只有少数需要使用参数。因此,我会使用别名与函数本身结合起来。
例如:
alias kgps='kubectl get pods --all-namespaces | grep '

这很好用,我可以搜索我的kubernetes pods。现在要删除这些pods,我需要在命令之间传递相同的参数,所以我使用一个包含函数的别名。

alias kdp="_(){ kubectl get pods --all-namespaces  | grep \$1 | awk '{print \$2}' | xargs kubectl delete pod; }; _"

我的大多数快捷键命令都可以通过别名(aliases)执行,只有少数需要这些东西的命令我才会使用别名与函数结合。

别名 vs 函数

现在,我想强调一下别名和函数之间的几个区别:

与函数相比,别名更容易覆盖系统命令

如果我需要覆盖 ls 命令,我可以更轻松地使用 别名(alias) 实现。

alias ls='ls -altrh'

相应的函数代码如下:

ls() { command ls -altrh "$@";}
ls() { /bin/ls -altrh "$@";}

别名通常用于创建快捷方式

别名主要用于创建快捷命令,而函数则用于许多其他方面,如复杂命令组合、自动补全和Bash提示。

别名更易管理

运行alias命令即可获取当前活动别名列表。

$ alias
....
vs='vagrant ssh'
vu='vagrant up'
vus='vu && vs'
....

为了获取我们需要使用的函数列表,我们需要使用 declare -f 或其他类似的命令。
$ declare -f | wc -l
  8226
$ alias | wc -l
  217

现在,如果我发布declare -f的部分输出结果如下:
$ declare -f
...
vi_mode_prompt_info () {
    return 1
}
virtualenv_prompt_info () {
    return 1
}
work_in_progress () {
    if $(git log -n 1 2>/dev/null | grep -q -c "\-\-wip\-\-")
    then
        echo "WIP!!"
    fi
}
zle-line-finish () {
    echoti rmkx
}
zle-line-init () {
    echoti smkx
}
zsh_stats () {
    fc -l 1 | awk '{CMD[$2]++;count++;}END { for (a in CMD)print CMD[a] " " CMD[a]/count*100 "% " a;}' | grep -v "./" | column -c3 -s " " -t | sort -nr | nl | head -n20
}

您可以看到有许多与我无关的函数被使用。但是,alias 命令给出了非常简洁的输出,我可以轻松地看到所有内容。在我的情况下,它们全部都是快捷命令。

转义别名和函数的语法对系统命令有所不同

要转义定义的别名,您需要在前面加上 \,而对于 functions,您需要使用 command <originalcommand> 或者命令的绝对路径 /bin/originalcommand

别名比函数优先级更高

请查看以下示例

alias ls='echo alias && ls'
$ ls() { /bin/ls -al }
alias
$ ls
alias
total 23173440
drwxrwxr-x+ 255 tarunlalwani  staff        8160 Jul 30 22:39 .
drwxr-xr-x+ 113 tarunlalwani  staff        3616 Jul 30 23:12 ..
...

当我们运行ls命令时,首先使用别名,然后调用函数中的下一个ls。这也成为一种使用相同名称包装现有函数并在内部重复使用原始函数的方法,只能使用alias完成,并促进了问题中的格式。


2
这就解释了为什么有些人(在我看来是错误的)喜欢使用别名,但并没有真正解释为什么你会在别名中包装一个函数。 - tripleee
如果你有没有用到的函数,这不能成为反对你已经使用的函数的理由。一些发行版也会在默认配置文件中定义别名(我通常在注意到时会解除类似unalias ll之类的别名设置,以保持我的环境清洁)。 - tripleee
1
我会继续深入挖掘,看看是否能找到其他可以解释这个问题的东西。但目前为止,这就是我所获取的。 - Tarun Lalwani
实际上,这解决了我的一个问题——我创建了一些自定义函数,但与运行“alias”以获取列表的简便性相比,列出它们很困难。这可能足以让我开始使用此语法来编写我的自定义函数。 - Richard Neish

8
我发现这个答案解释了在Bash中定义函数别名的好处。
声明别名而非函数的好处在于,如果你source-ing(或使用.)一个脚本,该脚本恰好声明了同名函数,你的别名不会被简单地覆盖。

一个很好的解释为什么函数比别名更好:https://stackoverflow.com/a/47334598/6862601 - codeforester

8

我发现了一个关于相关话题的Ask Ubuntu问题,其中的一个答案声称这是对不同设计原则的误解:给函数起一个长而具有描述性的名称,并为方便起见创建一个更短的别名。

仍然无法理解为什么每次都要用别名重新声明该函数。


我同意你的想法,选择一个合理、简洁且描述性强的名称开始编程,不要使用alias这个词。它是多余的。 - David C. Rankin
2
一个观点是,别名比函数具有更高的优先级。所以也许会有一些区别?只是猜测,但很想知道完整的解释。 - Mihir Luthra
说“别名比函数优先级高”很容易(因为别名扩展发生在命令查找之前),但这并不能解释为什么您需要或需要这样的覆盖行为。 - chepner
@chepner 当你想用一个临时别名覆盖一个现有的复杂函数时,你可以通过unalias恢复原来的函数。 - Walter A

3
这只是一个明显的反模式吗?我认为它的普及可能只是空手套白狼编程。别名易于理解,因此用户通常首先学习它们。随着用户技能和需求的增加,他们发现别名缺乏灵活的参数处理。因此,他们进行网络搜索,比如"shell alias parameter passing",然后找到建议使用该模式的帖子。
alias foo='_() { echo $2 $3 $1; }; _'

看哪,它起作用了。用户很满意,然后他们继续使用。
但是因为_()序列看起来很像一个shell咒语(2>&1>>等),用户从来没有想过_()只是function _的紧凑语法,并且从未进入学习函数的下一步。通过这种别名模式,他们获得了所有函数的好处,而不必学习“新”的语法。最可能甚至没有注意到令人讨厌的副作用:覆盖任何以前命名为_的函数。
我搜索了1981年到1991年的Usenet,但没有找到直接证据证明这个理论。

...还是有实际原因要这样做吗?在什么情况下这种设计有意义?

在我起草这个答案的五天里,我想出的每个理由都反对它。选择的答案 - 别名无法在子shell中被掩盖 - 是一个充分的理由,尽管我从来没有想过那么多:我不会去源代码我没有完全审查过的代码。

2

您可以使用别名来开启和关闭不想更改的功能。 假设您有调用函数_的代码。您可以通过以下方式切换函数的实现为另一个:

最初的回答

alias f='_ () { echo "useful code"; }; _'
alias g='_ () { echo "Other useful code"; }; _'
alias h='_ () { echo "Special code"; }; _'

最初的回答
现在你可以调用
f
_
g
_
h
_
f

@DavidC.Rankin正确地评论了,它看起来很糟糕。
我同意。
我想到了一些使用它的方法。你可以将其用于测试软件之类的东西。

最初的回答

alias ok='commitTransaction () { echo "commited"; return 0; }'
alias nok='commitTransaction () { echo "unknown error"; return 1; }'
alias locked='commitTransaction () { echo "locked"; return 2; }'
alias slow='commitTransaction () { sleep 20; echo "commited"; return 0;  }'

现在测试人员可以运行他的测试用例:
ok
# And now start ok test
nok
# And now start nok test

继续进行黑客攻击,何不制作一个更好的测试工具呢?


这不可能是人们独自做这件事的原因。我将在问题中更新一个链接,提供实际的现实例子。 - tripleee
@tripleee 我同意。更新后的问题中的链接显示,它可能被构造为回答“如何向别名添加参数”,其中使用alias是一个“要求”。 - Walter A
那个具体的问题在这方面是一个糟糕的例子;但正如问题中已经提到的,我在很多地方都看到这种情况,其中别名并不是使用案例的核心。Git自定义Bash提示符是一个反复出现的例子。 - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接