“alias”和“export”(以及函数)在BASH中有什么区别?

21

很惊讶之前没有被问过,但是...

aliasalias EXPORT='alias'

functionfunction exporter() { echo $EXPORT }

以及

exportexport ALIAS='export'

还有...

alias export=$(function) (j/k)

bash (zsh, 等等) 中的区别是什么。

具体来说,我最感兴趣的是了解它们词汇/实用上的区别。

alias this=that

export that=this

我同时有两种形式... 到处都是 - 我更愿意停止随意地选择一个而不是另一个。

我相信在某个地方有一个关于unix shell的“作用域和用例”的优秀参考资料...但是我想在这里发布问题,以维护正义的规范性。


此外,我担心你的问题表述方式似乎__过于宽泛__。 - devnull
http://links.mrgray.com/doesntexist - Alex Gray
很遗憾,您的示例并没有帮助我们理解您的疑问或者您想要实现什么。 - devnull
作为一般规则,始终优先选择函数而不是别名(除非您知道别名更可取,这种情况非常罕见)。 - gniourf_gniourf
2
您的个人资料显示您熟悉C语言。您可以将别名视为类似于#define,而函数则像是函数本身。 - devnull
你可能想要使用 my_alias() { my_command "$@"; } 来模拟导出别名。 - André Willik Valenti
2个回答

32
您正在询问两个非常不同的事物:别名和函数定义类似命令的行为;export将变量标记为要导出到子进程。让我先介绍类似命令的东西:
别名(alias ll='ls -l')定义了一个命令的缩写。它们旨在供交互式使用(实际上在shell脚本中默认禁用),并且简单但不灵活。例如,您在别名之后指定的任何参数都会被添加到命令的末尾;如果您想要像这样的东西 alias findservice='grep "$1" /etc/services',则无法做到,因为$1 在这里没有任何有用的作用。
函数类似于别名的更灵活、更强大的版本。函数可以接受和处理参数,包含循环、条件语句、here-documents等等...基本上,您可以在函数中执行shell脚本可以做的任何事情。请注意,定义函数的标准方法实际上不使用关键字function,只是在名称后面加括号。例如:findservice() { grep "$1" /etc/services; } 现在让我们谈谈shell变量。在我讲解export之前,我需要谈论未导出变量。基本上,您可以定义一个变量,使其具有某些(文本)值,然后如果您按$variablename 引用该变量,则它将被替换为命令。这与别名或函数有两个不同之处:别名或函数只能出现在命令的第一个单词中(例如,ll filename将使用别名ll,但echo ll不会),必须使用$ 显式调用变量(echo $foo将使用变量foo,但echo foo不会)。更根本地说,别名和函数旨在包含可执行代码(命令、shell语法等),而变量旨在存储非可执行数据。

顺便提一下,你几乎总是应该将变量引用放在双引号内 - 也就是说,使用 echo "$foo" 而不是只有 echo $foo。如果没有双引号,变量的内容会以一种有点奇怪的方式解析,容易导致错误。

还有一些“特殊”shell变量,它们由shell自动设置(例如$HOME),或者影响shell的行为(例如$PATH控制shell在哪里查找可执行命令),或两者都有。

一个被导出的变量在当前shell中和任何子进程(子shell、其他命令等)中都可用。例如,如果我输入LC_ALL=en_US.UTF-8,那么我的当前shell就会使用“en_US.UTF-8”语言环境设置。另一方面,如果我输入export LC_ALL=en_US.UTF-8,那么当前shell和它执行的所有子进程和命令都将使用这个语言环境设置。

请注意,可以将shell变量标记为导出,而不必定义它,并且一旦导出,它就会一直保持导出状态。例如,$PATH(据我所知)总是被导出的,因此PATH=/foo:/bar的效果和export PATH=/foo:/bar相同(尽管后者可能更好,以防万一$PATH某些情况下没有被导出)。

