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

ixt*_*lix 399 bash alias shell-script function

我用了将近 10 年的 Linux 才问出这个问题。这都是反复试验和随机深夜上网。

但是人们不应该为此需要10年。如果我刚开始使用 Linux,我想知道:何时使用别名、何时编写脚本以及何时编写函数?

就别名而言,我将别名用于不带参数的非常简单的操作。

alias houston='cd /home/username/.scripts/'
Run Code Online (Sandbox Code Playgroud)

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

alias command="bash bashscriptname"
Run Code Online (Sandbox Code Playgroud)

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

这样做有充分的理由吗?我真的很努力,但我真的想不出任何我想要这样做的情况。因此,如果存在会产生影响的边缘情况,请在下面回答。

因为那是我将一些东西放入我的 PATH 和chmod +x它的地方,这是经过多年 Linux 反复试验后出现的另一件事。

这让我进入下一个话题。例如,我.scripts/通过在我的.bashrc( PATH=$PATH:/home/username/.scripts/) 中添加一行,在主目录中添加了一个隐藏文件夹 ( ) 到我的 PATH ,因此那里的任何可执行文件都会自动自动完成。

如果我需要的话。

不过,我真的不需要那个,不是吗?我只会将它用于不是 shell 的语言,比如 Python。

如果是 shell,我可以在里面写一个函数.bashrc

funcname () {
  somecommand -someARGS "$@"
}
Run Code Online (Sandbox Code Playgroud)

正如我所说,我通过反复试验发现了很多。只有当我的电脑没电了,我才真正看到了功能之美,我不得不在周围人不使用它们的时候使用它们。

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

但我错过了什么吗?

那么,关于何时使用别名、何时编写脚本以及何时编写函数,您会告诉 Linux 初学者什么?

如果这不明显,我假设回答这个问题的人会使用所有三个选项。如果你只使用别名,或者只使用脚本,或者只使用函数——或者如果你只使用别名和脚本或别名和函数或脚本和函数——这个问题并不是真正针对你的。

jw0*_*013 279

其他答案提供了一些基于个人品味的软性一般准则,但忽略了人们在决定脚本、函数或别名时应考虑的许多相关事实

别名和函数 ¹

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

脚本

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

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

应用

鉴于上述情况,我们可以提出一些关于是否将某些内容制作为脚本或函数/别名的一般准则。

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

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

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

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

  • 相反,它是您很少使用的东西吗? 在这种情况下,当你不需要它时,让它占用内存是没有意义的,所以让它成为一个脚本。


¹ 虽然函数和别名有一些重要的区别,但它们被组合在一起,因为函数可以完成别名所能做的一切。别名不能有局部变量,也不能处理参数,而且它们对于超过一行的任何东西都是不方便的。

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

  • 值得注意的是:如果两个(或更多)脚本需要共享一些代码,最好将该代码放入一个函数中,该函数本身位于这两个脚本导入/源的第三个文件中。 (6认同)
  • 要添加到该问题列表中的另一项:_您是否需要即时更改命令中的功能?_ 对脚本的更改将反映在所有会话中,而函数和别名必须在每个会话的基础上重新加载或重新定义. (4认同)
  • @mikeserv - 也许你应该写一篇关于这些事实和变化的博客文章。但它将属于“高级 bash 脚本”类别,而不是此答案的“基本指南”类别。 (2认同)

Kev*_*vin 265

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

# 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"
Run Code Online (Sandbox Code Playgroud)

当您需要做一些比别名更复杂的事情时,应该使用函数,但它本身并没有用。例如,在我问到的关于根据是否在管道中更改默认行为的问题上回答这个问题grep

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

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

如果函数太多或函数太大,请将它们放入隐藏目录中的单独文件中,并在您的~/.bashrc:

if [ -d ~/.bash_functions ]; then
    for file in ~/.bash_functions/*; do
        . "$file"
    done
fi
Run Code Online (Sandbox Code Playgroud)

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

  • 同样重要的是要记住——除非以`.`或`source`作为来源——脚本由单独的bash进程执行并有自己的环境。出于这个原因,任何修改 shell 环境(例如函数、变量等)的东西都不会在您运行脚本的 shell 环境中持续存在。 (23认同)

phu*_*ehe 43

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

  • 首先我尝试创建一个别名,因为它是最简单的。
  • 如果事情太复杂而无法放在一行中,我会尝试将其变成一个函数。
  • 当函数开始增长超过十几行时,我将其放入脚本中。

真的没有什么可以限制你做一些有效的事情。

  • ...或者如果您需要副作用来改变 *current* shell。 (8认同)
  • 我经常跳过函数选项,并立即制作脚本。但我同意这部分是品味问题 (7认同)
  • 如果您在多个脚本中需要一个函数,它就开始有意义了。 (2认同)

noh*_*ide 18

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

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

看看我过去几年所做的 shell 脚本,我或多或少地停止了编写别名(因为随着时间的推移,它们都倾向于变成函数),并且仅在需要从非 bash 环境中使用脚本时才编写脚本。

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

  • 在`alias command="bash bashscriptname"` 中,脚本不一定是可执行的;在`alias c=/path/to/script` 中它必须。 (2认同)

Doc*_*ger 16

什么时候写脚本...

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

什么时候写函数...

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

什么时候写别名...

在库脚本等脚本中,有时需要函数的别名,例如当函数重命名但需要向后兼容时。这可以通过使用旧名称创建一个简单的函数来完成,该函数将其所有参数传递给新函数......

# A bash in-script 'alias'
function oldFunction () { newFunction "$@"; }
Run Code Online (Sandbox Code Playgroud)

可以在Wikipedia/Alias (command) 中找到有关alias命令bash和其他 shell的全面描述


led*_*den 13

以下是关于别名和函数的一些附加点:

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

例如:

alias f='echo Alias'; f             # prints "Alias"
function f { echo 'Function'; }; f  # prints "Alias"
unalias f; f                        # prints "Function"
Run Code Online (Sandbox Code Playgroud)

正如我们所见,别名和函数有单独的命名空间;可以使用declare -A -p BASH_ALIASESand找到更多详细信息declare -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
Run Code Online (Sandbox Code Playgroud)

正如我们所见,与函数不同,别名是不可嵌套的。此外,它们的使用仅限于交互式会话。

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

alias a_complex_thing='f() { do_stuff_in_function; } f'
Run Code Online (Sandbox Code Playgroud)

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


小智 12

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

这对于性能可能很重要——一个函数更快,因为它没有fork()exec(). 在正常情况下,差异是微不足道的,但是如果您正在调试内存不足且页面抖动的系统,则可能会产生很大的不同。

另外,如果你想修改你当前的 shell 环境,你应该使用一个函数。例如,函数可以更改$PATH当前 shell的命令查找,但脚本不能,因为它在$PATH.


use*_*own 8

脚本和别名以及脚本和函数并不相互排斥。您可以并且确实在脚本中存储别名和函数。

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

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

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


Mik*_*kel 6

如果它应该非常快,请将其设为别名或函数。

如果它应该可以在您首选的 shell 之外使用,请将其设为脚本。1

如果它需要参数,请将其设为函数或脚本。

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

如果它需要与 sudo 一起使用,请将其设为别名或脚本。3

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

脚注

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

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

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

4另见:fpath。当然你也可以这样做source ~/.bashrc或类似,但这通常有其他副作用。

  • 支持覆盖 `sudo` 的用法。关于脚注 4,我将别名存储在 `~/.bash_aliases` 中,将函数定义存储在 `~/.bash_functions` 中,以便我可以轻松地重新获取它们(没有任何副作用的危险)。 (3认同)