在Bash中,何时使用别名,何时编写脚本以及何时编写函数?


360

我花了将近10年的Linux使用时间问这个问题。这都是反复试验和随机的深夜互联网冲浪。

但是人们不需要10年的时间。如果我刚开始使用Linux,那么我想知道:什么时候使用别名,什么时候编写脚本,什么时候编写函数?

在涉及别名的地方,我将别名用于不带参数的非常简单的操作。

alias houston='cd /home/username/.scripts/'

这似乎很明显。但是有些人这样做:

alias command="bash bashscriptname"

(并将其添加到.bashrc文件中)

有充分的理由这样做吗?我真的很努力,但是我真的无法想到在任何情况下都需要这样做。因此,如果存在极端情况会有所作为,请在下面回答。

因为那是我要在PATH中放入内容的地方chmod +x,这是经过多年Linux反复试验之后出现的另一件事。

这将我带入下一个主题。举例来说,我添加了一个隐藏的文件夹(.scripts/只需添加一行到我的)主目录到我的PATH .bashrcPATH=$PATH:/home/username/.scripts/),所以任何可执行文件中有自动的自动填充。

如果需要的话。

不过,我真的不需要那个吗?我只会将其用于非Shell语言,例如Python。

如果是外壳,我可以在相同的内部编写一个函数.bashrc

funcname () {
  somecommand -someARGS "$@"
}

正如我所说,我通过反复试验发现了很多这样的问题。我只有在计算机死机时才真正看到功能的美,而当我周围的人不使用计算机时,我就不得不使用它们。

最终,我没有将整个脚本目录从一个计算机移动到另一个计算机,而是最终只用我自己的其他人替换了.bashrc,因为它们甚至从未进行任何修改。

但是我想念什么吗?

那么,您将如何告诉Linux新手何时使用别名,何时编写脚本以及何时编写函数?

如果不是很明显,我假设回答这个问题的人将使用所有三个选项。如果您仅使用别名,或仅使用脚本,或仅使用函数,或者仅使用别名和脚本,或者别名和函数或脚本和函数,则此问题并不是针对您的。



10
+1用于明确说明此问题不针对的{alias,script,function}的所有子集。+1代表孩子般的信念,您可以忽略空子集。
Thomas L Holaday

1
尽管该问题专门询问了bash,但请注意,较旧的Bourne外壳具有“别名”功能,但没有功能。如果您担心兼容性,这可能会有所不同。
AndyB

如果.bashrc真的是最好的地方,或者至少是一个可靠的地方,对此呢?在Linux中有很多方法可以做相同的事情,但是我很欣赏,但是在所有条件都相同的情况下,我更喜欢以最普通的方式做事情。
Kit10'6

Answers:


242

别名实际上(实际上)不应做多于更改命令的默认选项的事情。无非就是命令名称上的简单文本替换。它不能对参数做任何事情,而是将它们传递给它实际运行的命令。因此,如果您只需要在单个命令的开头添加参数,则别名将起作用。常见的例子是

# Make ls output in color by default.
alias ls="ls --color=auto"
# make mv ask before overwriting a file by default
alias mv="mv -i"

当您需要执行比别名更复杂的操作但不能单独使用时,应使用函数。例如,针对我询问的有关根据管道中是否存在来更改的默认行为的问题,请使用此答案grep

grep() { 
    if [[ -t 1 ]]; then 
        command grep -n "$@"
    else 
        command grep "$@"
    fi
}

这是一个完美的函数示例,因为它对于别名来说太复杂了(根据条件要求不同的默认值),但是在非交互式脚本中就不需要它了。

如果功能太多或功能太大,请将它们放入隐藏目录中的单独文件中,并将其来源到~/.bashrc