还可以通过将赋值作为命令的前缀来将变量导出到特定命令中,而不需要在当前shell中定义它。例如,LC_ALL=en_US.UTF-8 sort filename会告诉sort命令使用“en_US.UTF-8”语言环境设置,但不应用于当前shell(或任何其他命令)。


1
很棒的回答...接下来是什么?声明的alias和函数的范围是什么?它们是否也适用于子shell / 子进程等?如果不是,那么导出是否是一种可以强制进行这种继承的机制? - Alex Gray
在你的回答中,似乎只有变量可以被标记为导出。函数也可以被导出:fun() { echo hello; }; bash -c 'fun'会引发一个bash: fun: command not found错误,但是在执行export -f fun之后,bash -c 'fun'将会输出hello :) - gniourf_gniourf
3
Bash允许导出函数(使用export -f),但这是Bash的专属功能--我不知道其他任何Shell可以导出函数,或者能够理解由Bash导出的函数。因此,该功能存在但是非标准且有点奇怪。此外,据我所知,没有Shell支持导出别名。 - Gordon Davisson
1
好的答案。在shell脚本中让别名生效的一个小技巧是使用source命令。例如,我有一些别名,我想要在交互式环境下使用它们。我还有一个脚本,它将一个命令应用于一堆本地GIT存储库。我想在重新运行这些别名的脚本时在所有这些地方使用它们。为此,我将我的apply别名定义为source do-command-everywhere.sh。这很好用(只要你不使用exit!),因为脚本只是在当前进程中运行。 - Tevya
1
源代码仅适用于交互式shell。如果您有一个带有函数和别名的库脚本,您可以对其进行源处理,而别名将起作用,但是如果您在另一个脚本中执行相同的源处理,则默认情况下它将不起作用。您需要使用“shopt -s expand_aliases”来使其正常工作。 - Scott Tiger

2

TL;DR:

  1. 根据 POSIX,你提出的问题中实体的 shell 评估顺序为:
    别名 --> 变量 --> 命令替换 --> 特殊内建命令 --> 函数 --> 常规内建命令
  2. 别名不能在子 shell 中持久存在,但变量(在 Bash 中也包括函数)可以通过 export 命令实现。
  3. 常规内建命令可以被重写,方法是编写与常规内建命令同名的函数(因为函数会在常规内建命令之前扩展)。(注意:如果您试图向常规内建命令添加功能,请在函数定义中使用 command 调用内建命令,以免意外创建递归函数。)
  4. 变量可以使用(特殊内建命令)readonly 命令使其只读,但别名无法使用。

USE CASES:

  1. 如果需要在子 shell 中使用变量,请导出变量。
  2. 如果不希望父 shell 的生命周期内更改变量,请将变量设置为 readonly(执行后,无法使用 unset 撤消;必须重新启动父 shell)。
  3. 如果想要重写或添加常规内建命令的功能,请使用函数。

NOTE: 如果您想确保使用的是特殊或常规内建命令而不是别人的函数,请使用 builtin the_builtin或者如果 shell 不支持 builtin 命令,请使用 POSIX 命令 command -p the_builtin其中 -p 开关告诉 command 使用默认情况下随 shell 一起提供的 $PATH(以防用户已覆盖路径)。

NOTE: 变量可以被设置为像别名一样持久存在且无法更改。例如,

#! /bin/sh

my_cmd='ls -al'
export my_cmd
readonly my_cmd

将会表现得像
#! /bin/sh

alias my_cmd='ls -al'

只要my_cmd未被双引号包含(即${my_cmd},而不是"${my_cmd}"),它就不会被视为单个字符串;IFS应该是标准的space-tab-newline,而不是被切换到其他值,这样my_cmd的元素就会被扩展,每个由空格分隔的部分将被评估为单个令牌(否则它将被评估为单个字符串)。每个shell(如bashzshkshyash等)都有所不同,因此请务必查阅其参考手册(它们都以独特的方式实现POSIX,有时根本不实现)。保留html标签。

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