if [ -d ~/.bash_functions ]; then
    for file in ~/.bash_functions/*; do
        . "$file"
    done
fi

脚本应该独立存在。它应该具有可重复使用或用于多个目的的价值。


14
同样重要的是要记住,除非源于.或,否则source脚本将由单独的bash进程执行并具有自己的环境。因此,任何修改shell环境的内容(例如函数,变量等)都不会持久存在于运行脚本的shell环境中。
Will Vousden 2015年

260

其他答案根据个人喜好提供了一些软性的通用准则,但是忽略了在决定脚本,函数或别名时应该考虑的许多相关事实

别名和功能¹

  • 别名和函数的全部内容都存储在外壳程序的内存中。
  • 这样做的自然结果是别名和函数只能由当前外壳使用,而不能由您可以从外壳调用的任何其他程序使用,例如文本编辑器,脚本,甚至同一外壳的子实例。
  • 别名和函数由当前Shell执行,即它们在内部运行并影响Shell的当前环境。²运行别名或函数不需要单独的过程。

剧本

  • Shell不将脚本保存在内存中。而是在每次需要时从存储它们的文件中读取脚本。如果通过$PATH搜索找到了脚本,则许多外壳程序会将其路径名的哈希存储在内存中,以节省以后的$PATH查找时间,但这是不使用脚本时占用内存的程度。
  • 脚本可以比函数和别名更多的方式调用。它们可以作为参数传递给解释器,例如sh script,或直接作为可执行文件调用,在这种情况下,将#!/bin/sh调用shebang行(例如)中的解释器来运行它。在这两种情况下,脚本都是由一个单独的解释器进程运行的,其解释器环境与shell的环境不同,该脚本的环境不能以任何方式影响。实际上,解释器外壳甚至不必匹配调用外壳。因为以这种方式调用的脚本看起来像任何普通的可执行文件,所以它们可以被任何程序使用。

    最后,当前的Shell可以使用.或在某些Shell中读取和运行脚本source。在这种情况下,脚本的行为就像是按需读取的函数,而不是始终保存在内存中。

应用

鉴于以上所述,我们可以针对是否要制作脚本或函数/别名提出一些通用准则。

  • 除shell外,其他程序是否需要能够使用它? 如果是这样,它必须是一个脚本。

  • 您是否只想从交互式外壳程序中获得它? 通常希望在交互运行时更改许多命令的默认行为而不影响外部命令/脚本。对于这种情况,请在外壳程序的“仅交互式模式” rc文件中使用别名/函数集(bash为此.bashrc)。

  • 是否需要更改外壳环境? 函数/别名或源脚本都是可能的选择。

  • 是您经常使用的东西吗? 将其保存在内存中可能更有效,因此,如果可能的话,使其成为函数/别名。

  • 相反,这是您很少使用的东西吗? 在这种情况下,不需要它时就没有占用它的内存,因此将其设为脚本。


¹尽管函数和别名有一些重要的区别,但由于函数可以做别名可以做的一切,所以将它们组合在一起。别名不能具有局部变量,也不能处理参数,并且对于一行以上的内容不方便。

²Unix系统中每个正在运行的进程都有一个由一堆对组成的环境,这些variable=value对通常包含全局配置设置,例如LANG用于默认语言环境和PATH指定可执行搜索路径。


26
恕我直言,这是最好的答案。
卢克M

4
问题/答案格式是个好主意。我可能会偷。;-)
Mikel

4
值得注意的是:如果两个(或更多)脚本需要共享一些代码,则最好将这些代码放入一个函数中,该函数本身位于这两个脚本都导入/源出的第三个文件中。
kbolino

3
另一个添加到该问题列表中的项目:您是否需要即时更改命令中的功能?对脚本的更改将反映在所有会话中,而功能和别名必须在每个会话的基础上重新加载或重新定义。
Stratus3D

3
好答案。(对我而言)更重要的一点是:在为其他实用程序创建“快捷方式”时,最好使用别名,因为现有的自动完成功能仅适用于别名,而不适用于脚本或函数(别名为+1)。例如,通过创建一个alias g='gradle'我使用g别名时获得gradle自动补全功能,但当与带有gradle $*或函数的脚本一起使用时,它不会立即可用gradle $@
Yoav Aharoni

37

我认为这取决于每个人的口味。对我来说,逻辑是这样的:

  • 首先,我尝试创建一个别名,因为它是最简单的。
  • 如果事情太复杂以至于不能放在一行中,我会尝试使其成为一个函数。
  • 当函数开始扩展到超过十二行时,我将其放入脚本中。

确实没有什么可以限制您执行有效的操作


6
我经常跳过功能选项,并立即编写脚本。但我认为,这部分是一个品味的问题
伯恩哈德

2
如果您需要在多个脚本中使用某个功能,则该功能开始变得有意义。
尼尔斯2012年

7
...或者如果您需要副作用来更改当前的 shell。
glenn jackman 2015年

有道理,伙计!
探险家

15

至少部分是个人喜好问题。另一方面,在功能上有一些明显的区别:

  • 别名:仅适用于简单的文本替换,无参数/参数
  • 功能:易于编写/使用,完整的外壳脚本功能,仅在bash中可用
  • 脚本:或多或少像函数,但在bash之外也可用(可调用)

过去几年中,我一直在研究Shell脚本,我或多或少停止了编写别名(因为随着时间的推移它们都会逐渐发展成为函数),并且仅在需要在非bash环境中使用它们时才编写脚本。

PS:至于alias command="bash bashscriptname"我实际上没有任何理由这样做。即使bashscriptname不在$ PATH中,简单的操作alias c=/path/to/script也足够了。


1
alias command="bash bashscriptname"脚本中不一定必须是可执行的;在alias c=/path/to/script它必须。
马丁-マーチン

函数“仅在Bash内部可用”根本不是真的。如果您要说它们是仅限Bash的功能,那简直是错误的(Bourne shell和每个兼容的派生都有它们);而且,如果您要说它们是交互式外壳程序的功能,那也不是很准确(尽管在交互式外壳程序启动时加载的文件中定义的别名,变量和函数显然不会被非交互式外壳程序加载)。
三人

@tripleee含义更像是“您不能使用exec()shell函数” :-)
nohillside

11

以下是有关别名和函数的其他几点:

  • 同名别名和功能可以共存
  • 首先查找别名名称空间(请参阅第一个示例)
  • 不能在子shell或非交互式环境中设置(取消)别名(请参见第二个示例)

例如:

alias f='echo Alias'; f             # prints "Alias"
function f { echo 'Function'; }; f  # prints "Alias"
unalias f; f                        # prints "Function"

如我们所见,别名和函数有单独的命名空间。可以使用declare -A -p BASH_ALIASESdeclare -f f打印更多的详细信息(两者均存储在内存中)。

显示别名限制的示例:

alias a='echo Alias'
a        # OK: prints "Alias"
eval a;  # OK: prints "Alias"
( alias a="Nested"; a );  # prints "Alias" (not "Nested")
( unalias a; a );         # prints "Alias"
bash -c "alias aa='Another Alias'; aa"  # ERROR: bash: aa: command not found

我们可以看到,别名与函数不同,是不可嵌套的。而且,它们的使用仅限于交互式会话。

最后,请注意,可以通过立即调用函数来声明别名来进行任意计算,如下所示:

alias a_complex_thing='f() { do_stuff_in_function; } f'

在Git别名的情况下,它已经被广泛使用。这样做比声明一个函数的好处在于,别名不能简单地通过源代码(或使用.)一个恰好声明同名函数的脚本来覆盖。


10

什么时候写脚本...

  • 脚本将软件组件(也称为工具,命令,过程,可执行文件,程序)组装为更复杂的组件,这些组件本身也可以组装为更加复杂的组件。
  • 脚本通常是可执行的,因此可以按名称调用。当被调用时,将生成一个新的子进程供脚本运行。任何exported变量和/或函数的副本均按值传递给脚本。对这些变量的更改不会传播回父脚本。
  • 脚本也可以被加载(源),就好像它们是调用脚本的一部分一样。这类似于某些其他语言称为“导入”或“包含”的内容。来源时,它们在现有过程中执行。没有产生任何子进程。

何时编写函数...

  • 函数是有效地预加载的Shell脚本。它们的性能比调用单独的脚本好一些,但前提是必须从机械磁盘读取该脚本。如今,闪存驱动器,SSD以及Linux在未使用的RAM中的常规缓存的激增,使得这种改进在很大程度上无法衡量。
  • 函数是bash实现模块化,封装和重用的主要手段。它们提高了脚本的清晰度,可靠性和可维护性。
  • 调用函数的语法规则与调用可执行文件的语法规则相同。将调用与可执行文件同名的函数,而不是可执行文件。
  • 函数是它们所在脚本的本地函数。
  • 可以导出函数(按值复制),以便可以在称为脚本的内部使用它们。因此,功能只会传播到子进程,而不会传播到父进程。
  • 函数创建可重用的命令,这些命令通常被组装到库(仅包含函数定义的脚本)中,以供其他脚本使用。

什么时候写一个别名...

在诸如库脚本之类的脚本中,有时需要功能的别名,例如重命名功能但需要向后兼容时。这可以通过使用旧名称创建一个简单函数并将其所有参数传递给新函数来实现...

# A bash in-script 'alias'
function oldFunction () { newFunction "$@"; }

9

我不敢相信的另一件事是:函数在调用过程的上下文中执行,而脚本派生一个新的shell。

这可能是很重要的性能-函数更快,因为它不fork()exec()。在正常情况下,差异很小,但是如果您调试的系统内存不足且页面崩溃,则可能会有很大的不同。

另外,如果要修改当前的Shell环境,则应使用一个函数。例如,一个函数可以更改$PATH当前shell 的命令查找,但是脚本不能更改,因为它在的fork / exec副本上运行$PATH


向孩子的这种功能传播如何起作用?
HappyFace

1
@HappyFace在Bash中,您可以使用export -f一个函数,尽管其确切的内部功能有些晦涩。我相信这不能移植到传统的Bourne外壳上。
三点

7

脚本和别名与脚本和功能不是互斥的。您可以并且确实将别名和函数存储在脚本中。

脚本只是使持久化的代码。您希望将来使用的有用功能和别名存储在脚本中。但是,脚本通常是多个功能的集合。

由于别名没有参数化,因此它们非常有限。通常定义一些默认参数。

功能是代码的单独的单元,明确定义的的不能分离成更小的,有用的部分的几行代码的概念; 一个可以直接重用,也可以由其他功能重用。


5

如果应该非常快,则使其成为别名或函数。

如果应该在首选外壳程序之外使用它,请使其成为脚本。1个

如果带有参数,则使其成为函数或脚本。

如果需要包含特殊字符,请使其成为别名或脚本。2

如果需要使用sudo,请使其成为别名或脚本。3

如果要在不注销和登录的情况下轻松更改它,则脚本会更容易。4

脚注

1或使其成为别名,将其放入~/.env并设置export ENV="$HOME/.env",但是使其可移植地工作很复杂。

2函数名称必须是标识符,因此它们必须以字母开头,并且只能包含字母,数字和下划线。例如,我有一个别名alias +='pushd +1'。它不能是一个函数。

3并添加别名alias sudo='sudo '。同上任何其他命令,例如stracegdb等等,需要一个命令作为它的第一个参数。

4另请参见:fpath。当然,您也可以做source ~/.bashrc类似的事情,但这通常会有其他副作用。


1
我不知道您可以+在bash中使用别名。有趣的是,经过测试后,我发现可以在bash中创建+别名,但不能创建函数,正如您所说,但是zsh是相反的- +可以是函数但不是别名。
凯文(

zsh你要写alias -- +='some command here'
Mikel

不知何故,我不认为别名+是可移植的。请参阅别名上
jw013,2012年

3
支持覆盖sudo使用。关于脚注4,我将别名存储在~/.bash_aliases函数定义中,并将函数定义存储在其中,~/.bash_functions这样我就可以轻松地重新使用source它们(没有任何副作用的危险)。
安东尼纪勤

3

仅添加一些注意事项:

  • sudo只能使用单独的脚本(例如,如果您需要编辑系统文件),例如:
sudo v /etc/rc.conf  #where v runs vim in a new terminal window;
  • 只有别名或函数才能替换具有相同名称的系统命令(假设您将脚本dir添加到PATH的末尾,为了安全起见,在意外或恶意创建名称与系统命令相同的脚本时,出于安全考虑,建议这样做),例如:
alias ls='ls --color=auto'  #enable colored output;
  • 别名和函数需要较少的内存和时间来执行,但是要花费一些时间来加载(因为shell必须在显示提示之前先对它们进行解释)。如果您定期运行新的Shell进程,请考虑到这一点,例如:
# pressing key to open new terminal
# waiting for a few seconds before shell prompt finally appears.

除此之外,您可以使用最简单的形式,即首先考虑别名,然后考虑功能,然后考虑脚本。


5
别名也可以用于sudo。但是首先你需要alias sudo='sudo '
Mikel

的确,在fork + exec的过程中执行脚本会瞬间消耗更多的内存,但是将大量代码加载到当前shell实例的内存中会使它继续消耗更多的内存,通常会存储仅被很少使用。
三人

2

我的经验法则是:

  • 别名-一个命令,没有参数
  • 功能-一个命令一些参数
  • 脚本-几个命令,没有参数

1

在多用户(或多sysamin)环境中,即使所有内容最终只是一个简短的“ exec something ....”包装程序,我也使用脚本编写所有内容。

当然,从技术上讲,它比别名或函数要慢/效率低,但这几乎没有关系-并且只要它在路径中,脚本就总是可以工作。

您的功能可能是从cron,环境经过简化或修改的东西(例如sudo或env)中调用的,或者用户可能只是使用了与您不同的外壳-所有外壳或外壳都可能破坏别名或功能。

如果您对性能敏感,则将其作为一种特殊情况或更佳的方式来处理,请考虑使用更实用的脚本语言重写该触发器。

如果我们在谈论仅在其他脚本中使用的功能,那么您也可以考虑定义一个标准的shell,并编写一个可以是的函数库脚本。将我采购到所有其他脚本。

Ť


0

您最可能想使用别名的情况的示例。

我知道这是一篇老文章,但我想指出一种情况,我几乎不得不使用别名和脚本的组合,而我选择不使用函数。

我有一个~/.bin/名为的脚本setup,它执行以下操作:1

  1. 带我到某个目录。
  2. 定义一些变量。
  3. 打印有关目录状态的消息。2

关键是,如果我只运行setup <project-name>,就不会定义这些变量,也根本不会到达目录。我发现最好的解决方案是将此脚本PATH添加alias setup=". ~/.bin/setup"~/.bashrc或添加到其他内容。

笔记:

  1. 我使用脚本而不是函数来执行此任务不是因为它特别长,而是因为我可以对其进行编辑,并且如果要刷新其用法,也不必在编辑后提供文件源。
  2. 当我创建一个脚本来重新加载所有点文件时,发生了类似的情况。3
  1. 该脚本可在我的dotfiles存储库下找到.bin/
  2. 关于脚本:我给这个脚本一个参数,它是我在高级中定义的项目的名称。之后,脚本知道根据某个csv文件将其带到正确的目录。它定义的变量取自该目录中的makefile。该脚本随后运行ls -lgit status并向我展示发生了什么。
  3. 该脚本也可以在我的dotfiles存储库中找到.bin/

1
嗯,似乎应该只是一个函数,而不是别名脚本组合。(顺便说一句,环境被拼写为“环境”而不是“环境”。)
通配符

感谢您对错字的评论,我将在下一次提交时对其进行修复。至于使用函数而不是脚本-也许我将为该特定任务使用函数并删除这些别名。关键是,如果您不时编辑脚本和别名,有时使用脚本和别名是很容易的。
Doron Behar

0

何时编写脚本

当您可能想从外壳以外的工具运行命令时。

这包括vim(对我而言):编写了过滤器和其他程序作为脚本,我可以做一些事情,例如:%!my-filter通过编辑器中的程序过滤文件。

如果my-filter是函数或别名,那将是不可能的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.