GNU Bash 参考手册 5.3(中文)Bash 5.3 手册

GNU Bash 参考手册
5.3 版 · 中文翻译

The GNU Bash Reference Manual, for Bash Version 5.3(2025 年 5 月 18 日)· 版权所有 © 1988–2025 Free Software Foundation, Inc.

译者说明

  • 本文档由英文原版 PDF 全文翻译为简体中文,保留原手册的章节编号与层级结构;章节划分与标题另与 GNU 官方 5.3 版 HTML 原稿(bash.html)逐节核对。
  • 命令名、选项、变量名、函数名、环境变量名与文件名等一律保留英文原样;程序代码示例保持原貌。
  • 原书正文中的交叉引用(如“参见 3.6 节”)已改为可点击的站内锚点。附录 D 索引同样略去了印刷页码,条目均已链回正文中对应的内建命令、变量或 Readline 命令;索引中少数原书只作互见、正文未单列词条的项(如 meta-flag)指向相关小节。
  • 正文中的网址均可点击跳转。其中 10.8 节引用的 unix.org 大文件支持页面现已下线,该处保留了原书链接并另加译者注。
  • 译文基于 PDF 的自动识别结果,并对识别噪声(字符误识、LaTeX 残片、表格重复、被斜体标记吞掉的 * 等)作了人工校正;如发现个别用词瑕疵,请以英文原版为准。
  • 附录 C 为 GNU 自由文档许可证(GNU Free Documentation License 1.3)条款的中文翻译。
  • 页面带目录筛选、折叠与打印样式,可直接用浏览器打印或另存为 PDF;手机端目录收进抽屉,点屏幕左上角图标展开。

本文简要介绍 Bash shell(5.3 版,2025 年 5 月 18 日)所提供的各项特性。

本手册为《GNU Bash 参考手册》(The GNU Bash Reference Manual)5.3 版,最后更新于 2025 年 5 月 18 日,对应 Bash 5.3 版。

版权所有 © 1988–2025 Free Software Foundation, Inc.

在 GNU 自由文档许可证(GNU Free Documentation License)1.3 版或自由软件基金会发布的任何更新版本的条款下,允许复制、分发和/或修改本文档;本文档不包含不变章节(Invariant Sections)、前封面文字(Front-Cover Texts)和后封面文字(Back-Cover Texts)。许可证副本收录在题为“GNU Free Documentation License”的一节中。

1 简介#

1.1 什么是 Bash?#

Bash 是 GNU 操作系统的 shell,即命令语言解释器。其名称是 'Bourne-Again SHell' 的缩写,这是对 Stephen Bourne 的双关致敬——他是当前 Unix shell sh 的直接前身作者,sh 出现在第七版贝尔实验室研究版 Unix 中。

Bash 在很大程度上与 sh 兼容,并吸收了 Korn shell(ksh)和 C shell(csh)中的实用特性。它旨在成为 IEEE POSIX 规范(IEEE 标准 1003.1)中 IEEE POSIX Shell and Tools 部分的符合性实现。相较 sh,它在交互式使用和编程使用两方面都提供了功能改进。

虽然 GNU 操作系统提供了其他 shell(包括 csh 的一个版本),但 Bash 是默认 shell。与其他 GNU 软件一样,Bash 具有相当好的可移植性。它目前几乎能运行在所有版本的 Unix 以及少数其他操作系统上——Windows 和其他平台存在独立支持的移植版本。

1.2 什么是 shell?#

从根本上说,shell 就是一个执行命令的宏处理器。“宏处理器”这一术语指的是把文本和符号展开为更大表达式的功能。

Unix shell 既是命令解释器,也是一种编程语言。作为命令解释器,shell 为用户提供了访问 GNU 丰富实用程序集合的接口。编程语言特性则使这些实用程序能够组合起来使用。用户可以创建包含命令的文件,这些文件本身就成为命令。这些新命令与 /bin 等目录中的系统命令具有同等地位,从而允许用户或用户组建立自定义环境,自动完成他们的日常任务。

Shell 既可以交互式使用,也可以非交互式使用。在交互模式下,它接受从键盘键入的输入。非交互式执行时,shell 执行从文件或字符串读取的命令。

Shell 允许以同步和异步两种方式执行 GNU 命令。shell 等待同步命令完成后再接受更多输入;异步命令则在 shell 读取并执行其他命令的同时并行继续执行。重定向构造可以对这些命令的输入和输出进行细粒度控制。此外,shell 还允许控制命令环境的各项内容。

Shell 还提供一小组内建命令(builtin),用于实现那些通过独立实用程序无法实现、或实现起来很不方便的功能。例如,cd、break、continue 和 exec 无法在 shell 之外实现,因为它们直接操作 shell 本身。history、getopts、kill 或 pwd 等内建命令本可以在独立的实用程序中实现,但作为内建命令使用更为方便。所有 shell 内建命令将在后续各节中介绍。

尽管执行命令是必不可少的,但 shell 的大部分能力(和复杂度)源于其内嵌的编程语言。与任何高级语言一样,shell 提供变量、流程控制结构、引用和函数。

Shell 还提供一些专门面向交互式使用(而非扩充编程语言)的特性。这些交互式特性包括作业控制、命令行编辑、命令历史和别名。本手册介绍 Bash 如何提供所有这些特性。

2 定义#

以下定义适用于本手册余下的全部内容。

POSIX 一族基于 Unix 的开放系统标准。Bash 主要关注 POSIX 1003.1 标准中的 Shell and Utilities 部分。

blank 空格或制表符。

whitespace 属于当前区域设置(locale)中空格字符类的字符,或者是 isspace() 返回真值的字符。

builtin 由 shell 自身在内部实现的命令,而不是由文件系统中某个可执行程序实现的命令。

control operator 执行控制功能的词元。它是一个换行符或下列字符之一:||、&&、;;、&、|、( 或 )。

exit status 命令返回给其调用者的值。该值被限制为八位,因此最大值为 255。

field 文本的一个单元,是某种 shell 展开的结果。展开之后,执行命令时,得到的字段被用作命令名和参数。

filename 用于标识文件的字符串。

job 一组进程,包含一条管道以及由它派生的任何进程,它们全都处于同一进程组中。

job control 一种机制,用户可以通过它选择性地停止(挂起)和重新开始(恢复)进程的执行。

metacharacter 未被引用时用于分隔单词的字符。元字符是空格、制表符、换行符,或下列字符之一:|、&、;、(、)、< 或 >。

name 由字母、数字和下划线构成,且以字母或下划线开头的单词。名称用作 shell 变量名和函数名。也称为标识符(identifier)。

operator 控制运算符或重定向运算符。重定向运算符的列表参见 3.6 节“重定向”。运算符至少包含一个未引用的元字符。

process group 一组相关进程的集合,其中每个进程具有相同的进程组 ID。

process group ID 在进程组的生存期内代表该进程组的唯一标识符。

reserved word 对 shell 具有特殊含义的单词。大多数保留字用于引出 shell 流程控制结构,例如 for 和 while。

return status exit status 的同义词。

signal 一种机制,内核可通过它把系统中发生的事件通知给进程。

special builtin 被 POSIX 标准归类为特殊(special)的 shell 内建命令。

token 被 shell 视为单个单元的字符序列。它要么是一个单词(word),要么是一个运算符(operator)。

word 被 shell 当作一个单元处理的字符序列。单词不能包含未引用的元字符。

3 shell 基本特性#

Bash 是 'Bourne-Again SHell' 的缩写。Bourne shell 是最初由 Stephen Bourne 编写的传统 Unix shell。Bourne shell 的所有内建命令在 Bash 中都可用,并且其求值与引用规则取自 POSIX 规范对“标准”Unix shell 的规定。

本章简要概述 shell 的“构建块”:命令、控制结构、shell 函数、shell 参数、shell 展开、重定向(一种把输入输出定向到具名文件或从具名文件读取的方式),以及 shell 如何执行命令。

3.1 shell 语法#

当 shell 读取输入时,它会依次经过一系列操作。如果输入表示注释的开始,shell 会忽略注释符号('#')以及该行剩余的内容。

否则,大致说来,shell 读取输入并把输入划分为单词和运算符,同时运用引用规则来决定为各个单词和字符赋予何种含义。

随后 shell 把这些词元解析为命令和其他构造,去除某些单词或字符的特殊含义,对其他部分进行展开,按需重定向输入和输出,执行指定的命令,等待命令的退出状态,并使该退出状态可被进一步检查或处理。

3.1.1 shell 运行过程#

下面简要描述 shell 在读取并执行命令时的运行过程。基本上,shell 会做以下事情:

  1. 从文件(参见 3.8 节“shell 脚本”)、作为 -c 调用选项参数提供的字符串(参见 6.1 节“调用 Bash”)或用户终端读取输入。

  2. 按照 3.1.2 节“引用”所述的引用规则,把输入拆分为单词和运算符。这些词元由元字符分隔。此步骤执行别名展开(参见 6.6 节“别名”)。

  3. 把词元解析为简单命令和复合命令(参见 3.2 节“shell 命令”)。

  4. 执行各种 shell 展开(参见 3.5 节“shell 展开”),把展开后的词元拆分为文件名(参见 3.5.8 节“文件名展开”)以及命令和参数构成的列表。

  5. 执行任何必要的重定向(参见 3.6 节“重定向”),并从参数列表中移除重定向运算符及其操作数。

  6. 执行命令(参见 3.7 节“执行命令”)。

  7. 可选地等待命令完成并收集其退出状态(参见 3.7.5 节“退出状态”)。

3.1.2 引用#

引用用于去除某些字符或单词对 shell 的特殊含义。引用可用于禁用对特殊字符的特殊处理,防止保留字被识别为保留字,以及防止参数展开。

每个 shell 元字符(参见第 2 章“定义”)对 shell 都有特殊含义,若要表示其自身,就必须对它进行引用。

当使用命令历史展开功能时(参见 9.3 节“历史交互”),历史展开字符(通常是 '!')必须被引用,以防止历史展开。有关历史展开的更多细节,参见 9.1 节“Bash 历史功能”。

共有四种引用机制:转义字符、单引号、双引号和美元单引号(dollar-single quotes)。

3.1.2.1 转义字符#

未被引用的反斜杠 \ 是 Bash 的转义字符。它保留其后紧随字符的字面值,去除该字符所具有的任何特殊含义,换行符除外。如果出现反斜杠加换行符的组合,且反斜杠本身未被引用,则该换行符被视为行继续(也就是说,它从输入流中被移除并实际上被忽略)。

3.1.2.2 单引号#

把字符括在单引号('')中会保留引号内每个字符的字面值。单引号不能出现在单引号之间,即使前面带有反斜杠也不行。

3.1.2.3 双引号#

把字符括在双引号('"')中会保留引号内所有字符的字面值,但 $、`、\ 以及(启用历史展开时的)! 除外。当 shell 处于 POSIX 模式(参见 6.11 节“Bash POSIX 模式”)时,即使启用了历史展开,! 在双引号内也没有特殊含义。字符 $ 和 ` 在双引号内保留其特殊含义(参见 3.5 节“shell 展开”)。反斜杠仅在其后跟下列字符之一时才保留特殊含义:$、`、"、\ 或换行符。在双引号内,后跟这些字符之一的反斜杠会被移除。位于没有特殊含义的字符之前的反斜杠保持原样不变。

双引号可以通过在其前面加反斜杠的方式在双引号内被引用。如果启用了历史展开,那么除非出现在双引号内的 '!' 用反斜杠转义,否则就会执行历史展开。位于 '!' 之前的反斜杠不会被移除。

特殊参数 * 和 @ 在双引号内具有特殊含义(参见 3.5.3 节“shell 参数展开”)。

3.1.2.4 ANSI-C 引用#

形如 $'string' 的字符序列被视为一种特殊的单引号。该序列展开为 string,其中 string 内以反斜杠转义的字符按 ANSI C 标准的规定进行替换。如果存在反斜杠转义序列,则按如下方式解码:

转义序列 含义
\a 警报(响铃)
\b 退格
\e、\E 转义字符(不属于 ANSI C)
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\\ 反斜杠
\' 单引号
\" 双引号
\? 问号
\nnn 值为八进制值 nnn(一到三位八进制数字)的八位字符
\xHH 值为十六进制值 HH(一到两位十六进制数字)的八位字符
\uHHHH 值为十六进制值 HHHH(一到四位十六进制数字)的 Unicode(ISO/IEC 10646)字符
\UHHHHHHHH 值为十六进制值 HHHHHHHH(一到八位十六进制数字)的 Unicode(ISO/IEC 10646)字符
\cx 一个 control-x 字符

展开后的结果如同美元符号不存在一样,按单引号引用处理。

3.1.2.5 特定于区域设置的翻译#

在双引号字符串前加上美元符号('$'),例如 $"hello, world",会使该字符串按当前区域设置(locale)进行翻译。gettext 基础设施执行查找和翻译,使用 LC_MESSAGES、TEXTDOMAINDIR 和 TEXTDOMAIN 这几个 shell 变量,如下所述。此处未涵盖的更多细节请参见 gettext 文档。如果当前区域设置为 C 或 POSIX、如果没有可用的翻译,或者该字符串未被翻译,则美元符号被忽略,该字符串按上面所述作为双引号字符串处理。由于这是双引号引用的一种形式,无论字符串是否被翻译和替换,默认都保持双引号引用。如果通过 shopt 内建命令启用了 noexpand_translation 选项(参见 4.3.2 节“Shopt 内建命令”),被翻译的字符串将使用单引号而不是双引号。

本节余下部分简要概述如何使用 gettext 为名为 scriptname 的 shell 脚本中的字符串创建翻译。gettext 文档中有更多细节。

一旦用 $"... " 标记了脚本中希望翻译的字符串,就可以用下面的命令创建一个 gettext“模板”文件:

bash --dump-po-strings scriptname > domain.pot

domain 是你的消息域(message domain)。它只是一个任意字符串,用于标识 gettext 所需的文件,例如某个软件包名或脚本名。在你安装翻译的系统中,它必须在所有消息域中保持唯一,这样 gettext 才能知道哪些翻译对应于你的脚本。你将使用这个模板文件为每种目标语言创建翻译。模板文件按惯例使用后缀 '.pot'。

你把这个模板文件复制为每种希望支持的目标语言各一个单独的文件(称为“PO”文件,使用后缀 '.po')。PO 文件有多种命名约定,但当你要把模板文件翻译成某种特定语言时,首先把模板文件复制成一个以目标语言命名、并加上 '.po' 后缀的文件。例如,字符串的西班牙语翻译会放在名为 'es.po' 的文件中,而要开始使用名为 "example" 的消息域,可以运行

cp example.pot es.po

最终,PO 文件通常命名为 domain.po,并安装到为某种特定语言存放多个翻译文件的目录中。

无论选择哪种命名约定,你都需要把 PO 文件中的字符串翻译成相应的语言。这必须手工完成。

当翻译和 PO 文件都完成之后,你将使用 gettext 工具生成所谓的“MO”文件,即 PO 文件的编译版本,gettext 工具使用它来高效地查找翻译。MO 文件也称为“消息目录”(message catalog)文件。使用 msgfmt 程序完成这一工作。例如,如果你有一个包含西班牙语翻译的文件,可以运行

msgfmt -o es.mo es.po

来生成相应的 MO 文件。

有了 MO 文件之后,你需要决定把它们安装到哪里,并使用 TEXTDOMAINDIR shell 变量告诉 gettext 工具它们的位置。安装时请确保为 MO 文件命名所用的消息域与为 PO 文件所用的一致。

你的用户将使用 LANG 或 LC_MESSAGES shell 变量来选择所需的语言。

你把 TEXTDOMAIN 变量设置为脚本的消息域。如上所述,你用消息域来命名翻译文件。

你(或者可能是你的用户)把 TEXTDOMAINDIR 变量设置为存放消息目录文件的目录名。如果把消息文件安装到系统的标准消息目录中,就不必操心这个变量。

存放消息目录文件的目录因系统而异。有些系统使用由 LC_MESSAGES shell 变量选定的消息目录。另一些系统则根据 TEXTDOMAIN shell 变量的值构造消息目录名,可能还会加上 '.mo' 后缀。如果使用 TEXTDOMAIN 变量,你可能需要如上所述把 TEXTDOMAINDIR 变量设置为消息目录文件所在的位置。以这种方式同时使用这两个变量很常见:$TEXTDOMAINDIR/$LC_MESSAGES/LC_MESSAGES/$TEXTDOMAIN.mo。

如果采用了后一种约定,并且想把包含西班牙语(es)和世界语(eo)翻译的消息目录文件存放为你用于自定义翻译文件的本地目录,可以运行

TEXTDOMAIN=example
TEXTDOMAINDIR=/usr/local/share/locale

cp es.mo ${TEXTDOMAINDIR}/es/LC_MESSAGES/${TEXTDOMAIN}.mo
cp eo.mo ${TEXTDOMAINDIR}/eo/LC_MESSAGES/${TEXTDOMAIN}.mo

当这一切都完成,且包含已编译翻译的消息目录文件安装到正确位置后,你的用户只要在运行脚本之前设置 LANG 或 LC_MESSAGES 环境变量,就能看到任何受支持语言的已翻译字符串。

3.1.3 注释#

在非交互式 shell 中,或者启用了 shopt 内建命令的 interactive_comments 选项的交互式 shell 中(参见 4.3.2 节“Shopt 内建命令”),以 '#' 开头的单词引入注释。单词始于行首、未引用的空白之后,或运算符之后。注释会使该单词以及该行剩余的所有字符被忽略。未启用 interactive_comments 选项的交互式 shell 不允许注释。在交互式 shell 中,interactive_comments 选项默认启用。关于什么使 shell 成为交互式 shell 的说明,参见 6.3 节“交互式 shell”。

3.2 shell 命令#

一个简单的 shell 命令,例如 echo a b c,由命令本身后跟参数构成,参数之间以空格分隔。

更复杂的 shell 命令由简单命令以多种方式组合而成:以管道方式(其中一条命令的输出成为第二条命令的输入)、在循环或条件结构中,或者以其他某种形式分组。

3.2.1 保留字#

保留字是对 shell 具有特殊含义的单词。它们用于开始和结束 shell 的复合命令。

下列单词在未被引用且作为命令的第一个单词时被识别为保留字(例外情况见下文):

if then elif else fi time
for in until while do done
case esac coproc select function
{ } [[ ]]

如果 in 是 case 或 select 命令的第三个单词,它被识别为保留字。如果 in 和 do 是 for 命令中的第三个单词,它们被识别为保留字。

3.2.2 简单命令#

简单命令是最常执行的一类命令。它只是一串由空白分隔的单词,以 shell 的某个控制运算符结束(参见第 2 章“定义”)。通常第一个单词指定要执行的命令,其余单词则是该命令的参数。

简单命令的返回状态(参见 3.7.5 节“退出状态”)是 POSIX 1003.1 waitpid 函数所提供的退出状态;如果命令是被信号 n 终止的,则为 128+n。

3.2.3 管道#

管道是由控制运算符 | 或 |& 分隔的一个或多个命令组成的序列。

管道的格式为

[time [-p]] [!] command1 [ | 或 |& command2 ] ...

管道中每个命令的输出都通过一条管道连接到下一个命令的输入。也就是说,每个命令读取前一个命令的输出。这一连接在 command1 指定的任何重定向之前执行。

如果 |& 是管道运算符,那么 command1 的标准错误连同其标准输出一起,通过管道连接到 command2 的标准输入;它是 2>&1 | 的简写形式。把标准错误隐式重定向到标准输出这一动作,与该简写形式一致,在 command1 指定的任何重定向之后执行。

如果保留字 time 位于管道之前,管道结束后 Bash 会打印该管道的时间统计信息。这些统计信息目前包括已用(墙钟)时间以及命令执行所消耗的用户时间和系统时间。-p 选项把输出格式改为 POSIX 规定的格式。当 shell 处于 POSIX 模式(参见 6.11 节“Bash POSIX 模式”)时,如果下一个词元以 '-' 开头,它不把 time 识别为保留字。TIMEFORMAT 变量的值是一个格式字符串,用于规定计时信息应如何显示。可用格式的说明参见 5.2 节“Bash 变量”。把 time 作为保留字提供,使得对 shell 内建命令、shell 函数和管道计时成为可能。外部的 time 命令很难对这些对象计时。

当 shell 处于 POSIX 模式(参见 6.11 节“Bash POSIX 模式”)时,你可以把 time 本身作为简单命令使用。在这种情况下,shell 显示 shell 及其子进程所消耗的用户时间和系统时间总计。TIMEFORMAT 变量规定计时信息的格式。

如果管道不是异步执行的(参见 3.2.4 节“命令列表”),shell 会等待管道中的所有命令完成。

多命令管道中(即创建了管道的位置)的每个命令都在各自的子 shell 中执行,子 shell 是一个单独的进程(参见 3.7.3 节“命令执行环境”)。如果通过 shopt 内建命令启用了 lastpipe 选项(参见 4.3.2 节“Shopt 内建命令”),且作业控制未激活,则管道的最后一个元素可以由 shell 进程运行。

管道的退出状态是管道中最后一个命令的退出状态,除非启用了 pipefail 选项(参见 4.3.1 节“Set 内建命令”)。如果启用了 pipefail,管道的返回状态是以非零状态退出的最后一个(最右边)命令的值;如果所有命令都成功退出,则为零。如果保留字 '!' 位于管道之前,退出状态是上面所述退出状态的逻辑取反。如果管道不是异步执行的(参见 3.2.4 节“命令列表”),shell 会等待管道中的所有命令终止后才返回值。异步管道的返回状态是 0。

3.2.4 命令列表#

列表是由运算符 ;、&、&& 或 || 之一分隔的一个或多个管道组成的序列,并且可选地以 ;、& 或换行符之一结束。

在这些列表运算符中,&& 和 || 具有相同的优先级,其次是同享相同优先级的 ; 和 &。

列表中可能出现一个或多个换行符序列来分隔命令,其作用等同于分号。

如果命令以控制运算符 & 结束,shell 会在子 shell 中异步执行该命令。这称为在后台执行命令,这类命令被称为异步命令。shell 不等待命令完成,返回状态为 0(真)。当作业控制未激活时(参见第 7 章“作业控制”),在没有任何显式重定向的情况下,异步命令的标准输入从 /dev/null 重定向。

以 ; 分隔的命令顺序执行;shell 依次等待每个命令终止。返回状态是最后执行的命令的退出状态。

AND 列表和 OR 列表分别是由控制运算符 && 和 || 分隔的一个或多个管道组成的序列。AND 列表和 OR 列表按左结合性执行。

AND 列表的形式为

command1 && command2

当且仅当 command1 返回零(成功)退出状态时,才执行 command2。

OR 列表的形式为

command1 || command2

当且仅当 command1 返回非零退出状态时,才执行 command2。

AND 列表和 OR 列表的返回状态是列表中最后执行的命令的退出状态。

3.2.5 复合命令#

复合命令是 shell 编程语言的构造。每个构造以保留字或控制运算符开始,并以相应的保留字或运算符结束。与某个复合命令相关联的任何重定向(参见 3.6 节“重定向”)都适用于该复合命令内的所有命令,除非被显式覆盖。

在大多数情况下,复合命令描述中的命令列表与命令的其余部分之间可以用一个或多个换行符分隔,并且列表之后可以跟一个换行符来代替分号。

Bash 提供循环构造、条件命令,以及把命令分组并作为单元执行的机制。

3.2.5.1 循环构造#

Bash 支持下列循环构造。

请注意,在命令语法的描述中,凡出现 ; 的地方,都可以用一个或多个换行符替代。

until until 命令的语法为:

until test-commands; do consequent-commands; done

只要 test-commands 的退出状态非零,就执行 consequent-commands。返回状态是 consequent-commands 中最后执行的命令的退出状态;如果一条都没执行,则为零。

while while 命令的语法为:

while test-commands; do consequent-commands; done

只要 test-commands 的退出状态为零,就执行 consequent-commands。返回状态是 consequent-commands 中最后执行的命令的退出状态;如果一条都没执行,则为零。

for for 命令的语法为:

for name [ [in words ...] ; ] do commands; done

展开 words(参见 3.5 节“shell 展开”),然后对结果列表中的每个单词执行一次 commands,并把 name 绑定到当前单词。如果没有 'in words',for 命令就对每个已设置的位置参数执行一次 commands,就好像指定了 'in "$@"' 一样(参见 3.4.2 节“特殊参数”)。

返回状态是最后执行的命令的退出状态。如果 words 的展开结果中没有任何项,则不执行任何命令,返回状态为零。

for 命令还有一种与 C 语言类似的形式:

for ((expr1 ; expr2 ; expr3)); do commands; done

首先,按下面所述的规则求算术表达式 expr1 的值(参见 6.5 节“shell 算术”)。然后反复求算术表达式 expr2 的值,直到它求值为零。每当 expr2 求值为非零,就执行 commands 并求算术表达式 expr3 的值。如果省略了任何表达式,其行为就如同它求值为 1。返回值是 commands 中最后执行的命令的退出状态;如果任何表达式无效,则为非零。

使用 break 和 continue 内建命令(参见 4.1 节“Bourne shell 内建命令”)来控制循环的执行。

3.2.5.2 条件构造#

if if 命令的语法为:

if test-commands; then
  consequent-commands;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi

执行 test-commands 列表;如果其返回状态为零,则执行 consequent-commands 列表。如果 test-commands 返回非零状态,则依次执行各个 elif 列表;如果某个 elif 的退出状态为零,就执行相应的 more-consequents 并结束该命令。如果存在 'else alternate-consequents',且最后一个 if 或 elif 子句中的最后一个命令具有非零退出状态,则执行 alternate-consequents。返回状态是最后执行的命令的退出状态;如果没有任何条件测试为真,则为零。

case case 命令的语法为:

case word in
    [ [(] pattern [| pattern]...) command-list ;;]...
esac

case 从第一个模式向最后一个模式依次尝试,选择性地执行与第一个匹配 word 的模式相对应的 command-list。匹配按下文 3.5.8.1 节“模式匹配”所述的规则进行。如果启用了 nocasematch shell 选项(参见 4.3.2 节“Shopt 内建命令”中对 shopt 的说明),则匹配时不考虑字母字符的大小写。| 用于在模式列表中分隔多个模式,而 ) 运算符结束模式列表。一个模式列表及其关联的 command-list 称为一个子句(clause)。

每个子句必须以 ;;、;& 或 ;;& 结束。在 shell 尝试匹配模式之前,word 要经过波浪号展开、参数展开、命令替换、进程替换、算术展开和引用移除(参见 3.5.3 节“shell 参数展开”)。每个模式要经过波浪号展开、参数展开、命令替换、算术展开、进程替换和引用移除。

case 子句的数量可以任意多,每个子句都以 ;;、;& 或 ;;& 结束。第一个匹配的模式决定执行哪个 command-list。一种常见的惯用法是用 * 作为最后一个模式来定义默认情况,因为该模式总会匹配。

下面是一个在脚本中使用 case 的例子,可用来描述动物的某个有趣特征:

echo -n "Enter the name of an animal: "
read ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
  horse | dog | cat) echo -n "four";;
  man | kangaroo) echo -n "two";;
  *) echo -n "an unknown number of";;
esac
echo " legs."

如果使用 ;; 运算符,case 命令在第一个模式匹配后就结束。用 ;& 代替 ;; 会使执行继续到下一个子句关联的 command-list(如果有的话)。用 ;;& 代替 ;; 会使 shell 测试下一个子句中的模式(如果有的话),如果匹配成功就执行任何关联的 command-list,并继续 case 语句的执行,就好像该模式列表没有匹配一样。

如果没有模式匹配,返回状态为零。否则,返回状态是最后执行的 command-list 的退出状态。

select select 构造允许轻松生成菜单。它的语法与 for 命令几乎相同:

select name [in words ...]; do commands; done

首先,展开 in 之后的单词列表,生成一个项列表,并把展开后的单词集合打印到标准错误流,每个单词前面带一个编号。如果省略了 'in words',则打印位置参数,就好像指定了 'in "$@"' 一样。然后 select 显示 PS3 提示符并从标准输入读取一行。如果该行由与某个已显示单词对应的编号组成,则 select 把 name 的值设置为该单词。如果该行为空,select 再次显示单词和提示符。如果读到 EOF,select 结束并返回 1。读取到的任何其他值会导致 name 被设置为空。读取到的该行保存在变量 REPLY 中。

每次选择之后都会执行 commands,直到执行了 break 命令,此时 select 命令结束。

下面是一个例子,允许用户从当前目录中选择一个文件名,并显示所选文件的名称和索引。

select fname in *;
do
    echo you picked $fname \($REPLY\)
    break;
done

((...)) 语法形式为 (( expression ))。算术表达式按下面所述的规则求值(参见 6.5 节“shell 算术”)。该表达式所经历的展开与它处于双引号内时相同,但 expression 中未转义的双引号字符不被特殊处理,而是被移除。由于这可能导致空字符串,该命令把空字符串视为求值为 0 的表达式。如果表达式的值非零,返回状态为 0;否则返回状态为 1。

[[...]] 语法形式为 [[ expression ]]。求条件表达式并返回零(真)或非零(假)状态。表达式由下文 6.4 节“Bash 条件表达式”中所述的基本项(primaries)组成。位于 [[ 和 ]] 之间的单词不进行单词拆分和文件名展开。shell 对这些单词执行波浪号展开、参数和变量展开、算术展开、命令替换、进程替换以及引用移除。诸如 '-f' 这样的条件运算符必须未被引用才能被识别为基本项。

与 [[ 一起使用时,< 和 > 运算符使用当前区域设置(locale)按字典序排序。

使用 == 和 != 运算符时,运算符右侧的字符串被视为一个模式,并按下文 3.5.8.1 节“模式匹配”所述的规则进行匹配,就好像启用了 extglob shell 选项一样。= 运算符与 == 完全相同。如果启用了 nocasematch shell 选项(参见 4.3.2 节“Shopt 内建命令”中对 shopt 的说明),则匹配时不考虑字母字符的大小写。如果字符串匹配(==)或不匹配(!=)该模式,返回值为 0,否则为 1。

如果使用 shell 的任何引用机制对模式的任何部分进行引用,被引用的部分按字面匹配。这意味着被引用部分中的每个字符都匹配其自身,而不具有任何特殊的模式匹配含义。

还有一个额外的二元运算符 =~,其优先级与 == 和 != 相同。使用 =~ 时,运算符右侧的字符串被视为 POSIX 扩展正则表达式模式并据此匹配(使用通常在 regex(3) 中描述的 POSIX regcomp 和 regexec 接口)。如果字符串匹配该模式,返回值为 0,不匹配则为 1。如果正则表达式在语法上有错,条件表达式返回 2。如果启用了 nocasematch shell 选项(参见 4.3.2 节“Shopt 内建命令”中对 shopt 的说明),则匹配时不考虑字母字符的大小写。

你可以对模式的任何部分进行引用,以强制被引用的部分按字面匹配,而不是作为正则表达式匹配(见上文)。如果模式存储在某个 shell 变量中,对变量展开进行引用会强制整个模式按字面匹配。

如果模式匹配字符串的任何部分,匹配就成功。如果想让模式匹配整个字符串,请使用 ^ 和 $ 正则表达式运算符锚定该模式。

例如,如果值中任何位置存在这样一个字符序列——由任意个(包括零个)空格字符类中的字符,紧接零个或一个 'a',再跟一个 'b' 组成——则下面这个表达式将匹配一行(存储在 shell 变量 line 中):

[[ $line =~ [[:space:]]*(a)?b ]]

这意味着 line 的取值如 'aab'、' aaaaaab'、'xaby' 和 ' ab' 都会匹配,其值中任何位置包含 'b' 的行也会匹配。

如果要匹配对正则表达式语法而言特殊的字符('^$|.*+?'),必须对它进行引用以去除其特殊含义。这意味着在模式 'xxx.txt' 中,'.' 匹配字符串中的任意字符(其通常的正则表达式含义),而在模式 '"xxx.txt"' 中,它只能匹配字面量 '.'。

同样,如果要在模式中包含一个对正则表达式语法有特殊含义的字符,必须确保它未被引用。例如,如果要在字符串的开头或结尾锚定模式,就不能使用任何形式的 shell 引用去引用 ^ 或 $ 字符。

如果要在行的开头匹配 'initial string',下面这样可行:

[[ $line =~ ^"initial string" ]]

但这样不行:

[[ $line =~ "^initial string" ]]

因为在第二个示例中,'^' 被引用了,不再具有其通常的特殊含义。

有时不使用引号就很难正确指定正则表达式,或者在注意 shell 引用和 shell 引用移除的同时,还要跟踪正则表达式所使用的引用也很难。把正则表达式存储在 shell 变量中,通常是避免 shell 特殊字符引用问题的有用办法。例如,下面这段与上面使用的模式等价:

pattern='[[:space:]]*(a)?b'
[[ $line =~ $pattern ]]

shell 程序员应当特别小心反斜杠,因为 shell 和正则表达式都用反斜杠来去除其后字符的特殊含义。这意味着在 shell 的单词展开完成之后(参见 3.5 节“shell 展开”),模式中原本未被引用的部分里残留的任何反斜杠都可能去除模式字符的特殊含义。如果模式的任何部分被引用,shell 会尽力确保正则表达式把这些残留的反斜杠当作字面量处理——前提是它们出现在被引用的部分中。

下面两组命令并不等价:

pattern='\.'

[[ . =~ $pattern ]]
[[ . =~ \. ]]

[[ . =~ "$pattern" ]]
[[ . =~ '\.' ]]

前两个匹配会成功,后两个不会,因为在后两个中反斜杠将成为待匹配模式的一部分。在前两个示例中,传给正则表达式解析器的模式是 '.'。反斜杠去除了 '.' 的特殊含义,因此字面量 '.' 匹配成功。在后两个示例中,传给正则表达式解析器的模式中反斜杠被引用了(例如 '\.'),因此无法匹配该字符串,因为该字符串不包含反斜杠。如果前两个示例中的字符串不是 '.',而比如是 'a',那么模式也不会匹配,因为模式中被引用的 '.' 失去了匹配任意单个字符的特殊含义。

正则表达式中的方括号表达式也可能成为错误的来源,因为通常在正则表达式中特殊的字符在方括号之间会失去其特殊含义。不过,可以用方括号表达式匹配特殊模式字符而无需引用它们,因此它们有时在这类用途上很有用。

虽然这样写法看起来有点奇怪,但下面的模式将匹配字符串中的 '.':

[[ . =~ [.] ]]

shell 在把模式传给正则表达式函数之前会执行任何单词展开,所以你可以认为 shell 的引用优先。如上所述,正则表达式解析器会按它自己的规则解释 shell 展开后模式中残留的任何未被引用的反斜杠。这样做的意图是尽量避免让 shell 程序员引用两次,因此 shell 引用在必要时应该足以引用特殊模式字符。

数组变量 BASH_REMATCH 记录字符串的哪些部分匹配了模式。BASH_REMATCH 中索引为 0 的元素包含匹配整个正则表达式的字符串部分。被正则表达式中带括号的子表达式所匹配的子串保存在 BASH_REMATCH 其余的索引中。索引为 n 的 BASH_REMATCH 元素是匹配第 n 个带括号子表达式的字符串部分。

Bash 在全局作用域中设置 BASH_REMATCH;把它声明为局部变量会导致意外的结果。

表达式可以用下列运算符组合,按优先级由高到低排列:

( expression ) 返回 expression 的值。可用于覆盖运算符的正常优先级。

! expression 如果 expression 为假,则为真。

expression1 && expression2 如果 expression1 和 expression2 都为真,则为真。

expression1 || expression2 如果 expression1 或 expression2 为真,则为真。

如果 expression1 的值足以确定整个条件表达式的返回值,则 && 和 || 运算符不求解 expression2。

3.2.5.3 命令分组#

Bash 提供两种方式把命令列表分组为作为单元执行。当命令被分组时,重定向可以应用于整个命令列表。例如,列表中所有命令的输出可以被重定向到同一条流。

( ) 语法形式为 ( list )。把命令列表放在圆括号之间会强制 shell 创建一个子 shell(参见 3.7.3 节“命令执行环境”),list 中的每个命令都在该子 shell 环境中执行。由于列表在子 shell 中执行,变量赋值在子 shell 结束后不会继续有效。

{ } 语法形式为 { list; }。把命令列表放在花括号之间会使该列表在当前 shell 环境中执行。不会创建子 shell。list 之后的分号(或换行符)是必需的。

除了创建子 shell 之外,出于历史原因这两种构造之间还有一个细微差别。花括号是保留字,所以它们必须通过空白或其他 shell 元字符与列表分隔开。圆括号是运算符,即使它们与列表之间没有空白分隔,shell 也会把它们识别为独立的词元。

这两种构造的退出状态都是 list 的退出状态。

3.2.6 协程#

协程(coprocess)是前面带有 coproc 保留字的 shell 命令。协程像命令以 & 控制运算符结束那样在子 shell 中异步执行,并在执行中的 shell 与协程之间建立一条双向管道。

协程的语法为:

coproc [NAME] command [redirections]

这会创建一个名为 NAME 的协程。command 既可以是简单命令(参见 3.2.2 节“简单命令”),也可以是复合命令(参见 3.2.5 节“复合命令”)。NAME 是一个 shell 变量名。如果没有提供 NAME,默认名称是 COPROC。

协程推荐使用的形式是

coproc NAME { command; }

推荐这种形式,因为使用简单命令会导致协程总是被命名为 COPROC,而这种形式用起来更简单,也比其他复合命令更完整。

协程还有其他形式:

coproc NAME compound-command
coproc compound-command
coproc simple-command

如果 command 是复合命令,NAME 是可选的。coproc 之后的单词决定该单词是否被解释为变量名:如果它不是引出复合命令的保留字,就被解释为 NAME。如果 command 是简单命令,则不允许使用 NAME;这是为了避免 NAME 与简单命令的第一个单词之间产生混淆。

执行协程时,shell 在执行该协程的 shell 上下文中创建一个名为 NAME 的数组变量(参见 6.7 节“数组”)。command 的标准输出通过一条管道连接到执行中的 shell 中的一个文件描述符,该文件描述符被赋给 NAME[0]。command 的标准输入通过一条管道连接到执行中的 shell 中的一个文件描述符,该文件描述符被赋给 NAME[1]。这条管道在 command 指定的任何重定向之前建立(参见 3.6 节“重定向”)。这些文件描述符可以通过标准的单词展开被用作 shell 命令和重定向的参数。除了为执行 command 和进程替换而创建的文件描述符之外,这些文件描述符在子 shell 中不可用。

为执行协程而生成的 shell 的进程 ID 可作为变量 NAME_PID 的值获得。可以使用 wait 内建命令等待协程终止。

由于协程是作为异步命令创建的,coproc 命令总是返回成功。协程的返回状态是 command 的退出状态。

3.2.7 GNU Parallel#

有一些并行运行命令的方式并未内建于 Bash 中。GNU Parallel 正是为此而设计的工具。

GNU Parallel 顾名思义可用于并行地构建并运行命令。你可以用不同的参数运行同一条命令,无论这些参数是文件名、用户名、主机名,还是从文件读取的行。GNU Parallel 为许多最常用的操作(输入行、输入行的各个部分、指定输入源的不同方式等等)提供了简写引用。Parallel 可以替代 xargs,也可以把来自其输入源的命令馈送给若干个不同的 Bash 实例。

完整的说明请参阅 GNU Parallel 文档,其网址为 https://www.gnu.org/software/parallel/parallel_tutorial.html。

3.3 shell 函数#

shell 函数是一种把命令组织在一起、以便稍后用单个名字整体执行的方式。它们的执行方式与“普通”简单命令完全一样。当把一个 shell 函数的名字用作简单命令名时,shell 会执行与该函数名关联的那组命令。shell 函数在当前 shell 上下文中执行;不会为解释它们而创建新进程。

函数用如下语法声明:

fname () compound-command [ redirections ]

或

function fname [()] compound-command [ redirections ]

这段语法定义了名为 fname 的 shell 函数。保留字 function 是可选的。如果给出了保留字 function,那么圆括号是可选的。函数的 body(函数体)是复合命令 compound-command(参见 3.2.5 节“复合命令”)。该命令通常是用 { 和 } 括起来的列表,但也可以是前面列出的任何复合命令。如果使用了保留字 function 但没有给出圆括号,建议使用花括号。当 shell 处于 POSIX 模式(参见 6.11 节“Bash POSIX 模式”)时,fname 必须是合法的 shell 名称,且不能与某个特殊内建命令同名(参见 4.4 节“特殊内建命令”)。不在 POSIX 模式时,函数名可以是任何不包含 $ 的未引用 shell 单词。

与 shell 函数关联的任何重定向(参见 3.6 节“重定向”)都在函数执行时进行。函数定义用 unset 内建命令的 -f 选项删除(参见 4.1 节“Bourne shell 内建命令”)。

函数定义的退出状态为零,除非发生语法错误,或者已经存在同名的只读函数。函数被执行时,其退出状态是函数体中最后执行的那条命令的退出状态。

注意,出于历史原因,在最常见的用法中,包围函数体的花括号必须与其函数体之间用 blanks 或 newlines(空格或换行符)分隔。这是因为花括号属于保留字,只有当它们与命令列表之间用 whitespace(空白)或另一个 shell 元字符分隔时,才会被识别为保留字。使用花括号时,列表必须以分号、一个‘&’或一个 newline(换行符)结束。

当 fname 被指定为某个简单命令的名字时,就会执行 compound-command。函数在发起调用的那个 shell 的上下文中执行;不会为解释它们而创建新进程(可以与执行 shell 脚本的情形相对照)。

函数执行时,传给函数的参数在函数执行期间成为位置参数(参见 3.4.1 节“位置参数”)。展开为位置参数个数的特殊参数‘#’会被更新,以反映新的位置参数集合。特殊参数 0 保持不变。函数执行期间,FUNCNAME 变量的第一个元素被设置为该函数的名字。

除下列例外之外,函数与其调用者在 shell 执行环境的所有其他方面都完全相同:除非用 declare 内建命令为函数赋予 trace 属性,或者用 set 内建命令启用了 -o functrace 选项(此时所有函数都继承 DEBUG 和 RETURN trap),否则 DEBUG 和 RETURN trap 不会被继承;除非启用了 -o errtrace shell 选项,否则 ERR trap 不会被继承。关于 trap 内建命令的说明,参见 4.1 节“Bourne shell 内建命令”。

FUNCNEST 变量如果被设为一个大于 0 的数值,就定义了函数的最大嵌套层级。超过该限制的函数调用会导致整条命令中止。

如果在内建命令 return 在函数中被执行,函数就完成,执行从函数调用之后的下一命令继续。与 RETURN trap 关联的任何命令都在继续执行之前执行。函数完成时,位置参数和特殊参数 # 的值恢复为函数执行之前的值。如果给 return 提供了数值参数,该值就是函数的返回状态;否则函数的返回状态是执行 return 之前最后执行的那条命令的退出状态。

函数的局部变量用 local 内建命令声明(局部变量)。通常,变量及其值在函数与其调用者之间是共享的。这些变量只对该函数及它所调用的命令可见。当 shell 函数调用其他函数时,这一点尤其重要。

在下面的说明中,当前作用域是指当前正在执行的函数。先前作用域由该函数的调用者等等组成,一直回到“全局”作用域,即 shell 没有在执行任何 shell 函数时。当前局部作用域中的局部变量,是指在当前正在执行的函数中用 local 或 declare 内建命令声明的变量。

局部变量会“遮蔽”在先前作用域中声明的同名变量。例如,在函数中声明的局部变量会隐藏先前作用域中声明的同名变量,包括全局变量:引用和赋值都指向该局部变量,先前作用域中的那些变量保持不变。函数返回后,全局变量重新可见。

shell 使用动态作用域来控制变量在函数内的可见性。使用动态作用域时,可见变量及其值是导致执行到达当前函数的那一串函数调用的结果。函数看到的变量值取决于该变量在其调用者中的值(如果有的话),无论那个调用者是全局作用域还是另一个 shell 函数。这也正是局部变量声明所遮蔽的值,以及函数返回时被恢复的值。

例如,如果变量 var 在函数 func1 中被声明为 local,而 func1 调用了另一个函数 func2,那么在 func2 内部对 var 的引用会解析为来自 func1 的局部变量 var,从而遮蔽任何名为 var 的全局变量。

下面的脚本演示了这种行为。执行时,该脚本显示

In func2, var = func1 local
func1()
{
    local var='func1 local'
    func2
}

func2()
{
    echo "In func2, var = $var"
}

var=global
func1

unset 内建命令也按照同样的动态作用域行事:如果某个变量是当前作用域的局部变量,unset 就取消它;否则 unset 会如上所述指向在任何调用作用域中找到的那个变量。如果当前局部作用域中的某个变量被 unset,它就保持该状态(表现为未设置),直到在该作用域中重新设置它,或者直到函数返回。函数一旦返回,先前作用域中该变量的任何实例就变得可见。如果 unset 作用于先前作用域中的变量,那么该同名变量曾被遮蔽的任何实例都变得可见(关于 localvar_unset shell 选项如何改变这一行为,见下文)。

declare(typeset)内建命令的 -f 选项(参见 4.2 节“Bash 内建命令”)列出函数名和函数定义。declare 或 typeset 的 -F 选项只列出函数名(如果启用了 extdebug shell 选项,还可选地列出源文件和行号)。函数可以被导出,使得子 shell 进程(执行单独的 shell 调用时创建的那些进程)自动拥有这些函数定义,这是通过 export 内建命令的 -f 选项实现的(参见 4.1 节“Bourne shell 内建命令”)。unset 内建命令的 -f 选项(参见 4.1 节“Bourne shell 内建命令”)删除函数定义。

函数可以是递归的。FUNCNEST 变量可用来限制函数调用栈的深度,从而限制函数调用的次数。默认情况下,Bash 对递归调用次数不设限制。

3.4 shell 参数#

参数(parameter)是存储值的实体。它可以是一个名称、一个数字,或者下面列出的某个特殊字符。变量是用名称表示的参数。变量有一个值以及零个或多个属性。属性用 declare 内建命令赋予(参见 4.2 节“Bash 内建命令”中 declare 的说明)。export 和 readonly 内建命令赋予特定的属性。

如果参数已被赋过值,它就是已设置的(set)。空串是合法的值。变量一旦设置,就只能通过 unset 内建命令取消设置。

变量用如下形式的语句赋值:

name=[value]

如果没有给出 value,变量被赋予空串。所有值都要经历波浪号展开、参数和变量展开、命令替换、算术展开以及引用去除(参见 3.5.3 节“shell 参数展开”)。如果变量设置了 integer 属性,那么即使没有使用 $((...)) 展开,value 也会被当作算术表达式求值(参见 3.5.5 节“算术展开”)。不进行单词拆分和文件名展开。赋值语句也可以作为 alias、declare、typeset、export、readonly 和 local 内建命令(声明命令)的参数出现。处于 POSIX 模式时(参见 6.11 节“Bash POSIX 模式”),这些内建命令可以出现在命令中一个或多个 command 内建命令实例之后,并保留这些赋值语句的特性。例如,

command export var=value

在赋值语句给 shell 变量或数组下标赋值的情形中(参见 6.7 节“数组”),‘+=’运算符会追加到或加到变量先前的值上。这也包括像 declare 这类接受赋值语句的声明命令的参数。当把‘+=’应用于设置了 integer 属性的变量时,变量当前的值和 value 各自被当作算术表达式求值,结果之和被赋给该变量。当前值通常是整数常量,但也可以是表达式。当把‘+=’用复合赋值的形式应用于数组变量时(参见 6.7 节“数组”),变量的值不会被取消设置(而使用‘=’时会被取消设置),新值被追加到数组末尾:对索引数组而言,从比数组最大下标大 1 的位置开始;对关联数组而言,则作为额外的键值对添加。当应用于字符串值变量时,value 会被展开并追加到变量的值上。

可以用 declare 或 local 内建命令的 -n 选项给变量赋予 nameref 属性(参见 4.2 节“Bash 内建命令”),从而创建一个 nameref,即对另一个变量的引用。这允许间接地操作变量。每当引用该 nameref 变量、给它赋值、取消它的设置,或者修改它的属性时(使用或更改 nameref 属性本身除外),实际操作都作用于由该 nameref 变量的值所指定的那个变量。nameref 常用于 shell 函数内部,用来引用名字作为参数传给函数的变量。例如,如果某个变量名作为第一个参数传给 shell 函数,那么在函数内部运行

declare -n ref=$1

会创建一个局部 nameref 变量 ref,其值是作为第一个参数传入的变量名。对 ref 的引用和赋值,以及对它属性的修改,都被当作对名字由 $1 传入的那个变量的引用、赋值和属性修改。

如果 for 循环中的控制变量具有 nameref 属性,那么单词列表可以是一个 shell 变量列表,循环执行时会依次为列表中的每个单词建立一个名称引用。数组变量不能被赋予 nameref 属性。不过,nameref 变量可以引用数组变量和带下标的数组变量。nameref 可以用 unset 内建命令的 -n 选项取消设置(参见 4.1 节“Bourne shell 内建命令”)。否则,如果执行 unset 时以某个 nameref 变量的名字作为参数,被该 nameref 变量引用的那个变量会被取消设置。

shell 启动时,它会读取自己的环境,并为每个具有合法名称的环境变量创建一个 shell 变量,如下所述(参见 3.7.4 节“环境”)。

3.4.1 位置参数#

位置参数是由一位或多位数字表示的参数,但不包括单个数字 0。位置参数在调用 shell 时由 shell 的参数赋值,也可以用 set 内建命令重新赋值。位置参数 N 可以引用为 ${N},当 N 只由一位数字组成时也可以引用为 $N。位置参数不能用赋值语句赋值。set 和 shift 内建命令用来设置和取消设置它们(参见第 4 章“shell 内建命令”)。执行 shell 函数时,位置参数会被临时替换(参见 3.3 节“shell 函数”)。

当展开由多于一位数字组成的位置参数时,必须把它括在花括号中。不加花括号时,‘$’后面的数字只能指代前九个位置参数之一($1-$9)或者特殊参数 $0(见下文)。

3.4.2 特殊参数#

shell 对若干参数作特殊处理。这些参数只能被引用;不允许给它们赋值。特殊参数由下列字符之一表示。

($*) 展开为位置参数,从 1 开始。当展开不在双引号内时,每个位置参数展开为一个单独的单词。在进行单词展开的上下文中,这些单词还要经历进一步的单词拆分和文件名展开。当展开出现在双引号内时,它展开为单个单词,其中各个参数的值用 IFS 变量值的第一个字符分隔。也就是说,"$*" 等价于 "$1c$2c…",其中 c 是 IFS 变量值的第一个字符。如果 IFS 未设置,参数之间用空格分隔。如果 IFS 为 null,参数直接拼接,中间没有分隔符。

($@) 展开为位置参数,从 1 开始。在进行单词拆分的上下文中,它把每个位置参数展开为一个单独的单词;如果不在双引号内,这些单词还要经历单词拆分。在不进行单词拆分的上下文中(例如赋值语句的值部分),它展开为单个单词,各位置参数之间用空格分隔。当展开出现在双引号内且进行单词拆分时,每个参数展开为一个单独的单词。也就是说,"$@" 等价于 "$1" "$2" …。如果带双引号的展开出现在某个单词内部,则第一个参数的展开与原单词开头部分的展开拼接,最后一个参数的展开与原单词结尾部分的展开拼接。当没有位置参数时,"$@" 和 $@ 展开为空(也就是说,它们被移除)。

($#) 展开为位置参数的个数,以十进制表示。

($?) 展开为最近执行的那条命令的退出状态。

($-)(一个连字符。)展开为当前的选项标志,即调用时指定的、由 set 内建命令设置的,或者由 shell 自身设置的(例如 -i 选项)那些标志。

($$) 展开为 shell 的进程 ID。在子 shell 中,它展开为发起调用的那个 shell 的进程 ID,而不是子 shell 的进程 ID。

($!) 展开为最近被放入后台的作业的进程 ID,无论该作业是作为异步命令执行的,还是用 bg 内建命令执行的(参见 7.2 节“作业控制内建命令”)。

($0) 展开为 shell 或 shell 脚本的名字。它在 shell 初始化时设置。如果 Bash 是通过一个命令文件调用的(参见 3.8 节“shell 脚本”),$0 被设为该文件的名字。如果 Bash 是用 -c 选项启动的(参见 6.1 节“调用 Bash”),那么当要执行的字符串之后存在第一个参数时,$0 被设为该参数。否则,$0 被设为用来调用 Bash 的文件名,即第零个参数所给出的名字。

3.5 shell 展开#

展开是在命令行被拆分成词元(token)之后进行的。Bash 执行下列这些展开:

展开的顺序是:花括号展开;波浪号展开、参数和变量展开、算术展开以及命令替换(以从左到右的方式进行);单词拆分;文件名展开;以及引用去除。

在能够支持它的系统上,还有一种额外的展开可用:进程替换。它与波浪号展开、参数和变量展开、算术展开以及命令替换在同一时刻进行。

引用去除总是最后进行。它移除原单词中存在的引号字符,而不是由其他某个展开产生的引号字符,除非这些引号字符本身被引用过。更多细节参见 3.5.9 节“引用去除”。

只有花括号展开、单词拆分和文件名展开可能增加展开结果的单词数;其他展开都是把一个单词展开为一个单词。仅有的例外是 "$@" 和 $* 的展开(参见 3.4.2 节“特殊参数”),以及 "${name[@]}" 和 ${name[*]} 的展开(参见 6.7 节“数组”)。

3.5.1 花括号展开#

花括号展开是一种生成共享同一前缀和后缀(两者都可以为空)的任意字符串的机制。该机制与文件名展开类似(参见 3.5.8 节“文件名展开”),但生成的文件名不必真实存在。要作花括号展开的模式由以下部分构成:一个可选的前导部分(preamble),后接一对花括号之间的一系列逗号分隔字符串或一个序列表达式,再后接一个可选的后置部分(postscript)。前导部分被加到花括号内每个字符串的前面,然后后置部分被追加到每个结果字符串之后,从左到右依次展开。

花括号展开可以嵌套。各个被展开字符串的结果不排序;花括号展开保持从左到右的顺序。例如,

bash$ echo a{d,c,b}e
ade ace abe

序列表达式的形式是 {x..y[..incr]},其中 x 和 y 要么是整数,要么是字母,而 incr 是可选的增量,是一个整数。给出整数时,该表达式展开为 x 与 y 之间(含两端)的每一个数字。如果 x 或 y 以零开头,则生成的每一项都会含有相同的位数,必要时用零填充。给出字母时,该表达式按照 C 区域设置(locale)展开为 x 与 y 之间(含两端)按字典序排列的每一个字符。注意 x 和 y 必须是同一类型(整数或字母)。给出增量时,它被用作相邻两项之间的差。默认的增量是 1 或 -1,视情况而定。

花括号展开在任何其他展开之前进行,其他展开所特有的字符在结果中保持不变。它严格是文本层面的。Bash 不会对此展开的上下文或花括号之间的文本作任何语法解释。

一个正确构成的花括号展开必须包含未引用的左花括号和右花括号,并且至少包含一个未引用的逗号或一个合法的序列表达式。任何构成错误的花括号展开都保持原样不变。

可以用反斜杠引用‘{’或‘,’,以阻止它被当作花括号表达式的一部分。为避免与参数展开冲突,字符串‘${’不被视为可以进行花括号展开,并且会抑制花括号展开,直到闭合的‘}’为止。

当要生成的字符串的公共前缀比上面例子中更长时,这种构造通常用作简写:

mkdir /usr/local/src/bash/{old,new,dist,bugs}

或

chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}

花括号展开引入了与历史版本 sh 的一点不兼容。当左花括号或右花括号作为单词的一部分出现时,sh 不把它们特殊处理,并在输出中原样保留它们。而 Bash 会因为花括号展开而从单词中移除花括号。例如,输入给 sh 的单词 'file{1,2}' 在输出中与输入完全相同。Bash 在花括号展开后把该单词输出为 'file1 file2'。为了与 sh 严格兼容,可以用 +B 选项启动 Bash,或者用 set 命令(参见第 4 章“shell 内建命令”)的 +B 选项禁用花括号展开。

3.5.2 波浪号展开#

如果一个单词以未引用的波浪号字符(‘~’)开头,那么直到第一个未引用斜杠为止的所有字符(如果没有未引用斜杠,则是全部字符)都被视为波浪号前缀(tilde-prefix)。如果波浪号前缀中没有任何字符被引用,则波浪号前缀中波浪号之后的字符被当作可能的登录名。如果该登录名是空串,波浪号被替换为 HOME shell 变量的值。如果 HOME 未设置,波浪号改为展开为执行该 shell 的用户的主目录。否则,波浪号前缀被替换为与指定登录名相关联的主目录。

如果波浪号前缀是‘~+’,则用 shell 变量 PWD 的值替换该波浪号前缀。如果波浪号前缀是‘~-’,则 shell 用 shell 变量 OLDPWD 的值来替换(如果它已设置)。

如果波浪号前缀中波浪号之后的字符由一个数字 N 组成,且可选地带有一个‘+’或‘-’前缀,则波浪号前缀被替换为目录栈中对应的元素,就像用波浪号前缀中波浪号之后的字符作为参数调用 dirs 内建命令所显示的那样(参见 6.8 节“目录栈”)。如果波浪号前缀去掉波浪号后是一个不带前导‘+’或‘-’的数字,波浪号展开会假定为‘+’。

波浪号展开的结果被当作已引用处理,因此该替换不经历单词拆分和文件名展开。

如果登录名无效,或者波浪号展开失败,波浪号前缀保持原样不变。

Bash 会检查每个变量赋值中紧跟在‘:’或第一个‘=’之后的未引用波浪号前缀,并在这些情形下执行波浪号展开。因此,可以在对 PATH、MAILPATH 和 CDPATH 的赋值中使用带波浪号的文件名,shell 会赋给展开后的值。

下表显示 Bash 如何处理未引用的波浪号前缀:

波浪号前缀 含义
~ $HOME 的值。
~/foo $HOME/foo
~fred/foo 用户 fred 主目录中的目录或文件 foo。
~+/foo $PWD/foo
~-/foo ${OLDPWD-'~-'}/foo
~N dirs +N 所显示的字符串。
~+N dirs +N 所显示的字符串。
~-N dirs -N 所显示的字符串。

当满足变量赋值条件的单词(参见 3.4 节“shell 参数”)作为简单命令的参数出现时,Bash 也会对它们执行波浪号展开。处于 POSIX 模式时,除上面列出的那些声明命令外,Bash 不这样做。

3.5.3 shell 参数展开#

‘$’字符引入参数展开、命令替换或算术展开。要展开的参数名或符号可以括在花括号中,花括号是可选的,其作用是保护要展开的变量,使其不被紧随其后、可能被解释为名称一部分的字符所影响。例如,如果第一个位置参数的值是‘a’,那么 ${11} 展开为第十一个位置参数的值,而 $11 展开为‘a1’。

使用花括号时,与之匹配的结束花括号是第一个既未被反斜杠转义、也不在引号字符串之内、并且不在内嵌的算术展开、命令替换或参数展开之中的‘}’。

参数展开的基本形式是 ${parameter},它替换为 parameter 的值。parameter 是如上所述的 shell 参数(参见 3.4 节“shell 参数”)或数组引用(参见 6.7 节“数组”)。当 parameter 是位数多于一位的位置参数,或者 parameter 后面跟着一个不应被解释为其名称一部分的字符时,花括号是必需的。

如果 parameter 的第一个字符是感叹号(!),并且 parameter 不是 nameref,它就引入一层间接(indirection)。Bash 把展开 parameter 其余部分所形成的值当作新的 parameter;然后展开这个新的 parameter,并在其余展开中使用该值,而不是使用原 parameter 的展开结果。这被称为间接展开。该值要经历波浪号展开、参数展开、命令替换和算术展开。如果 parameter 是 nameref,那么为兼容起见,这会展开为 parameter 所引用的变量的名字,而不执行完整的间接展开。例外是下面说明的 ${!prefix*} 和 ${!name[@]} 的展开。感叹号必须紧跟在左花括号之后,才能引入间接。

在下列每一种情形中,word 都要经历波浪号展开、参数展开、命令替换和算术展开。

在不进行子串展开时,使用下面说明的形式(例如‘:-’),Bash 会测试参数是否未设置或为 null。省略冒号则只测试参数是否未设置。换句话说,如果包含冒号,运算符既测试 parameter 是否存在,也测试它的值不为 null;如果省略冒号,运算符只测试是否存在。

${parameter:-word}

如果 parameter 未设置或为 null,则替换为 word 的展开。否则替换为 parameter 的值。

$ v=123
$ echo ${v-unset}
123
$ echo ${v:-unset-or-null}
123
$ unset v
$ echo ${v-unset}
unset
$ v=
$ echo ${v-unset}

$ echo ${v:-unset-or-null}
unset-or-null
${parameter:=word}

如果 parameter 未设置或为 null,则把 word 的展开赋给 parameter,而展开的结果就是 parameter 的最终值。位置参数和特殊参数不能用这种方式赋值。

$ unset var
$ : ${var=DEFAULT}
$ echo $var
DEFAULT
$ var=
$ : ${var=DEFAULT}
$ echo $var

$ var=
$ : ${var:=DEFAULT}
$ echo $var
DEFAULT
$ unset var
$ : ${var:=DEFAULT}
$ echo $var
DEFAULT
${parameter:?word}

如果 parameter 为 null 或未设置,shell 会把 word 的展开(如果 word 不存在,则把大意如此的提示消息)写到标准错误,并且如果 shell 不是交互式的,就以非零状态退出。交互式 shell 不会退出,但也不会执行与该展开相关联的那条命令。否则,替换为 parameter 的值。

$ var=
$ : ${var:?var is unset or null}
bash: var: var is unset or null
$ echo ${var?var is unset}

$ unset var
$ : ${var?var is unset}
bash: var: var is unset
$ : ${var:?var is unset or null}
bash: var: var is unset or null
$ var=123
$ echo ${var:?var is unset or null}
123
${parameter:+word}

如果 parameter 为 null 或未设置,则不替换任何内容;否则替换为 word 的展开。parameter 的值不会被使用。

$ var=123
$ echo ${var:+var is set and not null}
var is set and not null
$ echo ${var+var is set}
var is set
$ var=
$ echo ${var:+var is set and not null}

$ echo ${var+var is set}
var is set
$ unset var
$ echo ${var+var is set}

$ echo ${var:+var is set and not null}

$
${parameter:offset}
${parameter:offset:length}

这被称为子串展开(Substring Expansion)。它展开为 parameter 的值中从 offset 指定的字符开始、最多 length 个字符的子串。如果 parameter 是‘@’或‘*’,是用‘@’或‘*’作下标的索引数组,或者是一个关联数组名,则结果有所不同,如下所述。如果省略 :length(上面的第一种形式),它展开为 parameter 的值中从 offset 指定的字符开始、一直延伸到该值末尾的子串。

如果省略 offset,它被当作 0。如果省略 length,但 offset 之后的冒号存在,则 length 被当作 0。length 和 offset 是算术表达式(参见 6.5 节“shell 算术”)。

如果 offset 求值为一个小于零的数,该值被用作从 parameter 的值末尾开始计算的字符偏移。如果 length 求值为一个小于零的数,它被解释为从 parameter 的值末尾开始计算的字符偏移,而不是字符个数,展开结果就是 offset 与该结果之间的那些字符。

注意,负的 offset 与冒号之间必须至少用一个空格分隔,以免与‘:-’展开混淆。

下面是一些示例,说明对参数和带下标的数组进行子串展开的情况:

$ string=01234567890abcdefgh
$ echo ${string:7}
7890abcdefgh
$ echo ${string:7:0}

$ echo ${string:7:2}
78
$ echo ${string:7:-2}
7890abcdef
$ echo ${string: -7}
bcdefgh
$ echo ${string: -7:0}

$ echo ${string: -7:2}
bc
$ echo ${string: -7:-2}
bcdef
$ set -- 01234567890abcdefgh
$ echo ${1:7}
7890abcdefgh
$ echo ${1:7:0}

$ echo ${1:7:2}
78
$ echo ${1:7:-2}
7890abcdef
$ echo ${1: -7}
bcdefgh
$ echo ${1: -7:0}

$ echo ${1: -7:2}
bc
$ echo ${1: -7:-2}
bcdef
$ array[0]=01234567890abcdefgh
$ echo ${array[0]:7}
7890abcdefgh
$ echo ${array[0]:7:0}

$ echo ${array[0]:7:2}
78
$ echo ${array[0]:7:-2}
7890abcdef
$ echo ${array[0]: -7}
bcdefgh
$ echo ${array[0]: -7:0}

$ echo ${array[0]: -7:2}
bc
$ echo ${array[0]: -7:-2}
bcdef

如果 parameter 是‘@’或‘*’,结果是从 offset 开始的 length 个位置参数。负的 offset 相对于比最大的位置参数大 1 的位置计算,因此偏移 -1 求值为最后一个位置参数(如果没有任何位置参数,则为 0)。如果 length 求值为一个小于零的数,就是展开错误。

下面的示例说明使用位置参数进行子串展开的情况:

$ set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:7}
7 8 9 0 a b c d e f g h
$ echo ${@:7:0}

$ echo ${@:7:2}
7 8
$ echo ${@:7:-2}
bash: -2: substring expression < 0
$ echo ${@: -7:2}
b c
$ echo ${@:0}
./bash 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${@:0:2}
./bash 1
$ echo ${@: -7:0}

如果 parameter 是用‘@’或‘*’作下标的索引数组名,结果是该数组中从 ${parameter[offset]} 开始、共 length 个成员。负的 offset 相对于比指定数组最大下标大 1 的位置计算。如果 length 求值为一个小于零的数,就是展开错误。

下面这些示例说明如何对索引数组使用子串展开:

$ array=(0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h)
$ echo ${array[@]:7}
7 8 9 0 a b c d e f g h
$ echo ${array[@]:7:2}
7 8
$ echo ${array[@]: -7:2}
b c
$ echo ${array[@]: -7:-2}
bash: -2: substring expression < 0
$ echo ${array[@]:0}
0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
$ echo ${array[@]:0:2}
0 1
$ echo ${array[@]: -7:0}

对关联数组应用子串展开会产生未定义的结果。子串下标以零为基准,除非使用的是位置参数,此时下标默认从 1 开始。如果 offset 为 0,并且使用的是位置参数,则 $0 被加到列表的最前面。

${!prefix*}
${!prefix@}

展开为名称以 prefix 开头的那些变量的名字,用 IFS 特殊变量值的第一个字符分隔。当使用‘@’且展开出现在双引号内时,每个变量名展开为一个单独的单词。

${!name[@]}
${!name[*]}

如果 name 是数组变量,则展开为 name 中被赋过值的数组下标(键)列表。如果 name 不是数组,则在 name 已设置时展开为 0,否则展开为空。当使用‘@’且展开出现在双引号内时,每个键展开为一个单独的单词。

${#parameter}

替换为 parameter 的值按字符计的长度。如果 parameter 是‘*’或‘@’,替换所得的值是位置参数的个数。如果 parameter 是用‘*’或‘@’作下标的数组名,替换所得的值是数组中的元素个数。如果 parameter 是用负数作下标的索引数组名,该数字被解释为相对于比 parameter 最大下标大 1 的位置,因此负下标从数组末尾往回计数,下标 -1 指向最后一个元素。

${parameter#word}
${parameter##word}

word 被展开以产生一个模式,并按照下面说明的规则(参见 3.5.8.1 节“模式匹配”)与展开后的 parameter 值匹配。如果该模式匹配 parameter 展开值的开头部分,那么展开的结果就是删除最短匹配模式(‘#’的情形)或最长匹配模式(‘##’的情形)之后的 parameter 展开值。如果 parameter 是‘@’或‘*’,模式删除操作依次应用于每个位置参数,展开结果就是所得列表。如果 parameter 是用‘@’或‘*’作下标的数组变量,模式删除操作依次应用于数组的每个成员,展开结果就是所得列表。

${parameter%word}
${parameter%%word}

word 被展开以产生一个模式,并按照下面说明的规则(参见 3.5.8.1 节“模式匹配”)与展开后的 parameter 值匹配。如果该模式匹配 parameter 展开值的末尾部分,那么展开的结果就是删除最短匹配模式(‘%’的情形)或最长匹配模式(‘%%’的情形)之后的 parameter 值。如果 parameter 是‘@’或‘*’,模式删除操作依次应用于每个位置参数,展开结果就是所得列表。如果 parameter 是用‘@’或‘*’作下标的数组变量,模式删除操作依次应用于数组的每个成员,展开结果就是所得列表。

${parameter/pattern/string}
${parameter//pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}

pattern 被展开以产生一个模式,并如下所述(参见 3.5.8.1 节“模式匹配”)与展开后的 parameter 值匹配。展开值中 pattern 的最长匹配被替换为 string。string 要经历波浪号展开、参数和变量展开、算术展开、命令替换和进程替换以及引用去除。

在上面的第一种形式中,只替换第一个匹配。如果在 parameter 与 pattern 之间有两个斜杠(上面的第二种形式),pattern 的所有匹配都被替换为 string。如果 pattern 前面带有‘#’(上面的第三种形式),它必须匹配 parameter 展开值的开头。如果 pattern 前面带有‘%’(上面的第四种形式),它必须匹配 parameter 展开值的末尾。

如果 string 的展开为 null,pattern 的匹配被删除,并且跟在 pattern 之后的‘/’可以省略。

如果使用 shopt 启用了 patsub_replacement shell 选项(参见 4.3.2 节“Shopt 内建命令”),那么 string 中任何未引用的‘&’实例都被替换为 pattern 的匹配部分。这样做意在复现一个常见的 sed 惯用法。

引用 string 的任何部分都会阻止对被引用部分作替换,包括存储在 shell 变量中的替换字符串。反斜杠转义 string 中的‘&’;反斜杠会被移除,以便在替换字符串中允许出现字面的‘&’。如果 string 带双引号,用户应当小心,以避免反斜杠与双引号之间发生不希望的相互作用,因为反斜杠在双引号内有特殊含义。模式替换是在展开 string 之后才检查未引用的‘&’,因此用户应当确保在替换中希望被按字面处理的任何‘&’出现位置都被正确引用,并确保希望被替换的任何‘&’实例都未被引用。

例如,

var=abcdef
rep='& '
echo ${var/abc/& }
echo "${var/abc/& }"
echo ${var/abc/$rep}
echo "${var/abc/$rep}"

将显示四行 "abc def",而

var=abcdef
rep='& '
echo ${var/abc/\& }
echo "${var/abc/\& }"
echo ${var/abc/"& "}
echo ${var/abc/"$rep"}

将显示四行 "& def"。与模式删除运算符一样,包围替换字符串的双引号会引用展开后的字符,而包住整个参数替换的双引号则不会,因为该展开是在一个不考虑任何外层双引号的上下文中进行的。

由于反斜杠可以转义‘&’,它也可以转义替换字符串中的反斜杠。这意味着‘\’会把一个字面的反斜杠插入替换结果中,因此下面这两条 echo 命令

var=abcdef
rep='\\&xyz'
echo ${var/abc/\\&xyz}
echo ${var/abc/$rep}

都会输出‘\abcxyzdef’。

应当很少需要只把 string 括在双引号中。

如果 nocasematch shell 选项(参见 4.3.2 节“Shopt 内建命令”中 shopt 的说明)已启用,匹配时不考虑字母字符的大小写。

如果 parameter 是‘@’或‘*’,替换操作依次应用于每个位置参数,展开结果就是所得列表。如果 parameter 是用‘@’或‘*’作下标的数组变量,替换操作依次应用于数组的每个成员,展开结果就是所得列表。

${parameter^pattern}
${parameter^^pattern}
${parameter,pattern}
${parameter,,pattern}

这种展开修改 parameter 中字母字符的大小写。首先,pattern 被展开以产生一个模式,如下文 3.5.8.1 节“模式匹配”所述。

然后 Bash 按照下面的说明,把 parameter 展开值中的字符与 pattern 逐一比对。如果某个字符匹配该模式,就转换它的大小写。该模式不应试图匹配多于一个字符。

使用‘^’把匹配 pattern 的小写字母转换为大写;使用‘,’把匹配的大写字母转换为小写。‘^’和‘,’变体只检查展开值中的第一个字符,并在它匹配 pattern 时转换其大小写;‘^^’和‘,,’变体检查展开值中的所有字符,并转换其中每一个匹配 pattern 的字符。如果省略 pattern,它被当作‘?’,即匹配每一个字符。

如果 parameter 是‘@’或‘*’,大小写修改操作依次应用于每个位置参数,展开结果就是所得列表。如果 parameter 是用‘@’或‘*’作下标的数组变量,大小写修改操作依次应用于数组的每个成员,展开结果就是所得列表。

${parameter@operator}

展开结果要么是对 parameter 的值所作的变换,要么是关于 parameter 本身的信息,这取决于 operator 的取值。每个 operator 都是单个字母:

U 展开结果是一个字符串,即把 parameter 值中的小写字母字符转换为大写后的结果。

u 展开结果是一个字符串,即把 parameter 值的第一个字符转换为大写后的结果(如果它是字母的话)。

L 展开结果是一个字符串,即把 parameter 值中的大写字母字符转换为小写后的结果。

Q 展开结果是一个字符串,即按可重新用作输入的形式引用(quoting)的 parameter 值。

E 展开结果是一个字符串,即 parameter 值中的反斜杠转义序列按照 $'…' 引用机制展开后的结果。

P 展开结果是一个字符串,即把 parameter 的值当作提示符字符串展开所得的结果(参见 6.9 节“控制提示符”)。

A 展开结果是一个字符串,其形式为赋值语句或 declare 命令,如果对它求值,就能重建带属性和值的 parameter。

K 产生 parameter 值的可能带引号的版本,但它把索引数组和关联数组的值打印为一串带引号的键值对(参见 6.7 节“数组”)。键和值都按可重新用作输入的形式引用。

a 展开结果是一个字符串,由表示 parameter 属性的标志值组成。

k 类似于‘K’变换,但在单词拆分之后把索引数组和关联数组的键与值展开为单独的单词。

如果 parameter 是‘@’或‘*’,该操作依次应用于每个位置参数,展开结果就是所得列表。如果 parameter 是用‘@’或‘*’作下标的数组变量,该操作依次应用于数组的每个成员,展开结果就是所得列表。

展开的结果如下所述要经历单词拆分和文件名展开。

3.5.4 命令替换#

命令替换允许用命令的输出替换命令本身。命令替换的标准形式是命令被如下包围时:

$(command)

或(已废弃):

`command`

Bash 执行命令替换的方式是在子 shell 环境中执行 command,并用该命令的标准输出(删除所有结尾换行)替换命令替换。嵌入的换行不会被删除,但在单词拆分期间可能会被移除。命令替换 $(cat file) 可以用等价但更快的 $(< file) 替代。

使用老式反引号形式的替换时,反斜杠保留其字面含义,除非其后跟 $、` 或 \。第一个前面没有反斜杠的反引号终止命令替换。使用 $(command) 形式时,圆括号之间的所有字符都构成命令;其中没有任何字符被特殊对待。

还有一种替代形式的命令替换:

${c command;}

它在当前执行环境中执行 command 并捕获其输出,同样删除结尾换行。

左花括号之后的字符 c 必须是空格、制表符、换行或 |,并且右花括号必须出现在保留字可以出现的位置(即前面是诸如分号之类的命令终止符)。Bash 允许右花括号与单词中其余字符直接相连,而不必像保留字通常要求的那样后跟一个 shell 元字符。

command 的任何副作用都会立即在当前执行环境中生效,并在命令完成后持续存在于当前环境中(例如,exit 内建命令会退出 shell)。

这种命令替换表面上类似于执行一个未命名的 shell 函数:会像执行 shell 函数时那样创建局部变量,return 内建命令会强制 command 结束;然而执行环境的其余部分,包括位置参数,都与调用者共享。

如果左花括号之后的第一个字符是 |,则该结构在 command 执行后展开为 REPLY shell 变量的值,不删除任何结尾换行,并且 command 的标准输出与调用 shell 中的相同。command 执行时,Bash 会把 REPLY 创建为一个初始未设置的局部变量,并在 command 完成后把 REPLY 恢复为命令替换之前的值,与任何局部变量一样。

例如,下面的结构展开为 12345,并使当前执行环境中的 shell 变量 X 保持不变:

${ local X=12345; echo $X; }

(不把 X 声明为局部变量就会像正常的 shell 函数执行那样修改它在当前环境中的值),而下面的结构不需要任何输出就能展开为 12345:

${| REPLY=12345;}

并且会把 REPLY 恢复为命令替换之前的值。

命令替换可以嵌套。使用反引号形式嵌套时,用反斜杠转义内层反引号。

如果替换出现在双引号内,Bash 不会对结果执行单词拆分和文件名展开。

3.5.5 算术展开#

算术展开对一个算术表达式求值并替换为结果。算术展开的格式是:

$(( expression ))

expression 经历的展开与它位于双引号内时相同,但 expression 中未转义的双引号字符不被特殊对待,而是被移除。expression 中的所有词元都经历参数和变量展开、命令替换以及引号消除。结果被当作要计算的算术表达式。由于 Bash 处理双引号的方式可能导致空字符串,算术展开把空字符串视为求值为 0 的表达式。算术展开可以嵌套。

求值按下文列出的规则进行(参见 6.5 节“shell 算术”)。如果表达式无效,Bash 会向标准错误打印一条表示失败的消息,不执行该替换,也不执行与该展开关联的命令。

3.5.6 进程替换#

进程替换允许使用文件名来引用某个进程的输入或输出。它采用如下形式:

<(list)

或

>(list)

进程 list 被异步运行,其输入或输出表现为一个文件名。作为展开的结果,这个文件名被作为参数传递给当前命令。

如果使用 >(list) 形式,写入该文件就为 list 提供输入。如果使用 <(list) 形式,读取该文件就得到 list 的输出。注意 < 或 > 与左圆括号之间不能出现空格,否则该结构会被解释为重定向。

进程替换在支持命名管道(FIFO)或以 /dev/fd 方法命名打开文件的系统上受支持。

如果可用,进程替换与参数和变量展开、命令替换以及算术展开同时执行。

3.5.7 单词拆分#

shell 会扫描那些没有出现在双引号内的参数展开、命令替换和算术展开的结果,以进行单词拆分。没有被展开的单词不会被拆分。

shell 把 $IFS 的每个字符都当作分隔符,并使用这些字符作为字段终止符,把其他展开的结果拆分成字段。

IFS 空白字符是上文定义(参见第 2 章“定义”)的、出现在 IFS 值中的空白。空格、制表符和换行始终被视为 IFS 空白,即使它们没有出现在当前区域设置(locale)的空白类别中。

如果 IFS 未设置,单词拆分的行为就像它的值是 <空格><制表符><换行>,并把这三个字符当作 IFS 空白。如果 IFS 的值为空,则不进行单词拆分,但隐式空参数(见下文)仍会被移除。

单词拆分首先移除此前各次展开结果开头和末尾的 IFS 空白字符序列,然后拆分剩余的单词。

如果 IFS 的值完全由 IFS 空白组成,则任何 IFS 空白字符序列都界定一个字段,因此字段由未加引号的 IFS 空白之外的字符组成,空字段只由引用(quoting)产生。

如果 IFS 包含非空白字符,那么 IFS 值中任何不是 IFS 空白的字符,连同紧邻的 IFS 空白字符,都界定一个字段。这意味着相邻的非 IFS 空白分隔符会产生一个空字段。IFS 空白字符序列同样界定一个字段。

显式的空参数("" 或 '')会被保留,并作为空字符串传给命令。未加引号的隐式空参数(来自没有值的参数展开)会被移除。在双引号内展开一个没有值的参数会产生一个空字段,它被保留并作为空字符串传给命令。

当一个加了引号的空参数作为一个展开结果非空的单词的一部分出现时,单词拆分移除该空参数部分,只留下非空的展开结果。也就是说,单词 -d'' 在单词拆分和空参数移除之后变成 -d。

3.5.8 文件名展开#

在单词拆分之后,除非设置了 -f 选项(参见 4.3.1 节“set 内建命令”),Bash 会扫描每个单词中的 *、? 和 [ 字符。如果出现其中一个字符且它未被引用,则该单词被视为一个模式,并被替换为匹配该模式的文件名排序列表(参见 3.5.8.1 节“模式匹配”),具体还受 GLOBSORT shell 变量的值影响(参见 5.2 节“Bash 变量”)。

如果没有找到匹配的文件名,且 shell 选项 nullglob 处于禁用状态,则该单词保持原样。如果设置了 nullglob 选项而没有找到匹配项,该单词会被移除。如果设置了 failglob shell 选项而没有找到匹配项,Bash 会打印一条错误消息,并且不执行该命令。如果启用了 shell 选项 nocaseglob,则匹配时不考虑字母字符的大小写。

当模式用于文件名展开时,文件名开头的 . 或紧跟斜杠之后的 . 必须被显式匹配,除非设置了 shell 选项 dotglob。为了匹配文件名 . 和 ..,模式必须以 . 开头(例如 .?),即使设置了 dotglob 也是如此。如果启用了 globskipdots shell 选项,则文件名 . 和 .. 永远不会匹配,即使模式以 . 开头。在不匹配文件名时,. 字符不被特殊对待。

匹配文件名时,斜杠字符必须始终由模式中的斜杠显式匹配,但在其他匹配上下文中,它可以由下文描述的特殊模式字符匹配(参见 3.5.8.1 节“模式匹配”)。

关于 nocaseglob、nullglob、globskipdots、failglob 和 dotglob 选项的说明,参见 4.3.2 节“shopt 内建命令”中对 shopt 的描述。

GLOBIGNORE shell 变量可用于限制匹配某个模式的文件名集合。如果设置了 GLOBIGNORE,每个同时匹配 GLOBIGNORE 中某个模式的匹配文件名都会从匹配列表中移除。如果设置了 nocaseglob 选项,则与 GLOBIGNORE 中模式的匹配不考虑大小写。当 GLOBIGNORE 已设置且非空时,文件名 . 和 .. 总会被忽略。然而,把 GLOBIGNORE 设为非空值会产生启用 dotglob shell 选项的效果,因此所有其他以 . 开头的文件名都会匹配。要恢复忽略以 . 开头文件名的旧行为,可把 .* 作为 GLOBIGNORE 中的一个模式。当 GLOBIGNORE 未设置时,dotglob 选项被禁用。GLOBIGNORE 的模式匹配遵循 extglob shell 选项的设置。

GLOBSORT shell 变量的值控制路径名展开结果的排序方式,如下文所述(参见 5.2 节“Bash 变量”)。

3.5.8.1 模式匹配#

模式中出现的任何字符(下文描述的特殊模式字符除外)都匹配它自身。NUL 字符不能出现在模式中。反斜杠转义其后的字符;匹配时转义用的反斜杠被丢弃。如果要按字面匹配这些特殊模式字符,就必须对它们加引用。

特殊模式字符具有以下含义:

例如,在默认的 C 区域设置中,[a-dx-z] 等价于 [abcdxyz]。许多区域设置按字典顺序对字符排序,在这些区域设置中 [a-dx-z] 通常不等价于 [abcdxyz];例如,它可能等价于 [aBbCcDdxYyZz]。要获得方括号表达式中范围的传统解释,可以把 LC_COLLATE 或 LC_ALL 环境变量设为值 C,或者启用 globasciiranges shell 选项,以此强制使用 C 区域设置。

在方括号表达式内,可以用 [:class:] 语法指定字符类,其中 class 是 POSIX 标准中定义的以下类之一:

alnum alpha ascii blank cntrl digit graph lower
print punct space upper word xdigit

字符类匹配属于该类的任何字符。word 字符类匹配字母、数字和字符 _。

例如,下面的模式会匹配当前区域设置中属于 space 字符类的任何字符,然后是任何大写字母或 !,一个点,最后是任何小写字母或连字符。

[[:space:]][[:upper:]!].[-[:lower:]]

在方括号表达式内,可以用 [=c=] 语法指定等价类,它匹配与字符 c 具有相同排序权重(由当前区域设置定义)的所有字符。

在方括号表达式内,[.symbol.] 语法匹配排序符号 symbol。

如果使用 shopt 内建命令启用了 extglob shell 选项,shell 会识别若干扩展模式匹配运算符。在下文的描述中,pattern-list 是由一个或多个以 | 分隔的模式组成的列表。匹配文件名时,dotglob shell 选项决定受测试的文件名集合,如前所述。复合模式可以由下列一个或多个子模式构成:

extglob 选项会改变解析器的行为,因为圆括号通常被视为具有语法含义的运算符。为确保扩展匹配模式被正确解析,请确保在解析包含这些模式的结构(包括 shell 函数和命令替换)之前已经启用 extglob。

匹配文件名时,dotglob shell 选项决定受测试的文件名集合:启用 dotglob 时,文件名集合包含所有以 . 开头的文件,但文件名 . 和 .. 必须由以点开头的模式或子模式匹配;禁用时,该集合不包含任何以 . 开头的文件名,除非模式或子模式以 . 开头。如果启用了 globskipdots shell 选项,文件名 . 和 .. 永远不会出现在集合中。如上所述,只有在匹配文件名时 . 才有特殊含义。

对长字符串进行复杂的扩展模式匹配速度较慢,尤其是当模式包含可选分支而字符串包含多个匹配时。改为对较短的字符串分别进行匹配,或者使用字符串数组而不是单个长字符串,可能会更快。

3.5.9 引号消除#

在前述各次展开之后,所有未加引号出现的字符 \、' 和 "(不是由上述某个展开产生的)都会被移除。

3.6 重定向#

在命令执行之前,可以用 shell 解释的特殊记法对其输入和输出进行重定向。重定向允许对命令的文件句柄进行复制、打开、关闭,使其指向不同的文件,并能改变命令读取和写入的文件。与 exec 内建命令一起使用时,重定向会修改当前 shell 执行环境中的文件句柄。下列重定向运算符可以出现在简单命令之前或其中任何位置,也可以跟在命令之后。重定向按出现的顺序从左到右处理。

每个前面可以带文件描述符编号的重定向,也可以改为带一个形如 {varname} 的单词。在这种情况下,对于除 >&- 和 <&- 之外的每个重定向运算符,shell 都会分配一个大于或等于 10 的文件描述符,并把它赋给 {varname}。如果 {varname} 位于 >&- 或 <&- 之前,则 varname 的值定义了要关闭的文件描述符。如果提供了 {varname},该重定向在命令作用域之外仍然有效,这使 shell 程序员无需使用 exec 内建命令就能手动管理文件描述符的生命周期。varredir_close shell 选项管理这一行为(参见 4.3.2 节“shopt 内建命令”)。

在下文的描述中,如果省略文件描述符编号,且重定向运算符的第一个字符是 <,则该重定向指标准输入(文件描述符 0)。如果重定向运算符的第一个字符是 >,则该重定向指标准输出(文件描述符 1)。

在下文的描述中,重定向运算符之后的单词(除非另有说明)会经历花括号展开、波浪号展开、参数和变量展开、命令替换、算术展开、引号消除、文件名展开以及单词拆分。如果它展开为多个单词,Bash 会报告一个错误。

重定向的顺序很重要。例如,命令

ls > dirlist 2>&1

把标准输出(文件描述符 1)和标准错误(文件描述符 2)都定向到文件 dirlist,而命令

ls 2>&1 > dirlist

只把标准输出定向到文件 dirlist,因为标准错误在标准输出被重定向到 dirlist 之前就已经成为标准输出的副本。

用于重定向时,Bash 会对若干文件名做特殊处理,如下表所述。如果 Bash 运行所在的操作系统提供这些特殊文件,Bash 就使用它们;否则 Bash 在内部按如下所述的行为模拟它们。

文件名 说明
/dev/fd/fd 如果 fd 是有效整数,则复制文件描述符 fd。
/dev/stdin 复制文件描述符 0。
/dev/stdout 复制文件描述符 1。
/dev/stderr 复制文件描述符 2。
/dev/tcp/host/port 如果 host 是有效主机名或 Internet 地址,且 port 是整数端口号或服务名,Bash 会尝试打开对应的 TCP 套接字。
/dev/udp/host/port 如果 host 是有效主机名或 Internet 地址,且 port 是整数端口号或服务名,Bash 会尝试打开对应的 UDP 套接字。

打开或创建文件失败会导致重定向失败。

使用大于 9 的文件描述符的重定向应谨慎使用,因为它们可能与 shell 内部使用的文件描述符冲突。

3.6.1 重定向输入#

重定向输入会以读方式打开文件名由 word 展开结果决定的文件,用于文件描述符 n,如果未指定 n,则用于标准输入(文件描述符 0)。

重定向输入的一般格式是:

[n]<word

3.6.2 重定向输出#

重定向输出会以写方式打开文件名由 word 展开结果决定的文件,用于文件描述符 n,如果未指定 n,则用于标准输出(文件描述符 1)。如果文件不存在就创建它;如果文件确实存在,则把它截断为零长度。

重定向输出的一般格式是:

[n]>[|]word

如果重定向运算符是 >,且 set 内建命令的 noclobber 选项已启用,那么当文件名由 word 展开结果决定、该文件存在且为常规文件时,重定向失败。如果重定向运算符是 >|,或者重定向运算符是 > 且 set 内建的 noclobber 选项未启用,则即使 word 指定的文件已存在,Bash 也会尝试重定向。

3.6.3 追加重定向输出#

以这种方式重定向输出会以追加方式打开文件名由 word 展开结果决定的文件,用于文件描述符 n,如果未指定 n,则用于标准输出(文件描述符 1)。如果文件不存在就创建它。

追加输出的一般格式是:

[n]>>word

3.6.4 重定向标准输出和标准错误#

该结构把标准输出(文件描述符 1)和标准错误输出(文件描述符 2)都重定向到文件名由 word 展开结果决定的文件。

重定向标准输出和标准错误有两种格式:

&>word

和

>&word

两种形式中,第一种更可取。它在语义上等价于

>word 2>&1

使用第二种形式时,word 不能展开为数字或 -。如果确实如此,出于兼容性原因,会应用其他重定向运算符(参见下文“复制文件描述符”)。

3.6.5 追加标准输出和标准错误#

该结构把标准输出(文件描述符 1)和标准错误输出(文件描述符 2)都追加到文件名由 word 展开结果决定的文件。

追加标准输出和标准错误的格式是:

&>>word

它在语义上等价于

>>word 2>&1

(参见下文“复制文件描述符”)。

3.6.6 Here 文档#

这类重定向指示 shell 从当前源读取输入,直到读到一行只包含 delimiter(且末尾无空白)为止。到那时为止读到的所有行都成为命令的标准输入(如果指定了 n,则为文件描述符 n)。

here 文档的格式是:

[n]<<[-]word
    here-document
delimiter

shell 不对 word 执行参数和变量展开、命令替换、算术展开或文件名展开。

如果 word 的任一部分被引用,则 delimiter 是对 word 进行引号消除后的结果,并且 here 文档中的各行不会被展开。如果 word 未被引用,则 delimiter 就是 word 本身,here 文档文本的处理方式类似于双引号字符串:here 文档的所有行都经历参数展开、命令替换和算术展开,字符序列 \newline 按字面处理,并且必须用 \ 来引用字符 \、$ 和 `;然而双引号字符没有特殊含义。

如果重定向运算符是 <<-,shell 会从输入行以及包含 delimiter 的行中剥去前导制表符。这使得 shell 脚本中的 here 文档可以自然地缩进。

如果 delimiter 未被引用,\<newline> 序列被当作续行处理:两行被连接,反斜杠-换行被移除。这发生在读取 here 文档期间,在检查结束 delimiter 之前,因此连接后的行可以构成结束 delimiter。

3.6.7 Here 字符串#

here 文档的一种变体,格式为:

[n]<<< word

word 经历波浪号展开、参数和变量展开、命令替换、算术展开以及引号消除。不执行文件名展开和单词拆分。结果作为单个字符串(附加一个换行)提供给命令的标准输入(如果指定了 n,则为文件描述符 n)。

3.6.8 复制文件描述符#

重定向运算符

[n]<&word

用于复制输入文件描述符。如果 word 展开为一个或多个数字,则文件描述符 n 成为该文件描述符的副本。如果 word 中的数字未指定一个为输入而打开的文件描述符,则属于重定向错误。如果 word 求值为 -,则关闭文件描述符 n。如果未指定 n,则使用标准输入(文件描述符 0)。

运算符

[n]>&word

以类似方式用于复制输出文件描述符。如果未指定 n,则使用标准输出(文件描述符 1)。如果 word 中的数字未指定一个为输出而打开的文件描述符,则属于重定向错误。如果 word 求值为 -,则关闭文件描述符 n。作为特殊情况,如果省略 n 且 word 未展开为一个或多个数字或 -,则如前述那样重定向标准输出和标准错误。

3.6.9 移动文件描述符#

重定向运算符

[n]<&digit-

把文件描述符 digit 移动到文件描述符 n,如果未指定 n,则移动到标准输入(文件描述符 0)。digit 在被复制到 n 之后关闭。

类似地,重定向运算符

[n]>&digit-

把文件描述符 digit 移动到文件描述符 n,如果未指定 n,则移动到标准输出(文件描述符 1)。

3.6.10 以读写方式打开文件描述符#

重定向运算符

[n]<>word

以既读又写方式打开文件名由 word 展开结果决定的文件,用于文件描述符 n,如果未指定 n,则用于文件描述符 0。如果文件不存在就创建它。

3.7 执行命令#

3.7.1 简单命令展开#

当 shell 执行简单命令时,它按从左到右的顺序执行下列展开、赋值和重定向。

  1. 解析器标记为变量赋值(那些位于命令名之前)的单词和重定向被保存起来供后续处理。
  2. 不是变量赋值或重定向的单词会被展开(参见 3.5 节“shell 展开”)。如果展开后仍有单词,第一个单词就被当作命令名,其余单词作为参数。
  3. 重定向按前述方式执行(参见 3.6 节“重定向”)。
  4. 每个变量赋值中 = 之后的文本在被赋给变量之前经历波浪号展开、参数展开、命令替换、算术展开以及引号消除。

如果没有产生命令名,变量赋值会影响当前 shell 环境。对于这种命令(只由赋值语句和重定向组成的命令),赋值语句在重定向之前执行。否则,变量被加入被执行命令的环境,而不影响当前 shell 环境。如果任一赋值试图给只读变量赋值,则发生错误,命令以非零状态退出。

如果没有产生命令名,则执行重定向,但不影响当前 shell 环境。重定向错误会使命令以非零状态退出。

如果展开之后还剩下命令名,则执行按下文所述继续进行。否则命令退出。如果某个展开包含命令替换,则命令的退出状态是最后执行的命令替换的退出状态。如果没有任何命令替换,命令以零状态退出。

3.7.2 命令查找与执行#

命令被拆分成单词后,如果结果是一个简单命令和可选的参数列表,shell 就执行以下动作。

  1. 如果命令名不包含斜杠,shell 会尝试定位它。如果存在同名 shell 函数,则按照 3.3 节“shell 函数”所述调用该函数。
  2. 如果该名称不匹配任何函数,shell 会在 shell 内建命令列表中搜索它。如果找到匹配项,则调用该内建命令。
  3. 如果该名称既不是 shell 函数也不是内建命令,且不包含斜杠,Bash 会搜索 $PATH 的每个元素,查找包含同名可执行文件的目录。Bash 使用哈希表记住可执行文件的完整路径名,以避免多次搜索 PATH(参见 4.1 节“Bourne shell 内建命令”中对 hash 的描述)。只有当该命令未在哈希表中找到时,Bash 才会对 $PATH 中的目录进行完整搜索。如果搜索不成功,shell 会查找名为 command_not_found_handle 的已定义 shell 函数。如果该函数存在,则在单独的执行环境中调用它,以原始命令和原始命令的参数作为其参数,该函数的退出状态成为该子 shell 的退出状态。如果该函数未定义,shell 会打印一条错误消息并返回退出状态 127。
  4. 如果搜索成功,或者命令名包含一个或多个斜杠,shell 会在单独的执行环境中执行所命名的程序。参数 0 被设为给出的名称,命令的其余参数被设为所提供的参数(如果有的话)。
  5. 如果这次执行失败,原因是文件不是可执行格式,并且该文件不是目录,则假定它是一个 shell 脚本,即一个包含 shell 命令的文件,shell 会按照 3.8 节“shell 脚本”所述执行它。
  6. 如果命令不是以异步方式启动的,shell 会等待命令完成并收集其退出状态。

3.7.3 命令执行环境#

shell 有一个执行环境,它由以下内容组成:

当要执行的不是内建命令或 shell 函数的简单命令时,它会在由以下内容组成的单独执行环境中被调用。除非另有说明,这些值都从 shell 继承。

在这个单独环境中被调用的命令不能影响 shell 的执行环境。

子 shell 是 shell 进程的一个副本。

命令替换、用圆括号分组的命令以及异步命令会在一个子 shell 环境中被调用,该环境是 shell 环境的副本,只是 shell 捕获的陷阱会重置为 shell 在启动时从其父进程继承的值。作为管道一部分而被调用的内建命令(可能除最后一个元素之外,这取决于 lastpipe shell 选项的值;参见 4.3.2 节“shopt 内建命令”)也在子 shell 环境中执行。对子 shell 环境所做的更改不能影响 shell 的执行环境。

当 shell 处于 POSIX 模式时,为执行命令替换而生成的子 shell 会从父 shell 继承 -e 选项的值。当不处于 POSIX 模式时,Bash 会在这类子 shell 中清除 -e 选项。关于在非 POSIX 模式下如何控制这一行为,参见 inherit_errexit shell 选项的说明(参见 4.2 节“Bash 内建命令”)。

如果命令后面跟一个 & 且作业控制未激活,该命令的默认标准输入是空文件 /dev/null。否则,被调用的命令继承调用 shell 的文件描述符,并按重定向做了修改。

3.7.4 环境#

程序被调用时会得到一个称为环境的字符串数组。它是一个名称-值对列表,形式为 name=value。

Bash 提供若干操作环境的方式。启动时,shell 扫描自己的环境,并为找到的每个名称创建一个参数,自动将其标记为导出到子进程。被执行的命令继承该环境。export、declare -x 和 unset 命令通过添加和删除参数与函数来修改环境。如果环境中某个参数的值被修改,新值自动成为环境的一部分,替换旧值。任何被执行的命令所继承的环境由以下内容组成:shell 的初始环境(其值可能在 shell 中被修改),减去被 unset 和 export -n 命令移除的任何对,再加上通过 export 和 declare -x 命令所做的任何添加。

如果任何参数赋值语句(如 3.4 节“shell 参数”所述)出现在简单命令之前,则在其执行期间,这些变量赋值是该命令环境的一部分。这些赋值语句只影响该命令所见的环境。如果这些赋值位于对 shell 函数的调用之前,则变量对该函数是局部的,并被导出到该函数的子进程。

如果设置了 -k 选项(参见 4.3.1 节“set 内建命令”),那么所有参数赋值都会放入命令的环境,而不只是位于命令名之前的那些。

当 Bash 调用外部命令时,变量 $_ 被设为该命令的完整路径名,并在其环境中传给该命令。

3.7.5 退出状态#

被执行命令的退出状态是 waitpid 系统调用或等价函数返回的值。退出状态落在 0 到 255 之间,不过如下文所述,shell 可能特殊使用大于 125 的值。来自 shell 内建命令和复合命令的退出状态也限于这一范围。在某些情况下,shell 会使用特殊值来表示特定的失败模式。

就 shell 的目的而言,以零退出状态退出的命令已经成功。因此,零退出状态表示成功,非零退出状态表示失败。采用这种看似违反直觉的方案,是为了有一种明确定义的方式表示成功,以及多种方式表示各种失败模式。

当命令因编号为 N 的致命信号而终止时,Bash 使用值 128+N 作为退出状态。

如果找不到命令,为执行它而创建的子进程返回状态 127。如果找到命令但该命令不可执行,返回状态为 126。

如果命令因展开或重定向期间的错误而失败,退出状态大于零。

退出状态被 Bash 条件命令(参见 3.2.5.2 节“条件结构”)以及某些列表结构(参见 3.2.4 节“列表”)使用。

所有 Bash 内建命令成功时返回退出状态零,失败时返回非零状态,因此它们可以被条件结构和列表结构使用。所有内建命令都返回退出状态 2 来表示用法不正确,通常指无效选项或缺少参数。

最后一条命令的退出状态可在特殊参数 $? 中获得(参见 3.4.2 节“特殊参数”)。

Bash 本身返回最后执行命令的退出状态,除非发生语法错误,此时它以非零值退出。另见 exit 内建命令(参见 4.1 节“Bourne shell 内建命令”)。

3.7.6 信号#

当 Bash 是交互式时,在没有任何陷阱的情况下,它忽略 SIGTERM(这样 kill 0 不会杀死交互式 shell),并捕获和处理 SIGINT(这样 wait 内建命令可被中断)。当 Bash 收到 SIGINT 时,它会跳出正在执行的任何循环。在所有情况下,Bash 都忽略 SIGQUIT。如果作业控制生效(参见第 7 章“作业控制”),Bash 忽略 SIGTTIN、SIGTTOU 和 SIGTSTP。

trap 内建命令修改 shell 的信号处理,如下文所述(参见 4.1 节“Bourne shell 内建命令”)。

Bash 执行的非内建命令,其信号处理器被设为 shell 从其父进程继承的值,除非 trap 把它们设为忽略,此时子进程也会忽略它们。当作业控制未生效时,异步命令在这些继承的处理器之外还忽略 SIGINT 和 SIGQUIT。作为命令替换结果而运行的命令会忽略键盘生成的作业控制信号 SIGTTIN、SIGTTOU 和 SIGTSTP。

shell 默认在收到 SIGHUP 时退出。退出之前,交互式 shell 会把 SIGHUP 重新发送给所有作业,无论它们正在运行还是已停止。shell 向已停止的作业发送 SIGCONT,以确保它们收到 SIGHUP(关于正在运行和已停止作业的更多信息,参见第 7 章“作业控制”)。要阻止 shell 把 SIGHUP 信号发送给某个特定作业,可用 disown 内建命令把它从作业表中移除(参见 7.2 节“作业控制内建命令”),或者用 disown -h 把它标记为不接收 SIGHUP。

如果用 shopt 设置了 huponexit shell 选项(参见 4.3.2 节“shopt 内建命令”),那么当交互式登录 shell 退出时,Bash 会向所有作业发送 SIGHUP。

如果 Bash 正在等待命令完成,并收到一个已设置陷阱的信号,它不会执行该陷阱,直到命令完成。如果 Bash 正通过 wait 内建命令等待一个异步命令,并收到一个已设置陷阱的信号,wait 内建命令会立即返回一个大于 128 的退出状态,紧接着 shell 执行该陷阱。

当作业控制未启用,且 Bash 正在等待前台命令完成时,shell 会收到键盘生成的信号,例如 SIGINT(通常由 ^C 生成),用户通常意图把这些信号发送给那条命令。出现这种情况是因为 shell 和该命令与终端处于同一个进程组,而 ^C 会把 SIGINT 发送给该进程组中的所有进程。由于当 shell 不是交互式时 Bash 默认不启用作作业控制,这种情形在非交互式 shell 中最常见。

当作业控制已启用,且 Bash 正在等待前台命令完成时,shell 不会收到键盘生成的信号,因为它与终端不在同一个进程组。这种情形在交互式 shell 中最常见,Bash 在其中默认尝试启用作作业控制。关于进程组更深入的讨论,参见第 7 章“作业控制”。

当作业控制未启用,且 Bash 在等待前台命令时收到 SIGINT,它会等到该前台命令终止,然后决定如何处理 SIGINT:

  1. 如果命令因 SIGINT 而终止,Bash 推断用户也意图把 SIGINT 发送给 shell,并对 SIGINT 采取行动(例如,运行 SIGINT 陷阱、退出非交互式 shell,或返回到顶层读取新命令)。
  2. 如果命令并非因 SIGINT 而终止,则程序自己处理了 SIGINT,没有把它当作致命信号。这种情况下,Bash 也不把 SIGINT 当作致命信号,而是假定 SIGINT 被用作程序正常运行的一部分(例如 emacs 用它中止编辑命令),或者被有意丢弃。不过,出于兼容性,Bash 会运行设置在 SIGINT 上的任何陷阱,就像它在等待前台命令完成期间收到其他被捕获信号时那样。

当作业控制已启用时,Bash 在等待前台命令期间不会收到诸如 SIGINT 之类的键盘生成信号。交互式 shell 不会理会 SIGINT,即使前台命令因此终止也是如此,只会记录其退出状态。如果 shell 不是交互式的,且前台命令因 SIGINT 而终止,则出于兼容性,Bash 假装自己收到了 SIGINT(上述情形 1)。

3.8 shell 脚本#

shell 脚本是包含 shell 命令的文本文件。当这样的文件作为调用 Bash 时的第一个非选项参数使用,且既未提供 -c 也未提供 -s 选项时(参见 6.1 节“调用 Bash”),Bash 会从该文件读取并执行命令,然后退出。这种操作模式会创建一个非交互式 shell。如果文件名不包含任何斜杠,shell 会先在当前目录中查找该文件,如果在那里找不到,再在 PATH 中的目录里查找。

Bash 会尝试判断该文件是文本文件还是二进制文件,并且不会执行它判定为二进制的文件。

当 Bash 运行 shell 脚本时,它把特殊参数 $0 设为该文件的名称,而不是 shell 的名称,并把位置参数设为其余参数(如果给出了的话)。如果没有提供额外的参数,则位置参数未设置。

可以通过使用 chmod 命令打开执行位来把 shell 脚本变为可执行。当 Bash 在 $PATH 中搜索命令时找到这样的文件,它会创建一个自身的新实例来执行它。换言之,如果 filename 是可执行的 shell 脚本,执行

filename arguments

等价于执行

bash filename arguments

这个子 shell 会重新初始化自身,因此效果就像调用了一个新 shell 来解释该脚本,例外是父进程记住的命令位置(参见 4.1 节“Bourne shell 内建命令”中对 hash 的描述)被子进程保留。

GNU 操作系统和大多数 Unix 版本把这一点作为操作系统命令执行机制的一部分。如果脚本的第一行以两个字符 #! 开头,该行的其余部分指定该程序的解释器,并且根据操作系统的不同,还可能指定该解释器的一个或多个可选参数。因此,你可以指定 Bash、awk、Perl 或其他解释器,并用那种语言编写脚本文件的其余部分。

解释器的参数由以下内容组成:脚本文件第一行中解释器名之后的一个或多个可选参数,接着是脚本文件的名称,再接着是提供给脚本的其余参数。解释器行如何拆分为解释器名和一组参数,其细节因系统而异。Bash 会在不自行处理这一点的操作系统上执行这个动作。注意,某些较老版本的 Unix 把解释器名和一个参数限制为最多 32 个字符,因此假定使用多个参数可行是不具可移植性的。

Bash 脚本常常以 #!/bin/bash 开头(假定 Bash 已安装在 /bin 中),因为这样可以确保即使脚本在另一个 shell 下执行,也会用 Bash 来解释它。一个常见的惯用法是使用 env 来查找 bash,即使它安装在别的目录中:#!/usr/bin/env bash 会在 $PATH 中找到第一个出现的 bash。

4 Shell 内建命令#

内建命令(builtin command)包含在 shell 自身之中。当把一个内建命令的名称用作简单命令的第一个词时(参见 3.2.2 节“简单命令”),shell 会直接执行该命令,而不调用其他程序。要实现那些用独立工具无法实现或不便于实现的功能,就需要内建命令。

本节简要描述 Bash 从 Bourne Shell 继承来的内建命令,以及 Bash 独有的或被 Bash 扩展过的内建命令。

若干内建命令在其他章节中描述:为作业控制设施提供 Bash 接口的内建命令(参见 7.2 节“作业控制内建命令”)、目录栈(参见 6.8.1 节“目录栈内建命令”)、命令历史(参见 9.2 节“Bash 历史内建命令”),以及可编程补全设施(参见 8.7 节“可编程补全内建命令”)。

许多内建命令已被 POSIX 或 Bash 扩展。

除非另有说明,每个被记为可接受以 '-' 开头的选项的内建命令都接受 '-' 来表示选项结束。:、true、false 和 test/[ 这些内建命令不接受选项,也不会对 '-' 作特殊处理。exit、logout、return、break、continue、let 和 shift 这些内建命令接受并处理以 '-' 开头的参数,而无需用 '-' 表示选项结束。其他接受参数但未规定接受选项的内建命令,会把以 '-' 开头的参数视为无效选项,并要求用 '-' 来阻止这种解释。

4.1 Bourne Shell 内建命令#

以下 shell 内建命令继承自 Bourne Shell。这些命令按 POSIX 标准的规定实现。

:#

: [arguments]

除了展开 arguments 并执行重定向外不做任何事。返回状态为零。

.#

. [-p path] filename [arguments]

. 命令在当前 shell 上下文中读取并执行 filename 参数中的命令。

如果 filename 不含斜杠,. 会搜索它。如果提供了 -p,. 会把 path 当作以冒号分隔的目录列表,在其中查找 filename;否则,. 使用 PATH 中的目录来查找 filename。filename 不需要是可执行的。当 Bash 不处于 POSIX 模式时,如果在 $PATH 中找不到 filename,它会搜索当前目录;但如果提供了 -p,则不搜索当前目录。如果 sourcepath 选项(参见 4.3.2 节“Shopt 内建命令”)被关闭,. 不搜索 PATH。

如果提供了任何参数,它们会在执行 filename 时成为位置参数。否则位置参数保持不变。

如果启用了 -T 选项,. 会继承 DEBUG 上的任何 trap;如果未启用,则在调用 . 前后保存并恢复任何 DEBUG trap 字符串,且 . 在执行期间会取消 DEBUG trap。如果未设置 -T,并且被 source 的文件更改了 DEBUG trap,则新值在 . 完成后仍然保留。返回状态是执行 filename 中最后一条命令的退出状态;如果没有执行任何命令则为零。如果找不到 filename,或无法读取它,返回状态为非零。该内建命令等价于 source。

break#

break [n]

退出 for、while、until 或 select 循环。如果提供了 n,break 会退出第 n 层外层循环。n 必须大于等于 1。除非 n 不大于等于 1,否则返回状态为零。

cd#

cd [-L] [-@] [directory]
cd -P [-e] [-@] [directory]

将当前工作目录更改为 directory。如果未提供 directory,则使用 HOME shell 变量的值作为 directory。如果存在 shell 变量 CDPATH,且 directory 不以斜杠开头,cd 会把它用作搜索路径:cd 在 CDPATH 的每个目录名中查找 directory,CDPATH 中的备选目录名以冒号(':')分隔。CDPATH 中的空目录名表示当前目录。

-P 选项表示不跟随符号链接:在 cd 遍历 directory 时,以及在处理 directory 中某个 .. 实例之前,会先解析符号链接。

默认情况下,或当提供 -L 选项时,directory 中的符号链接在 cd 处理完 directory 中的某个 .. 实例之后才被解析。

如果 . . . 出现在 directory 中,cd 会这样处理它:删去紧邻其前的那个路径名分量,回溯到某个斜杠或 directory 的开头,并验证它到这一点为止已处理的那部分 directory 在删去该路径名分量后是否仍是有效的目录名。如果不是有效的目录名,cd 返回非零状态。

如果 -e 选项与 -P 一起提供,并且 cd 在成功更改目录后无法成功确定当前工作目录,则返回非零状态。

在支持该选项的系统上,-@ 选项会把与文件关联的扩展属性呈现为一个目录。

如果 directory 是 '-',则在尝试更改目录之前会把它转换为 $OLDPWD。

如果 cd 使用了来自 CDPATH 的非空目录名,或者 '-' 是第一个参数,并且目录更改成功,cd 会把新工作目录的绝对路径名写入标准输出。

如果目录更改成功,cd 会把 PWD 环境变量的值设为新目录名,并把 OLDPWD 环境变量设为更改前当前工作目录的值。

如果目录成功更改则返回状态为零,否则为非零。

continue#

continue [n]

continue 恢复外层 for、while、until 或 select 循环的下一次迭代。如果提供了 n,Bash 会恢复第 n 层外层循环的执行。n 必须大于等于 1。除非 n 不大于等于 1,否则返回状态为零。

eval#

eval [arguments]

这些 arguments 会以空格分隔连接成一条单独的命令。Bash 随后读取并执行这条命令,并返回其退出状态作为 eval 的退出状态。如果没有参数或只有空参数,返回状态为零。

exec#

exec [-cl] [-a name] [command [arguments]]

如果提供了 command,它会替换 shell 而不创建新进程。command 不能是 shell 内建命令或函数。这些参数会成为 command 的参数。如果提供了 -l 选项,shell 会在传给 command 的第零个参数开头放置一个短横线。这正是 login 程序所做的。-c 选项使 command 在空环境中执行。如果提供了 -a,shell 会把 name 作为第零个参数传给 command。

如果由于某种原因无法执行 command,非交互式 shell 会退出,除非启用了 execfail shell 选项;在这种情况下,它返回非零状态。交互式 shell 在文件无法执行时返回非零状态。如果 exec 失败,子 shell 无条件退出。

如果未指定 command,可以使用重定向来影响当前 shell 环境。如果没有重定向错误,返回状态为零;否则返回状态为非零。

exit#

exit [n]

退出 shell,向 shell 的父进程返回状态 n。如果省略 n,退出状态为最后执行的那条命令的退出状态。在 shell 终止之前会执行 EXIT 上的任何 trap。

export#

export [-fn] [-p] [name[=value]]

将每个 name 标记为要在环境中传给随后执行的命令。如果提供了 -f 选项,这些 name 指的是 shell 函数;否则这些 name 指的是 shell 变量。

-n 选项表示取消导出每个 name:不再把它标记为导出。如果没有提供 name,或只给出了 -p 选项,export 会在标准输出上显示所有已导出变量名称的列表。同时使用 -p 和 -f 会显示已导出的函数。-p 选项以可重新用作输入的形式显示输出。

export 允许在导出或取消导出变量的同时为其设置值,方法是在变量名后紧跟 =value。这会在修改导出属性的同时把变量的值设为 value。

除非提供了无效选项、某个 name 不是有效的 shell 变量名、或 -f 与一个并非 shell 函数的 name 一起提供,否则返回状态为零。

false#

false

不做任何事;返回非零状态。

getopts#

getopts optstring name [arg ...]

getopts 由 shell 脚本或函数用来解析位置参数并获取选项及其参数。optstring 包含要识别的选项字符;如果某个字符后面跟着冒号,则该选项预期带有一个参数,该参数应与选项之间用空白分隔。冒号(':')和问号('?')不能用作选项字符。

每次调用时,getopts 会把下一个选项放入 shell 变量 name(如果 name 不存在则初始化它),并把要处理的下一个参数的索引放入变量 OPTIND。每次调用 shell 或 shell 脚本时,OPTIND 都初始化为 1。当某个选项需要参数时,getopts 会把该参数放入变量 OPTARG。

shell 不会自动重置 OPTIND;在同一次 shell 调用中多次调用 getopts 以使用新的一组参数时,必须手动重置它。

当到达选项结尾时,getopts 以大于零的返回值退出。OPTIND 被设为第一个非选项参数的索引,name 被设为 '?'。

getopts 通常解析位置参数,但如果提供了更多参数作为 arg 值,getopts 会改为解析这些参数。

getopts 可以用两种方式报告错误。如果 optstring 的第一个字符是冒号,getopts 使用静默错误报告。在正常操作下,getopts 在遇到无效选项或缺少选项参数时会打印诊断消息。如果变量 OPTERR 被设为 0,则即使 optstring 的第一个字符不是冒号,getopts 也不显示任何错误消息。

如果 getopts 检测到无效选项,它会把 '?' 放入 name,并且如果不是静默模式,还会打印一条错误消息并取消设置 OPTARG。如果 getopts 是静默的,它会把找到的选项字符赋给 OPTARG,且不打印诊断消息。

如果找不到必需的参数,并且 getopts 不是静默的,它会把 name 的值设为问号('?'),取消设置 OPTARG,并打印一条诊断消息。如果 getopts 是静默的,它会把 name 的值设为冒号(':'),并把 OPTARG 设为找到的选项字符。

getopts 如果找到(指定或未指定的)某个选项则返回 true。当遇到选项结尾或发生错误时,它返回 false。

hash#

hash [-r] [-p filename] [-dt] [name]

每次调用 hash 时,它会记住作为 name 参数指定的命令的完整文件名,以便后续调用时无需再搜索它们。这些命令是通过搜索 $PATH 中列出的目录找到的。与 name 关联的任何先前记住的文件名都会被丢弃。-p 选项阻止路径搜索,hash 使用 filename 作为 name 的位置。

-r 选项使 shell 忘记所有记住的位置。对 PATH 变量赋值也会清除所有已散列的文件名。-d 选项使 shell 忘记每个 name 记住的位置。

如果提供了 -t 选项,hash 会打印与每个 name 对应的完整路径名。如果在使用 -t 时提供了多个 name 参数,hash 会在对应的已散列完整路径之前打印每个 name。-p 选项以可重新用作输入的形式显示输出。

如果没有给出参数,或只提供了 -p,hash 会打印关于已记住命令的信息。-t、-d 和 -p 选项(这些作用于 name 参数的选项)互斥,只有其中一个会生效。如果提供了多个,-t 的优先级高于 -p,而两者都高于 -d。

除非找不到某个 name 或提供了无效选项,否则返回状态为零。

pwd#

pwd [-LP]

打印当前工作目录的绝对路径名。如果提供了 -P 选项,或启用了 set 内建命令的 -o physical 选项(参见 4.3.1 节“Set 内建命令”),打印的路径名将不包含符号链接。如果提供了 -L 选项,打印的路径名可能包含符号链接。除非在确定当前目录名称时遇到错误或提供了无效选项,否则返回状态为零。

readonly#

readonly [-aAf] [-p] [name[=value]] ...

将每个 name 标记为只读。这些 name 的值不能被后续的赋值或 unset 更改。如果提供了 -f 选项,每个 name 指代一个 shell 函数。-a 选项表示每个 name 指代一个索引数组变量;-A 选项表示每个 name 指代一个关联数组变量。如果同时提供这两个选项,-A 优先。如果没有提供 name 参数,或提供了 -p 选项,则打印所有只读名称的列表。其他选项可用于把输出限制为只读名称集合的某个子集。-p 选项以可重新用作输入的形式显示输出。

readonly 允许在更改只读属性的同时为变量设置值,方法是在变量名后紧跟 =value。这会在修改只读属性的同时把变量的值设为 value。

除非提供了无效选项、某个 name 参数不是有效的 shell 变量名或函数名、或 -f 与一个并非 shell 函数的 name 一起提供,否则返回状态为零。

return#

return [n]

停止执行 shell 函数或已 source 的文件,并向其调用者返回值 n。如果未提供 n,返回值为最后执行的那条命令的退出状态。如果 return 由 trap 处理程序执行,用于确定状态的最后一条命令是 trap 处理程序之前执行的那条命令。如果 return 在 DEBUG trap 期间执行,用于确定状态的最后一条命令是 trap 处理程序在调用 return 之前执行的那条命令。

当用 return 终止用 .(source)内建命令执行的脚本时,它会把 n 或脚本内最后执行的那条命令的退出状态作为该脚本的退出状态返回。如果提供了 n,返回值是它的最低有效 8 位。

与 RETURN trap 关联的任何命令,都会在函数或脚本之后的执行恢复之前执行。

如果 return 被传入非数字参数,或者在函数之外且不在由 . 或 source 执行脚本期间使用,则返回状态为非零。

shift#

shift [n]

把位置参数向左移动 n 位:从 $n+1 到 $# 的位置参数被重命名为 1 到 $#-n。由编号 $# 向下到 $#-n+1 表示的那些参数被取消设置。n 必须是一个小于等于 $# 的非负数。如果未提供 n,则假定为 1。如果 n 为零或大于 $#,位置参数不变。如果 n 大于 $# 或小于零,则返回状态为非零,否则返回状态为零。

test#

test expr

求一个条件表达式 expr,并返回状态 0(真)或 1(假)。每个运算符和操作数都必须是单独的参数。表达式由下面 6.4 节“Bash 条件表达式”中描述的基本项(primary)组成。test 不接受任何选项,也不接受并忽略 -- 参数来表示选项结束。在使用 [ 形式时,命令的最后一个参数必须是 ]。

表达式可以使用以下运算符组合,按优先级从高到低排列。求值取决于参数的数量;见下文。当有五个或更多参数时,test 使用运算符优先级。

! expr 如果 expr 为假则为真。

(expr) 返回 expr 的值。可用于覆盖正常的运算符优先级。

expr1 -a expr2 如果 expr1 和 expr2 都为真则为真。

expr1 -o expr2 如果 expr1 或 expr2 其中之一为真则为真。

test 和 [ 内建命令根据参数数量使用一组规则来求值条件表达式。

0 个参数 表达式为假。

1 个参数 当且仅当该参数不为空时,表达式为真。

2 个参数 如果第一个参数是 '!',则当且仅当第二个参数为空时表达式为真。如果第一个参数是一元条件运算符之一(参见 6.4 节“Bash 条件表达式”),则当该一元测试为真时表达式为真。如果第一个参数不是有效的一元运算符,则表达式为假。

3 个参数 按下列顺序应用以下条件。

  1. 如果第二个参数是二元条件运算符之一(参见 6.4 节“Bash 条件表达式”),则表达式的结果是使用第一个和第三个参数作为操作数的二元测试的结果。当有三个参数时,-a 和 -o 运算符被视为二元运算符。
  2. 如果第一个参数是 '!',则其值是对第二和第三个参数进行二参数测试所得的否定。
  3. 如果第一个参数恰好是 '(',且第三个参数恰好是 ')',则结果是对第二个参数进行一参数测试。
  4. 否则,表达式为假。

4 个参数 按下列顺序应用以下条件。

  1. 如果第一个参数是 '!',则结果是对其余参数构成的三参数表达式的否定。
  2. 如果第一个参数恰好是 '(',且第四个参数恰好是 ')',则结果是对第二和第三个参数进行二参数测试。
  3. 否则,表达式按上述规则使用优先级进行解析和求值。

5 个或更多参数 表达式按上述规则使用优先级进行解析和求值。

如果 shell 处于 POSIX 模式,或者表达式是 [[ 命令的一部分,则 '<' 和 '>' 运算符使用当前 locale 进行排序。如果 shell 不处于 POSIX 模式,则 test 和 [ 命令使用 ASCII 顺序按字典序排序。对 4 个或更多参数进行历史上那种运算符优先级解析,在遇到看起来像基本项的字符串时可能导致歧义。POSIX 标准已弃用 -a 和 -o 基本项以及在括号内嵌套表达式。脚本不应再使用它们。把 test 的调用限制为单个基本项,并把 -a 和 -o 的使用替换为 shell 的 && 和 || 列表运算符,要可靠得多。例如,使用

test -n string1 && test -n string2

而不是

test -n string1 -a -n string2

times#

times

打印出 shell 及其子进程使用的用户时间和系统时间。返回状态为零。

trap#

trap [-lpP] [action] [sigspec ...]

action 是一条命令,当 shell 收到 sigspec 中的任何信号时会被读取并执行。如果 action 不存在(且只有一个 sigspec)或等于 '-',则每个指定 sigspec 的处置会被重置为 shell 启动时所具有的值。如果 action 是空字符串,则由每个 sigspec 指定的信号会被 shell 及其调用的命令忽略。

如果没有提供参数,trap 会把与每个被 trap 的信号关联的动作打印为一组 trap 命令,这些命令可以重新用作 shell 输入,以恢复当前的信号处置。

如果 action 不存在且提供了 -p,trap 会把与每个 sigspec 关联的 trap 命令显示为一组 trap 命令(如果没有提供 sigspec,则为所有被 trap 的信号),这些命令可以重新用作 shell 输入,以恢复当前的信号处置。-P 选项行为类似,但只显示与每个 sigspec 参数关联的动作。-P 要求至少有一个 sigspec 参数。-P 或 -p 选项可用于子 shell 环境中(例如命令替换),并且只要在 trap 被用于更改信号处理之前使用,就会显示其父进程 trap 的状态。

-l 选项打印信号名称及其对应编号的列表。每个 sigspec 要么是信号名称,要么是信号编号。信号名称不区分大小写,且 SIG 前缀是可选的。如果提供 -l 但没有 sigspec 参数,它会打印有效信号名称的列表。

如果某个 sigspec 是 0 或 EXIT,则在 shell 退出时执行 action。如果某个 sigspec 是 DEBUG,则在每条简单命令之前、for 命令、case 命令、select 命令、(( 算术命令、[[ 条件命令、算术 for 命令之前,以及在 shell 函数中第一条命令执行之前,执行 action。关于其对 DEBUG trap 影响的细节,参见 extdebug shell 选项的描述(参见 4.3.2 节“Shopt 内建命令”)。如果某个 sigspec 是 RETURN,则每当 shell 函数或用 . 或 source 内建命令执行的脚本执行完毕时,执行 action。

如果某个 sigspec 是 ERR,则每当一个管道(可能由单条简单命令组成)、一个列表或一个复合命令返回非零退出状态时,执行 action,但须满足以下条件。如果失败的命令是紧随 until 或 while 保留字之后的命令列表的一部分、是 if 或 elif 保留字之后的测试的一部分、是在 && 或 || 列表中执行的命令的一部分(除最后一个 && 或 || 之后的命令外)、是管道中除最后一条之外的其他任何命令(受 pipefail shell 选项状态影响)、或者该命令的返回状态正用 ! 取反,则不执行 ERR trap。这些与 errexit(-e)选项所遵守的条件相同。

当 shell 不是交互式时,进入非交互式 shell 时被忽略的信号不能被 trap 或重置。交互式 shell 允许 trap 进入时被忽略的信号。被 trap 且未被忽略的信号,在创建子 shell 或子 shell 环境时会被重置为其原始值。

除非某个 sigspec 未指定有效信号(此时返回状态为非零),否则返回状态为零。

true#

true

不做任何事,返回状态 0。

umask#

umask [-p] [-S] [mode]

把 shell 进程的文件创建掩码设为 mode。如果 mode 以数字开头,它被解释为八进制数;如果不是,它被解释为类似 chmod 命令所接受的符号模式掩码。如果省略 mode,umask 打印掩码的当前值。如果提供 -S 选项而不带 mode 参数,umask 以符号格式打印掩码;默认输出是八进制数。如果提供了 -p 选项,且省略了 mode,输出的形式可以重新用作输入。如果成功更改了 mode,或未提供 mode 参数,则返回状态为零,否则为非零。

注意,当 mode 被解释为八进制数时,umask 的每一位都要从 7 中减去。因此,umask 为 022 会得到权限 755。

unset#

unset [-fnv] [name]

移除每个变量或函数 name。如果给出了 -v 选项,每个 name 指代一个 shell 变量,该变量会被移除。如果给出了 -f 选项,这些 name 指代 shell 函数,函数定义会被移除。如果提供了 -n 选项,并且 name 是一个带有 nameref 属性的变量,则取消设置的是 name 本身,而不是它所引用的变量。如果提供了 -f 选项,-n 不起作用。如果未提供任何选项,每个 name 指代一个变量;如果没有该名称的变量,则取消设置同名的函数(如果有的话)。只读变量和函数不能被 unset。当变量或函数被移除时,它们也会从传给后续命令的环境中移除。某些 shell 变量可能不能被 unset。某些 shell 变量在 unset 后会失去其特殊行为;此类行为在各个变量的描述中注明。除非某个 name 是只读的或不能被 unset,否则返回状态为零。

4.2 Bash 内建命令#

本节描述 Bash 独有或被 Bash 扩展的内建命令。其中一些命令在 POSIX 标准中有规定。

alias#

alias [-p] [name[=value] ...]

不带参数或带 -p 选项时,alias 会在标准输出上以可重新用作输入的形式打印别名列表。如果提供了参数,则为每个给出了 value 的 name 定义一个别名。如果没有给出 value,则打印别名 name 的名称和值。value 末尾的一个空格会使得在命令解析期间展开该别名时,检查下一个词是否可作别名替换。除非给定了某个 name(没有对应的 =value)而该 name 尚未定义别名,否则 alias 返回 true。别名在 6.6 节“别名”中描述。

bind#

bind [-m keymap] [-lsvSVX]
bind [-m keymap] [-q function] [-u function] [-r keyseq]
bind [-m keymap] -f filename
bind [-m keymap] -x keyseq[: ]shell-command
bind [-m keymap] keyseq:function-name
bind [-m keymap] keyseq:readline-command
bind [-m keymap] -p|-P [readline-command]
bind readline-command-line

显示当前 Readline(参见第 8 章“命令行编辑”)的键与函数绑定,把某个键序列绑定到 Readline 函数或宏或某个 shell 命令,或者设置一个 Readline 变量。每个非选项参数都是一条键绑定或命令,其形式与它出现在 Readline 初始化文件中时一样(参见 8.3 节“Readline 初始化文件”),但每条绑定或命令都必须作为单独的参数传入;例如,"\C-x\C-r":re-read-init-file。

在以下描述中,那些以可重新读取的形式显示输出的选项,会把输出格式化为命令,这些命令会出现在 Readline 初始化文件中,或会作为单独的参数提供给 bind 命令。

选项(如果提供)具有以下含义:

-m keymap 把 keymap 用作受后续绑定影响的键映射。可接受的键映射名称有 emacs、emacs-standard、emacs-meta、emacs-ctlx、vi、vi-move、vi-command 和 vi-insert。vi 等价于 vi-command(vi-move 也是其同义词);emacs 等价于 emacs-standard。

-l 列出所有 Readline 函数的名称。

-p 以可用作后续 bind 命令的参数或用于 Readline 初始化文件的方式显示 Readline 函数名和绑定。如果选项处理之后还有参数,bind 会把它们视为 readline 命令名,并把输出限制为这些名称。

-P 列出当前 Readline 函数名和绑定。如果选项处理之后还有参数,bind 会把它们视为 readline 命令名,并把输出限制为这些名称。

-s 以可用作后续 bind 命令的参数或用于 Readline 初始化文件的方式,显示绑定到宏的 Readline 键序列及其输出的字符串。

-S 显示绑定到宏的 Readline 键序列及其输出的字符串。

-v 以可用作后续 bind 命令的参数或用于 Readline 初始化文件的方式显示 Readline 变量名和值。

-V 列出当前 Readline 变量名和值。

-f filename 从 filename 读取键绑定。

-q function 显示调用指定 Readline 函数的键序列。

-u function 解除绑定到指定 Readline 函数的所有键序列。

-r keyseq 移除 keyseq 的任何当前绑定。

-x keyseq:shell-command 每当输入 keyseq 时使 shell-command 被执行。keyseq 与 shell-command 之间的分隔符要么是空白,要么是一个冒号(后面可选地跟空白)。如果分隔符是空白,shell-command 必须用双引号括起来。Readline 在保存 shell-command 之前会展开它的任何特殊反斜杠转义。如果分隔符是冒号,则任何括起来的双引号都是可选的,且 Readline 在保存命令字符串之前不会展开它。由于整个键绑定表达式必须是单个参数,它应当用单引号括起来。当执行 shell-command 时,shell 会把 READLINE_LINE 变量设为 Readline 行缓冲区的内容,并分别把 READLINE_POINT 和 READLINE_MARK 变量设为插入点的当前位置和保存的插入点(标记)。shell 会把用户提供的任何数字参数赋给 READLINE_ARGUMENT 变量。如果没有参数,则不设置该变量。如果执行的那条命令更改了 READLINE_LINE、READLINE_POINT 或 READLINE_MARK 中任何一个的值,那些新值将反映在编辑状态中。

-X 以可用作后续 bind 命令的参数的形式,列出绑定到 shell 命令的所有键序列及关联的命令。

除非提供了无效选项或发生错误,否则返回状态为零。

builtin#

builtin [shell-builtin [args]]

执行指定的 shell 内建命令 shell-builtin,把 args 传给它,并返回其退出状态。当定义一个与某个 shell 内建命令同名的 shell 函数、又想在函数内保留该内建命令的功能时,这很有用。如果 shell-builtin 不是 shell 内建命令,返回状态为非零。

caller#

caller [expr]

返回任何活动子例程调用(一个 shell 函数,或用 . 或 source 内建命令执行的脚本)的上下文。

不带 expr 时,caller 显示当前子例程调用的行号和源文件名。如果提供了一个非负整数作为 expr,caller 显示当前执行调用栈中与该位置对应的行号、子例程名和源文件。这些额外信息可用于例如打印堆栈回溯。当前帧是第 0 帧。

除非 shell 没有在执行子例程调用,或 expr 不对应调用栈中的有效位置,否则返回值为 0。

command#

command [-pVv] command [arguments ...]

command 内建命令运行 command 并带上 arguments,忽略任何名为 command 的 shell 函数。只有 shell 内建命令或通过搜索 PATH 找到的命令才会被执行。如果有一个名为 ls 的 shell 函数,在该函数内运行 command ls 会执行外部命令 ls,而不是递归调用该函数。-p 选项表示使用 PATH 的一个默认值,它保证能找到所有标准工具。这种情况下,如果找不到 command 或发生错误,返回状态为 127,否则为 command 的退出状态。

如果提供了 -V 或 -v 选项,command 会打印对 command 的描述。-v 选项显示一个单词,指明用于调用 command 的命令或文件名;-V 选项产生更详细的描述。这种情况下,如果找到 command 则返回状态为零,否则为非零。

declare#

declare [-aAfFgiIlnrtux] [-p] [name[=value] ...]

声明变量并赋予它们属性。如果没有给出 name,则改为显示变量或 shell 函数的值。

-p 选项会显示每个 name 的属性和值。当 -p 与 name 参数一起使用时,除 -f 和 -F 之外的其他附加选项都会被忽略。

当提供 -p 而不带 name 参数时,declare 会显示所有具有附加选项所指定属性的变量的属性和值。如果 -p 没有附带其他选项,declare 会显示所有 shell 变量的属性和值。-f 选项把显示限制为 shell 函数。

-F 选项阻止显示函数定义;只打印函数名和属性。如果使用 shopt 启用了 extdebug shell 选项(参见 4.3.2 节“Shopt 内建命令”),还会显示定义每个 name 的源文件名和行号。-F 隐含 -f。

-g 选项强制变量在全局作用域中创建或修改,即使 declare 在 shell 函数中执行也是如此。当 declare 不在 shell 函数中执行时它被忽略。

-I 选项使局部变量继承外围作用域中任何同名现有变量的属性(nameref 属性除外)和值。如果没有现有变量,则局部变量初始时处于未设置状态。

以下选项可用于把输出限制为具有指定属性的变量,或给变量赋予属性:

-a 每个 name 都是一个索引数组变量(参见 6.7 节“数组”)。

-A 每个 name 都是一个关联数组变量(参见 6.7 节“数组”)。

-f 每个 name 指代一个 shell 函数。

-i 该变量被当作整数处理;给变量赋值时执行算术求值(参见 6.5 节“shell 算术”)。

-l 给变量赋值时,所有大写字符被转换为小写。大写属性被禁用。

-n 给每个 name 赋予 nameref 属性,使其成为对另一个变量的名称引用。那个其他变量由 name 的值定义。对 name 的所有引用、赋值和属性修改(使用或更改 -n 属性本身的除外)都作用于 name 的值所引用的变量。nameref 属性不能应用于数组变量。

-r 使 name 成为只读。这些 name 随后不能被后续的赋值语句或 unset 赋予值。

-t 给每个 name 赋予 trace 属性。被 trace 的函数从调用 shell 继承 DEBUG 和 RETURN trap。trace 属性对变量没有特殊含义。

-u 给变量赋值时,所有小写字符被转换为大写。小写属性被禁用。

-x 把每个 name 标记为通过环境导出给后续命令。

使用 '+' 而非 '-' 则会改为关闭指定的属性,例外情况是 '+a' 和 '+A' 不能用于销毁数组变量,且 '+r' 不会移除 readonly 属性。

在函数中使用时,declare 会把每个 name 变为局部变量,如同 local 命令一样,除非提供了 -g 选项。如果变量名后紧跟 =value,则变量的值被设为 value。

当使用 -a 或 -A 以及复合赋值语法来创建数组变量时,其他属性要到后续赋值时才生效。

除非遇到无效选项、试图用 -f foo=bar 定义函数、试图给只读变量赋值、试图不使用复合赋值语法就给数组变量赋值(参见 6.7 节“数组”)、某个 name 不是有效的 shell 变量名、试图关闭只读变量的只读状态、试图关闭数组变量的数组状态、或试图用 -f 显示不存在的函数,否则返回状态为零。

echo#

echo [-neE] [arg ...]

输出 args,以空格分隔,并以换行结尾。除非发生写错误,否则返回状态为 0。如果指定了 -n,则不打印结尾换行。

如果给出了 -e 选项,echo 会解释以下反斜杠转义字符。-E 选项禁用对这些转义字符的解释,即使在默认会解释它们的系统上也是如此。xpg_echo shell 选项决定 echo 是否解释任何选项并展开这些转义字符。echo 不会把 -- 解释为选项结束。

echo 解释以下转义序列:

echo 会把任何无法识别的反斜杠转义字符原样写出。

enable#

enable [-a] [-dnps] [-f filename] [name ...]

启用和禁用 shell 内建命令。禁用一个内建命令后,就可以在不指定完整路径名的情况下执行一个与该 shell 内建命令同名的可执行文件,即使 shell 通常会在文件之前搜索内建命令。

如果提供了 -n,这些 name 被禁用。否则 name 被启用。例如,要使用通过 $PATH 找到的 test 二进制程序而不是 shell 内建版本,可输入 enable -n test。

如果提供了 -p 选项,或没有提供 name 参数,则打印 shell 内建命令的列表。在没有其他参数时,该列表由所有已启用的 shell 内建命令组成。-n 选项表示只打印已禁用的内建命令。-a 选项表示列出每个内建命令并标明其是否已启用。-s 选项表示把 enable 限制为 POSIX 特殊内建命令。

-f 选项表示在支持动态加载的系统上从共享对象 filename 加载新的内建命令 name。如果 filename 不含斜杠,Bash 会把 BASH_LOADABLES_PATH 变量的值用作以冒号分隔的目录列表,在其中搜索 filename。BASH_LOADABLES_PATH 的默认值取决于系统,并且可能包含 "." 以强制搜索当前目录。-d 选项会删除用 -f 加载的内建命令。如果 -s 与 -f 一起使用,新的内建命令会成为 POSIX 特殊内建命令(参见 4.4 节“特殊内建命令”)。

如果未提供任何选项,且某个 name 不是 shell 内建命令,enable 会尝试从一个名为 name 的共享对象加载 name,就好像该命令是 enable -f name name 一样。

除非某个 name 不是 shell 内建命令,或从共享对象加载新内建命令时出错,否则返回状态为零。

help#

help [-dms] [pattern]

显示关于内建命令的有用信息。如果指定了 pattern,help 会按以下所述对匹配 pattern 的所有命令给出详细帮助;否则它显示所有内建命令和 shell 复合命令的列表。

选项(如果提供)具有以下含义:

-d 显示每个 pattern 的简短描述

-m 以类似手册页的格式显示每个 pattern 的描述

-s 只显示每个 pattern 的简短用法摘要

如果不是上述情况,且 pattern 恰好匹配某个帮助主题的名称,help 会打印与该主题关联的描述。否则,help 执行前缀匹配并打印所有匹配帮助主题的描述。

如果 pattern 包含模式匹配字符(参见 3.5.8.1 节“模式匹配”),它会被当作 shell 模式,help 打印与 pattern 匹配的每个帮助主题的描述。

除非没有命令匹配 pattern,否则返回状态为零。

let#

let expression [expression ...]

let 内建命令允许对 shell 变量执行算术运算。每个 expression 都按下文 6.5 节“shell 算术”中给出的规则作为算术表达式求值。如果最后一个 expression 求值为 0,let 返回 1;否则 let 返回 0。

local#

local [option] name[=value] ...

对每个参数,创建一个名为 name 的局部变量并赋予它 value。option 可以是 declare 接受的任何选项。local 只能在函数内使用;它使变量 name 具有限定于该函数及其子函数可见的作用域。不在函数内使用 local 是错误的。

如果 name 是 '-',它使一组 shell 选项对调用 local 的那个函数局部化:在调用 local 之后,在函数内使用 set 内建命令更改的任何 shell 选项,都会在函数返回时恢复为其原始值。恢复的执行方式,如同执行了一系列 set 命令来恢复函数之前所拥有的那些值。

不带操作数时,local 把局部变量列表写入标准输出。除非 local 在函数之外使用、提供了无效的 name、或 name 是只读变量,否则返回状态为零。

logout#

logout [n]

退出一个登录 shell,向 shell 的父进程返回状态 n。

mapfile#

mapfile [-d delim] [-n count] [-O origin] [-s count]
        [-t] [-u fd] [-C callback] [-c quantum] [array]

从标准输入读取行,或者如果提供了 -u 选项则从文件描述符 fd 读取,读入索引数组变量 array。变量 MAPFILE 是默认数组。选项(如果提供)具有以下含义:

-d 使用 delim 的第一个字符而非换行来终止每个输入行。如果 delim 是空字符串,mapfile 会在读到 NUL 字符时终止一行。

-n 最多复制 count 行。如果 count 为 0,则复制所有行。

-O 从索引 origin 开始向 array 赋值。默认索引是 0。

-s 丢弃读取的前 count 行。

-t 从读到的每一行中移除末尾的分隔符(默认为换行)。

-u 从文件描述符 fd 而不是标准输入读取行。

-C 每读取 quantum 行就求值一次 callback。-c 选项指定 quantum。

-c 指定每次调用 callback 之间读取的行数。

如果指定了 -C 而没有 -c,则默认 quantum 为 5000。当 callback 被求值时,会把下一个要赋值的数组元素的索引,以及要赋给该元素的行,作为附加参数提供给 callback。callback 在读取该行之后、给数组元素赋值之前求值。

如果没有提供显式的 origin,mapfile 会在向 array 赋值之前清空 array。

除非提供了无效的选项或选项参数、array 无效或不可赋值、或者 array 不是索引数组,否则 mapfile 返回零。

printf#

printf [-v var] format [arguments]

在 format 的控制下把格式化后的 arguments 写入标准输出。-v 选项把输出赋给变量 var,而不是把它打印到标准输出。

format 是一个字符串,包含三类对象:普通字符(只是被原样复制到标准输出)、字符转义序列(被转换并复制到标准输出),以及格式说明(每一项导致打印下一个相应的参数)。除了标准的 printf(3) 格式字符 cCsSndiouxXeEfFgGaA 之外,printf 还解释以下附加格式说明符:

%b 使 printf 以与 echo -e 相同的方式展开相应参数中的反斜杠转义序列(参见 4.2 节“Bash 内建命令”)。

%q 使 printf 以可重新用作 shell 输入的形式输出相应参数。如果参数字符串中有任何字符需要,%q 和 %QP 使用 ANSI-C 引用风格(参见 3.1.2.4 节“ANSI-C 引用”),否则使用反斜杠引用。如果格式字符串使用 printf 的替代形式,这两种格式会用单引号引用该参数字符串。

%Q 类似 %q,但在引用参数之前对其应用所提供的精度。

%(datefmt)T 使 printf 输出把 datefmt 用作 strftime(3) 的格式字符串所得的日期时间字符串。相应的参数是一个表示自纪元以来秒数的整数。该格式说明符识别两个特殊参数值:-1 表示当前时间,-2 表示 shell 被调用时的时间。如果未指定参数,转换的行为就如同提供了 -1。这是通常 printf 行为的一个例外。

%b、%q 和 %T 格式说明符都使用格式说明中的字段宽度和精度参数,并为展开后的参数写出那么多字节(或使用那么宽的字段),而展开后的参数通常比原始参数包含更多字符。

%n 格式说明符接受一个相应参数,它被当作 shell 变量名处理。

%s 和 %c 格式说明符接受一个 l(long)修饰符,这强制它们把参数字符串转换为宽字符串,并以字符而非字节为单位应用所提供的字段宽度和精度。%S 和 %C 格式说明符分别等价于 %ls 和 %lc。

非字符串格式说明符的参数被当作 C 语言常量处理,但允许前导的正号或负号,且如果前导字符是单引号或双引号,则其值是后继字符使用当前 locale 所得的数值。

format 会按需要被重复使用,以耗尽所有 arguments。如果 format 需要的参数比提供的多,多余的格式说明表现得就像提供了合适的零值或空字符串一样。成功时返回值为零;如果提供了无效选项,或发生写错误或赋值错误,则为非零。

read#

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars]
     [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]

从标准输入读取一行,或者从作为 -u 选项参数提供的文件描述符 fd 读取,按上文 3.5.7 节“单词拆分”中所述把它拆分为单词,并把第一个单词赋给第一个 name,第二个单词赋给第二个 name,依此类推。如果单词比 name 多,剩余单词及它们之间的分隔符被赋给最后一个 name。如果从输入流读取的单词比 name 少,剩余的 name 被赋予空值。IFS 变量值中的字符按 shell 用于展开的相同规则(上文 3.5.7 节“单词拆分”中所述)被用来将该行拆分为单词。反斜杠字符 '\' 移除下一个所读字符的任何特殊含义,并用于续行。

选项(如果提供)具有以下含义:

-a aname 这些单词被赋给数组变量 aname 的连续索引,从 0 开始。在赋值之前会从 aname 中移除所有元素。其他 name 参数被忽略。

-d delim delim 的第一个字符终止输入行,而非换行。如果 delim 是空字符串,read 会在读到 NUL 字符时终止一行。

-e 如果标准输入来自终端,read 使用 Readline(参见第 8 章“命令行编辑”)来获取该行。Readline 使用当前(或默认,如果之前行编辑未处于活动状态)编辑设置,但使用 Readline 的默认文件名补全。

-E 如果标准输入来自终端,read 使用 Readline(参见第 8 章“命令行编辑”)来获取该行。Readline 使用当前(或默认,如果之前行编辑未处于活动状态)编辑设置,但使用 Bash 的默认补全,包括可编程补全。

-i text 如果正在使用 Readline 读取该行,read 在编辑开始前把 text 放入编辑缓冲区。

-n nchars read 在读取 nchars 个字符后返回,而不是等待完整的输入行,除非遇到 EOF 或 read 超时;但如果它在遇到分隔符之前读取的字符少于 nchars 个,则会遵守该分隔符。

-N nchars read 在恰好读取 nchars 个字符后返回,而不是等待完整的输入行,除非遇到 EOF 或 read 超时。输入中的分隔符字符不被特殊对待,也不会使 read 返回,直到它读满 nchars 个字符。结果不会按 IFS 中的字符拆分;其意图是变量被赋以恰好读到的那些字符(反斜杠除外;见下面的 -r 选项)。

-p prompt 在尝试读取任何输入之前显示 prompt,不带结尾换行,但仅当输入来自终端时。

-r 如果给出了此选项,反斜杠不起转义字符的作用。反斜杠被视为该行的一部分。特别地,反斜杠-换行对此时不能被用作续行。

-s 静默模式。如果输入来自终端,字符不被回显。

-t timeout 如果 read 在 timeout 秒内没有读到完整的输入行(或指定数量的字符),则使其超时并返回失败。timeout 可以是带小数点后小数部分的十进制数。此选项仅在 read 从终端、管道或其他特殊文件读取输入时有效;从常规文件读取时它没有效果。如果 read 超时,它把任何读到的部分输入保存到指定的变量名中,并返回大于 128 的状态。如果 timeout 为 0,read 立即返回,不尝试读取任何数据。这种情况下,如果指定文件描述符上有输入可用,或者 read 将返回 EOF,则退出状态为 0,否则为非零。

-u fd 从文件描述符 fd 而不是标准输入读取输入。

除了 delim 为空字符串的情况外,read 会忽略输入中的任何 NUL 字符。

如果没有提供 name,read 会把读到的那一行赋给变量 REPLY,不含结尾分隔符,除此之外不做修改。

除非遇到文件结尾、read 超时(这种情况下状态大于 128)、发生变量赋值错误(例如给只读变量赋值)、或把无效的文件描述符作为 -u 的参数提供,否则退出状态为零。

readarray#

readarray [-d delim] [-n count] [-O origin] [-s count]
          [-t] [-u fd] [-C callback] [-c quantum] [array]

从标准输入把行读入索引数组变量 array,或者如果提供了 -u 选项则从文件描述符 fd 读取。

mapfile 的同义词。

source#

source [-p path] filename [arguments]

. 的同义词(参见 4.1 节“Bourne Shell 内建命令”)。

type#

type [-afptP] [name ...]

指明每个 name 在用作命令名时会被如何解释。

如果使用 -t 选项,当 name 分别是别名、shell 保留字、shell 函数、shell 内建命令或可执行文件时,type 打印一个单词,分别为 'alias'、'keyword'、'function'、'builtin' 或 'file'。如果找不到该 name,type 不打印任何内容并返回失败状态。

如果使用 -p 选项,type 要么返回通过搜索 $PATH 为 name 找到的可执行文件名,要么在 -t 不会返回 'file' 时不返回任何内容。

-P 选项强制对每个 name 进行路径搜索,即使 -t 不会返回 'file'。

如果某个 name 存在于已散列命令表中,选项 -p 和 -P 会打印已散列的值,它不一定是在 $PATH 中最先出现的文件。

如果使用 -a 选项,type 返回所有包含名为 name 的命令的位置。这包括别名、保留字、函数和内建命令,但可以提供路径搜索选项(-p 和 -P)把输出限制为可执行文件。如果 -a 与 -p 一起提供,type 不查已散列命令表,只对 name 执行 PATH 搜索。

如果使用 -f 选项,type 不尝试查找 shell 函数,如同 command 内建命令那样。

如果所有 name 都被找到则返回状态为零,如果任何一个未找到则为非零。

typeset#

typeset [-afFgrxilnrtux] [-p] [name[=value] ...]

typeset 命令是为了与 Korn shell 兼容而提供的。它是 declare 内建命令的同义词。

ulimit#

ulimit [-HS] -a
ulimit [-HS] [-bcdefiklmnpqrstuvxPRT] [limit]

在允许此类控制的系统上,ulimit 提供对 shell 及它启动的进程可用的资源的控制。如果给出了某个选项,它被解释如下:

-S 更改并报告与某个资源关联的软限制。

-H 更改并报告与某个资源关联的硬限制。

-a 报告所有当前限制;不设置任何限制。

-b 套接字缓冲区的最大大小。

-c 创建的 core 文件的最大大小。

-d 进程数据段的最大大小。

-e 最大调度优先级("nice")。

-f shell 及其子进程写入的文件的最大大小。

-i 待处理信号的最大数量。

-k 可分配的 kqueue 的最大数量。

-l 可锁定到内存中的最大大小。

-m 最大常驻集大小(许多系统不遵守此限制)。

-n 打开的文件描述符的最大数量(大多数系统不允许设置此值)。

-p 管道缓冲区大小。

-q POSIX 消息队列中的最大字节数。

-r 最大实时调度优先级。

-s 最大栈大小。

-t 最大 cpu 时间量,以秒计。

-u 单个用户可用的最大进程数。

-v shell(在某些系统上还有其子进程)可用的最大虚拟内存量。

-x 文件锁的最大数量。

-P 伪终端的最大数量。

-R 实时进程在阻塞前可运行的最长时间,以微秒计。

-T 线程的最大数量。

如果提供了 limit,且未使用 -a 选项,则 limit 是指定资源的新值。特殊限制值 hard、soft 和 unlimited 分别表示当前硬限制、当前软限制和无限制。非 root 用户一旦设置了硬限制就不能再增大它;软限制可以增大到硬限制的值。否则,ulimit 打印指定资源的软限制当前值,除非提供了 -H 选项。当指定了多个资源时,如果合适,会在值之前打印限制名称和单位。设置新限制时,如果既未提供 -H 也未提供 -S,ulimit 会同时设置硬限制和软限制。如果未提供任何选项,则假定为 -f。

除了 -t(以秒计)、-R(以微秒计)、-p(以 512 字节块为单位)、-P、-T、-b、-k、-n 和 -u(这些是无缩放的值),以及处于 POSIX 模式时(参见 6.11 节“Bash POSIX 模式”)的 -c 和 -f(以 512 字节增量计)之外,各值以 1024 字节为增量。

除非提供了无效的选项或参数,或设置新限制时发生错误,否则返回状态为零。

unalias#

unalias [-a] [name ...]

从别名列表中移除每个 name。如果提供了 -a,移除所有别名。除非提供的某个 name 不是已定义的别名,否则返回值为真。别名在 6.6 节“别名”中描述。

4.3 修改 shell 行为#

4.3.1 set 内建命令#

这个内建命令相当复杂,因此值得单独用一节来讨论。set 允许你更改 shell 选项的值,设置位置参数,或者显示 shell 变量的名称与值。

set
set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [argument ...]
set [+abefhkmnptuvxBCEHPT] [+o option-name] [--] [-] [argument ...]
set -o
set +o

如果未提供任何选项或参数,set 会按当前 locale 排序显示所有 shell 变量和函数的名称与值,其格式可被重新用作输入,用以设置或重置当前已设置的变量。只读变量无法被重置。在 POSIX 模式下,只列出 shell 变量。

当提供选项时,这些选项用于设置或取消设置 shell 属性。选项处理结束后剩余的任意参数将替换位置参数。

选项若被指定,其含义如下:

-a 每个被创建或修改的变量或函数都会被赋予 export 属性,并被标记为导出到后续命令的环境中。

-b 使已终止的后台作业的状态立即被报告,而不是在打印下一个主提示符之前报告,或在某些情况下在前台命令退出时报告。仅在启用了作业控制时有效。

-e 如果管道(参见 3.2.3 节“管道”)返回非零状态则立即退出;该管道可能由单个简单命令(参见 3.2.2 节“简单命令”)、一个列表(参见 3.2.4 节“列表”)或一个复合命令(参见 3.2.5 节“复合命令”)组成。如果失败的命令属于紧跟在 while 或 until 保留字之后的命令列表,属于 if 语句中的测试,属于在 && 或 || 列表中执行的任何命令(最后那个 && 或 || 之后的命令除外),属于管道中除最后一条之外的任何命令(取决于 pipefail shell 选项的状态),或者该命令的返回状态正被 ! 取反,则 shell 不会退出。如果某个非子 shell 的复合命令因为在忽略 -e 的状态下有命令失败而返回非零状态,则 shell 不会退出。ERR 上的 trap(如果已设置)会在 shell 退出之前执行。

此选项分别应用于 shell 环境以及每个子 shell 环境(参见 3.7.3 节“命令执行环境”),并且可能导致子 shell 在执行完子 shell 中的所有命令之前就退出。

如果某个复合命令或 shell 函数在忽略 -e 的上下文中执行,则该复合命令或函数体内执行的任何命令都不会受到 -e 设置的影响,即使 -e 已设置且某个命令返回失败状态。如果某个复合命令或 shell 函数在忽略 -e 的上下文中执行时设置了 -e,则该设置不会产生任何效果,直到该复合命令或包含该函数调用的那条命令执行完毕为止。

-f 禁用文件名展开(通配)。

-h 在查找命令以便执行时定位并记住(hash)这些命令。此选项默认启用。

-k 赋值语句形式的所有参数都会被放入命令的环境中,而不只是那些位于命令名之前的参数。

-m 启用作业控制(参见第 7 章“作业控制”)。所有进程都在单独的进程组中运行。当后台作业完成时,shell 会打印一行包含其退出状态的信息。

-n 读取命令但不执行它们。这可用于检查脚本中的语法错误。交互式 shell 会忽略此选项。

-o option-name 设置与 option-name 对应的选项。如果提供了 -o 但未提供 option-name,set 会打印当前 shell 选项的设置情况。如果提供了 +o 但未提供 option-name,set 会在标准输出上打印一系列 set 命令,用以重建当前的选项设置。有效的选项名如下:

allexport 同 -a。

braceexpand 同 -B。

emacs 使用 emacs 风格的行编辑界面(参见第 8 章“命令行编辑”)。这也会影响 read -e 所使用的编辑界面。

errexit 同 -e。

errtrace 同 -E。

functrace 同 -T。

hashall 同 -h。

histexpand 同 -H。

history 启用命令历史,如 9.1 节“Bash 历史功能”所述。在交互式 shell 中此选项默认开启。

ignoreeof 交互式 shell 在读到 EOF 时不会退出。

keyword 同 -k。

monitor 同 -m。

noclobber 同 -C。

noexec 同 -n。

noglob 同 -f。

nolog 目前被忽略。

notify 同 -b。

nounset 同 -u。

onecmd 同 -t。

physical 同 -P。

pipefail 如果设置,管道的返回值是最后一个(最右侧)以非零状态退出的命令的值;如果管道中所有命令都成功退出,则为零。此选项默认禁用。

posix 启用 POSIX 模式;当 Bash 的默认行为与 POSIX 标准不同时,改变 Bash 的行为以符合该标准(参见 6.11 节“Bash POSIX 模式”)。这样做的目的是使 Bash 的行为表现为该标准的严格超集。

privileged 同 -p。

verbose 同 -v。

vi 使用 vi 风格的行编辑界面。这也会影响 read -e 所使用的编辑界面。

xtrace 同 -x。

-p 打开特权模式。在此模式下,$BASH_ENV 和 $ENV 文件不会被处理,shell 函数不会从环境继承,并且 SHELLOPTS、BASHOPTS、CDPATH 和 GLOBIGNORE 变量如果出现在环境中也会被忽略。如果 shell 启动时的有效用户(组)id 与实际用户(组)id 不相等,且未提供 -p 选项,则会采取上述这些行动,并把有效用户 id 设为实际用户 id。如果在启动时提供了 -p 选项,则不会重置有效用户 id。

关闭此选项会使有效用户和组 id 被设为实际用户和组 id。

-r 启用受限 shell 模式(参见 6.10 节“受限 shell”)。此选项一旦设置便无法取消。

-t 读取并执行一个命令后退出。

-u 在执行参数展开时,把未设置的变量和参数(特殊参数 @ 或 * 除外),或下标为 @ 或 * 的数组变量,视为错误。错误消息会被写入标准错误,且非交互式 shell 会退出。

-v 在读取 shell 输入行时将其打印到标准错误。

-x 在简单命令、for 命令、case 命令、select 命令以及算术 for 命令及其参数或相关单词列表被展开之后、被执行之前,把它们的跟踪信息打印到标准错误。shell 会在命令及其展开后的参数之前打印 PS4 变量展开后的值。

-B shell 将执行花括号展开(参见 3.5.1 节“花括号展开”)。此选项默认开启。

-C 防止使用 >、>& 和 <> 的输出重定向覆盖已存在的文件。改用重定向运算符 >| 代替 > 将覆盖此行为,强制创建输出文件。

-E 如果设置,ERR 上的任何 trap 都会被 shell 函数、命令替换以及在子 shell 环境中执行的命令继承。在这类情况下,ERR trap 通常不会被继承。

-H 启用 ! 风格的历史替换(参见 9.3 节“历史交互”)。对交互式 shell 此选项默认开启。

-P 如果设置,Bash 在执行像 cd 这类改变当前目录的命令时不会解析符号链接,而是使用物理目录结构。默认情况下,Bash 执行改变当前目录的命令时会遵循目录的逻辑链。

例如,如果 /usr/sys 是指向 /usr/local/sys 的符号链接,那么:

$ cd /usr/sys; echo $PWD
/usr/sys
$ cd ..; pwd
/usr

如果设置了 -P,则为:

$ cd /usr/sys; echo $PWD
/usr/local/sys
$ cd ..; pwd
/usr/local

-T 如果设置,DEBUG 和 RETURN 上的任何 trap 都会被 shell 函数、命令替换以及在子 shell 环境中执行的命令继承。在这类情况下,DEBUG 和 RETURN trap 通常不会被继承。

-- 如果此选项后没有参数,则取消设置位置参数。否则,位置参数会被设为这些参数,即使其中某些参数以 '-' 开头。

- 表示选项结束,并把所有剩余参数赋给位置参数。-x 和 -v 选项会被关闭。如果没有参数,位置参数保持不变。

使用 '+' 而非 '-' 会使这些选项被关闭。这些选项也可以在调用 shell 时使用。当前的选项集合可在 $- 中找到。

剩余的 N 个参数是位置参数,它们按顺序被赋给 $1、$2、… $N。特殊参数 # 被设为 N。

除非提供了无效选项,否则返回状态始终为零。

4.3.2 shopt 内建命令#

此内建命令允许你更改额外的可选 shell 行为。

shopt [-pqsu] [-o] [opname ...]

切换那些控制可选 shell 行为的设置的值。这些设置可以是下面列出的那些,或者,如果使用了 -o 选项,则可以是 set 内建命令的 -o 选项所提供的那些(参见 4.3.1 节“set 内建命令”)。

在无任何选项、或使用 -p 选项时,显示所有可设置选项的列表,并标明每一项是否已设置;如果提供了任何 optname,则输出仅限于这些选项。-p 选项以可被重新用作输入的格式显示输出。

其他选项的含义如下:

-s 启用(设置)每个 optname。

-u 禁用(取消设置)每个 optname。

-q 抑制正常输出;返回状态表明该 optname 是已设置还是未设置。如果在使用 -q 时提供了多个 optname 参数,那么当所有 optname 都已启用时返回状态为零,否则为非零。

-o 把 optname 的取值限制为 set 内建命令的 -o 选项所定义的那些(参见 4.3.1 节“set 内建命令”)。

如果在使用 -s 或 -u 时没有提供 optname 参数,shopt 分别只显示已设置或未设置的选项。

除非另有说明,shopt 选项默认是禁用(关闭)的。

列出选项时,如果所有 optname 都已启用则返回状态为零,否则为非零。设置或取消设置选项时,除非某个 optname 不是有效的 shell 选项,否则返回状态为零。

shopt 选项列表如下:

array_expand_once 如果设置,shell 会在算术表达式求值期间、在执行可进行变量赋值的那些内建命令期间,以及在执行进行数组解引用的那些内建命令期间,抑制对关联数组和索引数组下标的多次求值。

assoc_expand_once 已弃用;是 array_expand_once 的同义词。

autocd 如果设置,凡是其名称是一个目录名的命令名,都会如同 cd 命令的参数那样被执行。此选项仅由交互式 shell 使用。

bash_source_fullpath 如果设置,添加到 BASH_SOURCE 数组变量中的文件名会被转换为完整路径名(参见 5.2 节“Bash 变量”)。

cdable_vars 如果设置,传给 cd 内建命令的、并非目录的参数会被假定为某个变量的名称,该变量的值就是要切换到的目录。

cdspell 如果设置,cd 命令会尝试纠正目录组成部分拼写中的小错误。小错误包括字符顺序颠倒、缺少一个字符以及多出一个字符。如果 cd 更正了目录名,它会打印更正后的文件名,命令继续执行。此选项仅由交互式 shell 使用。

checkhash 如果设置,Bash 在尝试执行哈希表中找到的命令之前,会检查该命令是否存在。如果某个已哈希的命令不再存在,Bash 会执行正常的路径搜索。

checkjobs 如果设置,Bash 在退出交互式 shell 之前会列出任何已停止和正在运行的作业的状态。如果有作业正在运行,Bash 会推迟退出,直到在没有插入其他命令的情况下再次尝试退出(参见第 7 章“作业控制”)。如果有作业处于停止状态,shell 总是推迟退出。

checkwinsize 如果设置,Bash 会在每个外部(非内建)命令之后检查窗口大小,并在必要时更新 LINES 和 COLUMNS 的值;如果与 stderr 关联的文件描述符是终端,则使用它。此选项默认启用。

cmdhist 如果设置,Bash 会尝试把多行命令的所有行保存在同一个历史记录条目中。这样可以方便地重新编辑多行命令。此选项默认启用,但只有在命令历史已启用时才有效(参见 9.1 节“Bash 历史功能”)。

compat31、compat32、compat40、compat41、compat42、compat43、compat44 这些选项控制 shell 兼容模式的若干方面(参见 6.12 节“shell 兼容模式”)。

complete_fullquote 如果设置,Bash 在执行补全时会对文件名和目录名中的所有 shell 元字符加引号。如果未设置,当这些元字符出现在待补全单词中的 shell 变量引用里时,Bash 会把美元符号之类的元字符从待补全文件名中将要加引号的字符集合里移除。这意味着,展开为目录的变量名中的美元符号不会被加引号;不过,文件名中出现的任何美元符号也同样不会被加引号。这仅在 Bash 使用反斜杠为补全后的文件名加引号时有效。该变量默认已设置,这是直到 4.2 版本为止的默认 Bash 行为。

direxpand 如果设置,Bash 在执行文件名补全时会把目录名替换为单词展开的结果。这会改变 Readline 编辑缓冲区的内容。如果未设置,Bash 会尝试保留用户键入的内容。

dirspell 如果设置,当最初提供的目录名不存在时,Bash 会在单词补全过程中尝试对目录名进行拼写更正。

dotglob 如果设置,Bash 会把以 '.' 开头的文件名包含在文件名展开的结果中。文件名 . 和 .. 必须始终被显式匹配,即使设置了 dotglob 也是如此。

execfail 如果设置,非交互式 shell 在无法执行作为 exec 内建命令参数指定的文件时不会退出。交互式 shell 在 exec 失败时不会退出。

expand_aliases 如果设置,别名会按下文“别名”一节所述展开(参见 6.6 节“别名”)。对交互式 shell 此选项默认启用。

extdebug 如果在 shell 调用时、或在 shell 启动文件中设置,则安排在 shell 启动之前执行调试器 profile,等同于 --debugger 选项。如果在调用之后设置,则启用供调试器使用的行为:

  1. declare 内建命令的 -F 选项(参见 4.2 节“Bash 内建命令”)显示作为参数提供的每个函数名所对应的源文件名和行号。

  2. 如果 DEBUG trap 所运行的命令返回非零值,则跳过下一条命令,不执行它。

  3. 如果 DEBUG trap 所运行的命令返回值为 2,并且 shell 正在子例程(一个 shell 函数,或由 . 或 source 内建命令执行的 shell 脚本)中执行,则 shell 模拟一次对 return 的调用。

  4. BASH_ARGC 和 BASH_ARGV 按其描述进行更新(参见 5.2 节“Bash 变量”)。

  5. 启用函数跟踪:命令替换、shell 函数以及用 ( command ) 调用的子 shell 会继承 DEBUG 和 RETURN trap。

  6. 启用错误跟踪:命令替换、shell 函数以及用 ( command ) 调用的子 shell 会继承 ERR trap。

extglob 如果设置,启用上文所述的扩展模式匹配特性(参见 3.5.8.1 节“模式匹配”)。

extquote 如果设置,$'string' 和 $"string" 引用会在用双引号括起的 ${parameter} 展开中执行。此选项默认启用。

failglob 如果设置,在文件名展开期间未能匹配文件名的模式会导致展开错误。

force_fignore 如果设置,FIGNORE shell 变量所指定的后缀会使得在执行单词补全时忽略相应单词,即使被忽略的单词是唯一可能的补全项也是如此。关于 FIGNORE 的说明参见 5.2 节“Bash 变量”。此选项默认启用。

globasciiranges 如果设置,模式匹配方括号表达式中使用的范围表达式(参见 3.5.8.1 节“模式匹配”)在进行比较时表现得如同处于传统的 C locale 中。也就是说,模式匹配不会把当前 locale 的排序序列考虑在内,因此 'b' 不会排在 'A' 与 'B' 之间,且大写和小写 ASCII 字符会排在一起。

globskipdots 如果设置,文件名展开永远不会匹配文件名 . 和 ..,即使模式以 '.' 开头也是如此。此选项默认启用。

globstar 如果设置,在文件名展开上下文中使用的模式 '**' 会匹配所有文件以及零个或多个目录和子目录。如果该模式后跟 '/',则只匹配目录和子目录。

gnu_errfmt 如果设置,shell 错误消息会以标准 GNU 错误消息格式写出。

histappend 如果设置,shell 退出时历史列表会被追加到 HISTFILE 变量值所指定的文件中,而不是覆盖该文件。

histreedit 如果设置且正在使用 Readline,用户将有机会重新编辑一次失败的历史替换。

histverify 如果设置且正在使用 Readline,历史替换的结果不会立即传给 shell 解析器。取而代之,结果行会被载入 Readline 编辑缓冲区,从而允许进一步修改。

hostcomplete 如果设置且正在使用 Readline,当补全一个包含 '@' 的单词时,Bash 会尝试执行主机名补全(参见 8.4.6 节“补全命令”)。此选项默认启用。

huponexit 如果设置,交互式登录 shell 退出时 Bash 会向所有作业发送 SIGHUP(参见 3.7.6 节“信号”)。

inherit_errexit 如果设置,命令替换会继承 errexit 选项的值,而不是在子 shell 环境中取消设置它。此选项在启用 POSIX 模式时被启用。

interactive_comments 在交互式 shell 中,以 '#' 开头的单词会使得该单词以及该行所有剩余字符都被忽略,如同在非交互式 shell 中那样。此选项默认启用。

lastpipe 如果设置,且作业控制未激活,shell 会在当前 shell 环境中运行管道中最后一条未在后台执行的命令。

lithist 如果启用,且 cmdhist 选项已启用,多行命令会以嵌入换行的方式保存到历史中,而不是在可行时使用分号分隔符。

localvar_inherit 如果设置,局部变量在赋予任何新值之前会继承先前作用域中存在的同名变量的值和属性。nameref 属性不会被继承。

localvar_unset 如果设置,对先前函数作用域中的局部变量调用 unset 会把它们标记为在后续查找中处于未设置状态,直到该函数返回。这与在当前函数作用域中取消设置局部变量的行为完全相同。

login_shell 如果 shell 作为登录 shell 启动(参见 6.1 节“调用 Bash”),shell 会设置此选项。该值不可更改。

mailwarn 如果设置,且 Bash 正在检查邮件的文件自上次检查以来被访问过,Bash 会显示消息 "The mail in mailfile has been read"。

no_empty_cmd_completion 如果设置且正在使用 Readline,当在空行上尝试补全时,Bash 不会在 PATH 中搜索可能的补全项。

nocaseglob 如果设置,Bash 在执行文件名展开时以不区分大小写的方式匹配文件名。

nocasematch 如果设置,Bash 在执行 case 或 [[ 条件命令(参见 3.2.5.2 节“条件结构”)时执行匹配、在执行模式替换单词展开时,或在作为可编程补全的一部分过滤可能的补全项时,以不区分大小写的方式匹配模式。

noexpand_translation 如果设置,Bash 会把 $"...." 引用的翻译结果用单引号而不是双引号括起来。如果该字符串未被翻译,则此设置没有效果。

nullglob 如果设置,未匹配任何文件的文件名展开模式(参见 3.5.8 节“文件名展开”)会展开为空并被移除,而不是展开为模式自身。

patsub_replacement 如果设置,Bash 会把模式替换的替换字符串中出现的 '&' 展开为该模式所匹配的文本,如上文所述(参见 3.5.3 节“shell 参数展开”)。此选项默认启用。

progcomp 如果设置,启用可编程补全功能(参见 8.6 节“可编程补全”)。此选项默认启用。

progcomp_alias 如果设置,且可编程补全已启用,Bash 会把没有任何补全项的命令名视为可能的别名并尝试别名展开。如果该命令确实有别名,Bash 会使用别名展开后得到的命令词尝试可编程补全。

promptvars 如果设置,提示符字符串在下文所述展开之后还会经历参数展开、命令替换、算术展开和引号移除(参见 6.9 节“控制提示符”)。此选项默认启用。

restricted_shell 如果 shell 以受限模式启动(参见 6.10 节“受限 shell”),shell 会设置此选项。该值不可更改。启动文件被执行时不会重置它,从而允许启动文件判断某个 shell 是否受限。

shift_verbose 如果设置,当 shift 内建命令的移位计数超过位置参数的个数时,它会打印一条错误消息。

sourcepath 如果设置,且未提供 -p 选项,.(source)内建命令会使用 PATH 的值来查找包含作为参数提供的文件的目录。此选项默认启用。

varredir_close 如果设置,shell 会自动关闭使用 {varname} 重定向语法赋予的文件描述符(参见 3.6 节“重定向”),而不是在命令完成时让它们保持打开。

xpg_echo 如果设置,echo 内建命令默认会展开反斜杠转义序列。如果同时还启用了 posix shell 选项(参见 4.3.1 节“set 内建命令”),则 echo 不会解释任何选项。

4.4 特殊内建命令#

由于历史原因,POSIX 标准把若干内建命令归类为特殊内建命令。当 Bash 以 POSIX 模式执行时,特殊内建命令与其他内建命令在三个方面有所不同:

  1. 在命令查找期间,特殊内建命令会在 shell 函数之前被找到。

  2. 如果某个特殊内建命令返回错误状态,非交互式 shell 会退出。

  3. 位于命令之前的赋值语句在命令完成后仍会在 shell 环境中保持有效。

当 Bash 不以 POSIX 模式执行时,这些内建命令的行为与其余 Bash 内建命令并无不同。Bash 的 POSIX 模式在 6.11 节“Bash POSIX 模式”中描述。

以下是 POSIX 特殊内建命令:

break : . source continue eval exec exit export readonly return set
shift times trap unset

5 shell 变量#

本章介绍 Bash 使用的 shell 变量。Bash 会为若干变量自动赋予默认值。

5.1 Bourne shell 变量#

Bash 以与 Bourne shell 相同的方式使用某些 shell 变量。在某些情况下,Bash 会为该变量赋予默认值。

CDPATH 以冒号分隔的目录列表,用作 cd 内建命令的搜索路径。

HOME 当前用户的主目录;cd 内建命令的默认目录。该变量的值也被波浪号展开使用(参见 3.5.2 节“波浪号展开”)。

IFS 用于分隔字段的字符列表;在 shell 作为展开的一部分拆分单词时使用,read 内建命令也用它把行拆分为单词。关于单词拆分的说明,参见 3.5.7 节“单词拆分”。

MAIL 如果该值被设为文件名或目录名,并且未设置 MAILPATH 变量,Bash 会在指定文件或 Maildir 格式目录中有邮件到达时通知用户。

MAILPATH 以冒号分隔的文件名列表,shell 会定期检查这些文件中是否有新邮件。每个列表项都可以通过在文件名与消息之间用 ‘?’ 分隔,来指定新邮件到达该邮件文件时要打印的消息。在消息文本中使用时,$_ 会展开为当前邮件文件的名称。

OPTARG getopts 内建命令处理的最后一个选项参数的值。

OPTIND getopts 内建命令将要处理的下一个参数的索引。

PATH 以冒号分隔的目录列表,shell 在其中查找命令。PATH 值中长度为零(空)的目录名表示当前目录。空目录名可以表现为两个相邻的冒号,也可以表现为开头的或结尾的冒号。默认路径与系统相关,由安装 bash 的管理员设置。常见的取值是 /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin。

PS1 主提示符字符串。默认值是 \s-\v\$(末尾为一个空格)。关于在显示 PS1 之前会展开的完整转义序列列表,参见 6.9 节“控制提示符”。

PS2 次提示符字符串。默认值是 >。PS2 在显示之前以与 PS1 相同的方式展开。

5.2 Bash 变量#

这些变量由 Bash 设置或使用,但其他 shell 通常不会对它们做特殊处理。

Bash 使用的少数变量在其他章节中描述:用于控制作业控制设施的变量(参见 7.3 节“作业控制变量”)。

$_ 一个下划线。该变量有多种含义,取决于上下文。在 shell 启动时,$_ 被设置为用于调用 shell、或用于所执行的 shell 脚本的路径名,即通过环境或参数列表传入的那个名称。此后,它展开为在前台执行的上一简单命令的最后一个参数(在展开之后)。它也会被设置为用于调用所执行的每个命令的完整路径名,并被放入导出给该命令的环境中。检查邮件时,$_ 展开为邮件文件的名称。

BASH 用于执行当前 Bash 实例的完整路径名。

BASHOPTS 已启用 shell 选项的以冒号分隔的列表。列表中的每个词都是 shopt 内建命令的 -s 选项的合法参数(参见 4.3.2 节“shopt 内建命令”)。出现在 BASHOPTS 中的选项就是 shopt 报告为 “on” 的那些选项。如果 Bash 启动时该变量已在环境中,shell 会在读取任何启动文件之前启用列表中的每个选项。如果该变量被导出,子 shell 会启用列表中的每个选项。该变量是只读的。

BASHPID 展开为当前 Bash 进程的进程 ID。在某些情况下它与 $$ 不同,例如在无需重新初始化 Bash 的子 shell 中。对 BASHPID 赋值没有效果。如果 BASHPID 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_ALIASES 一个关联数组变量,其成员对应于 alias 内建命令所维护的内部别名列表(参见 4.1 节“Bourne shell 内建命令”)。添加到此数组中的元素会出现在别名列表中;然而,目前取消设置数组元素并不会使别名从别名列表中移除。如果 BASH_ALIASES 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_ARGC 一个数组变量,其各个值是当前 Bash 执行调用栈中每一帧的参数个数。当前子例程(shell 函数或使用 . 或 source 执行的脚本)的参数个数位于栈顶。执行子例程时,传入的参数个数会被压入 BASH_ARGC。shell 仅在扩展调试模式下才设置 BASH_ARGC(关于 shopt 内建命令的 extdebug 选项的说明,参见 4.3.2 节“shopt 内建命令”)。在 shell 已开始执行子例程之后再设置 extdebug,或者在未设置 extdebug 时引用该变量,可能会产生不一致的值。对 BASH_ARGC 赋值没有效果,并且它可能无法被取消设置。

BASH_ARGV 一个数组变量,包含当前 Bash 执行调用栈中的所有参数。最后一次子例程调用的最后一个参数位于栈顶;最初调用的第一个参数位于栈底。执行子例程时,shell 会把提供的参数压入 BASH_ARGV。shell 仅在扩展调试模式下才设置 BASH_ARGV(关于 shopt 内建命令的 extdebug 选项的说明,参见 4.3.2 节“shopt 内建命令”)。在 shell 已开始执行脚本之后再设置 extdebug,或者在未设置 extdebug 时引用该变量,可能会产生不一致的值。对 BASH_ARGV 赋值没有效果,并且它可能无法被取消设置。

BASH_ARGV0 被引用时,该变量展开为 shell 或 shell 脚本的名称(与 $0 相同;关于特殊参数 0 的说明,参见 3.4.2 节“特殊参数”)。给 BASH_ARGV0 赋值会把 $0 设为相同的值。如果 BASH_ARGV0 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_CMDS 一个关联数组变量,其成员对应于 hash 内建命令所维护的内部命令哈希表(参见 4.1 节“Bourne shell 内建命令”)。向此数组添加元素会使它们出现在哈希表中;然而,目前取消设置数组元素并不会从哈希表中移除命令名。如果 BASH_CMDS 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_COMMAND 展开为当前正在执行或即将执行的命令;除非 shell 是作为 trap 的结果在执行某条命令,此时它是在 trap 发生时正在执行的命令。如果 BASH_COMMAND 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_COMPAT 该值用于设置 shell 的兼容级别。关于各种兼容级别及其效果的说明,参见 6.12 节“Shell 兼容模式”。该值可以是一个十进制数(如 4.2),也可以是一个对应于所需兼容级别的整数(如 42)。如果 BASH_COMPAT 被取消设置或被设为空字符串,兼容级别会被设为当前版本的默认值。如果 BASH_COMPAT 被设为一个不属于任何合法兼容级别的值,shell 会打印一条错误消息,并把兼容级别设为当前版本的默认值。合法值的一个子集对应于下文所述的兼容级别(参见 6.12 节“Shell 兼容模式”)。例如,4.2 和 42 都是合法值,它们对应于 compat42 短选项,并把兼容级别设为 42。当前版本也是一个合法值。

BASH_ENV 如果在调用 Bash 执行 shell 脚本时设置了该变量,它的值会被展开,并用作在执行该脚本之前要读取的启动文件名。Bash 不使用 PATH 来搜索由此得到的文件名。参见 6.2 节“Bash 启动文件”。

BASH_EXECUTION_STRING -c 调用选项的命令参数。

BASH_LINENO 一个数组变量,其成员是源文件中各对应 FUNCNAME 成员被调用时所在的行号。${BASH_LINENO[$i]} 是源文件(${BASH_SOURCE[$i+1]})中调用 ${FUNCNAME[$i]} 的行号(如果是在另一个 shell 函数内部引用,则为 ${BASH_LINENO[$i-1]})。使用 LINENO 可获得当前行号。对 BASH_LINENO 赋值没有效果,并且它可能无法被取消设置。

BASH_LOADABLES_PATH 以冒号分隔的目录列表,enable 命令在其中查找可动态加载的内建命令。

BASH_MONOSECONDS 每次引用该变量时,只要系统提供单调时钟,它就会展开为系统单调时钟返回的值。如果没有单调时钟,它等同于 EPOCHSECONDS。如果 BASH_MONOSECONDS 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_REMATCH 一个数组变量,其成员由 [[ 条件命令的 ‘=~’ 二元运算符赋值(参见 3.2.5.2 节“条件结构”)。索引为 0 的元素是匹配整个正则表达式的字符串部分。索引为 n 的元素是匹配第 n 个带括号的子表达式的字符串部分。

BASH_SOURCE 一个数组变量,其成员是 FUNCNAME 数组变量中各对应 shell 函数名被定义所在的源文件名。shell 函数 ${FUNCNAME[$i]} 定义在文件 ${BASH_SOURCE[$i]} 中,并从 ${BASH_SOURCE[$i+1]} 中调用。对 BASH_SOURCE 赋值没有效果,并且它可能无法被取消设置。

BASH_SUBSHELL 每当 shell 在某个子 shell 或子 shell 环境中开始执行时,在该环境中该值加一。初始值为 0。如果 BASH_SUBSHELL 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

BASH_TRAPSIG 在执行 trap 动作期间,被设为与该动作对应的信号编号。关于信号编号和 trap 执行的信息,参见对 trap 的说明(参见 4.1 节“Bourne shell 内建命令”)。

BASH_VERSINFO 一个只读数组变量(参见 6.7 节“数组”),其成员保存当前 Bash 实例的版本信息。赋予该数组成员的取值如下:

BASH_VERSION 展开为描述当前 Bash 实例版本的字符串(如 5.2.37(3)-release)。

BASH_XTRACEFD 如果被设为一个对应于有效文件描述符的整数,Bash 会把启用 set -x 时生成的跟踪输出写入该文件描述符,而不是标准错误。这样可以把跟踪输出与诊断消息和错误消息分开。当 BASH_XTRACEFD 被取消设置或被赋予新值时,该文件描述符会被关闭。取消设置 BASH_XTRACEFD 或将它赋为空字符串,会使跟踪输出被发送到标准错误。注意,把 BASH_XTRACEFD 设为 2(标准错误文件描述符)然后再取消设置它,将导致标准错误被关闭。

CHILD_MAX 设置 shell 要记住的已退出子进程状态值的个数。Bash 不允许把该值降到 POSIX 规定的最小值以下,并且有一个不允许超过的最大值(当前为 8192)。最小值与系统相关。

COLUMNS 由 select 命令用于在打印选择列表时确定终端宽度。如果启用了 checkwinsize 选项(参见 4.3.2 节“shopt 内建命令”),或在交互式 shell 中收到 SIGWINCH 时,该变量会被自动设置。

COMP_CWORD 当前光标位置所在单词在 ${COMP_WORDS} 中的索引。该变量仅在由可编程补全设施调用的 shell 函数中可用(参见 8.6 节“可编程补全”)。

COMP_KEY 用于调用当前补全函数的按键(或按键序列的最后一个按键)。该变量仅在由可编程补全设施调用的 shell 函数和外部命令中可用(参见 8.6 节“可编程补全”)。

COMP_LINE 当前命令行。该变量仅在由可编程补全设施调用的 shell 函数和外部命令中可用(参见 8.6 节“可编程补全”)。

COMP_POINT 当前光标位置相对于当前命令开头的索引。如果当前光标位置位于当前命令末尾,该变量的值等于 ${#COMP_LINE}。该变量仅在由可编程补全设施调用的 shell 函数和外部命令中可用(参见 8.6 节“可编程补全”)。

COMP_TYPE 被设为一个整数值,对应于导致补全函数被调用的那次尝试补全的类型:TAB 表示常规补全,‘?’ 表示在连续按 Tab 后列出补全项,‘!’ 表示在部分单词补全时列出候选项,‘@’ 表示在单词未被修改时列出补全项,‘%’ 表示菜单式补全。该变量仅在由可编程补全设施调用的 shell 函数和外部命令中可用(参见 8.6 节“可编程补全”)。

COMP_WORDBREAKS Readline 库在执行单词补全时视为单词分隔符的字符集合。如果 COMP_WORDBREAKS 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

COMP_WORDS 一个数组变量,由当前命令行中的各个单词组成。该行按 Readline 拆分单词的方式拆分,使用上文所述的 COMP_WORDBREAKS。该变量仅在由可编程补全设施调用的 shell 函数中可用(参见 8.6 节“可编程补全”)。

COMPREPLY 一个数组变量,Bash 从中读取由可编程补全设施调用的 shell 函数所生成的候选补全项(参见 8.6 节“可编程补全”)。每个数组元素包含一个候选补全项。

COPROC 一个数组变量,创建用于保存与匿名协进程之间输出和输入的文件描述符(参见 3.2.6 节“协进程”)。

DIRSTACK 一个数组变量,包含目录栈的当前内容。目录按 dirs 内建命令显示它们的顺序出现在栈中。给该数组变量的成员赋值可用于修改栈中已有的目录,但必须使用 pushd 和 popd 内建命令来添加和移除目录。给该变量赋值不会改变当前目录。如果 DIRSTACK 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

EMACS 如果 shell 启动时 Bash 在环境中找到该变量,且其值为 ‘t’,Bash 会假定该 shell 运行在 Emacs shell 缓冲区中,并禁用行编辑。

ENV 在 POSIX 模式下调用交互式 shell 时(参见 6.11 节“Bash POSIX 模式”),该变量会以与 BASH_ENV 类似的方式被展开并执行(参见 6.2 节“Bash 启动文件”)。

EPOCHREALTIME 每次引用该参数时,它展开为自 Unix 纪元以来的秒数,是一个具有微秒粒度的浮点值(关于纪元的定义,参见 C 库函数 time 的文档)。对 EPOCHREALTIME 的赋值会被忽略。如果 EPOCHREALTIME 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

EPOCHSECONDS 每次引用该参数时,它展开为自 Unix 纪元以来的秒数(关于纪元的定义,参见 C 库函数 time 的文档)。对 EPOCHSECONDS 的赋值会被忽略。如果 EPOCHSECONDS 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

EUID 当前用户的有效用户 id 的数字形式。该变量是只读的。

EXECIGNORE 以冒号分隔的 shell 模式列表(参见 3.5.8.1 节“模式匹配”),定义在使用 PATH 进行命令搜索时要忽略的文件名集合。完整路径名匹配这些模式之一的文件,在补全以及通过 PATH 查找执行命令时不被视为可执行文件。这不会影响 [、test 和 [[ 命令的行为。命令哈希表中的完整路径名不受 EXECIGNORE 影响。使用该变量可以忽略那些设置了可执行位、但并非可执行文件的共享库文件。模式匹配会遵循 extglob shell 选项的设置。

FCEDIT fc 内建命令默认使用的编辑器。

FIGNORE 以冒号分隔的后缀列表,在执行文件名补全时要忽略这些后缀。后缀匹配 FIGNORE 中某一项的文件名会从匹配到的文件名列表中排除。一个示例值是 .o:~。

FUNCNAME 一个数组变量,包含当前位于执行调用栈中的所有 shell 函数的名称。索引为 0 的元素是当前正在执行的任何 shell 函数的名称。最底部的元素(索引最大的那个)是 “main”。该变量仅在某个 shell 函数正在执行时才存在。对 FUNCNAME 赋值没有效果。如果 FUNCNAME 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

该变量可以与 BASH_LINENO 和 BASH_SOURCE 一起使用。FUNCNAME 的每个元素在 BASH_LINENO 和 BASH_SOURCE 中都有对应的元素来描述调用栈。例如,${FUNCNAME[$i]} 是从文件 ${BASH_SOURCE[$i+1]} 的第 ${BASH_LINENO[$i]} 行被调用的。caller 内建命令利用这些信息显示当前调用栈。

FUNCNEST 一个大于 0 的数值,定义最大函数嵌套层级。超过该嵌套层级的函数调用会导致当前命令中止。

GLOBIGNORE 以冒号分隔的模式列表,定义在文件名展开时要忽略的文件名集合。如果某个由文件名展开模式匹配到的文件名同时也匹配 GLOBIGNORE 中的某个模式,它就会从匹配列表中移除。模式匹配会遵循 extglob shell 选项的设置。

GLOBSORT 控制文件名展开结果的排序方式。该变量的值指定文件名展开结果的排序依据和排序顺序。如果该变量被取消设置或被设为空字符串,文件名展开使用历史行为,即按名称排序,顺序为由 LC_COLLATE shell 变量确定的升序字典序。

如果设置了该变量,合法值以一个可选的 ‘+’ 开头(该字符被忽略),或者以 ‘-’ 开头(把排序顺序从升序反转为降序),其后跟一个排序说明符。合法的排序说明符有 ‘name’、‘numeric’、‘size’、‘mtime’、‘atime’、‘ctime’ 和 ‘blocks’,它们分别按名称、按数值顺序而非字典序的名称、文件大小、修改时间、访问时间、inode 变更时间和块数对文件排序。如果任何非名称键比较结果相等(例如两个文件大小相同),排序会使用名称作为次级排序键。

例如,取值 -mtime 会按修改时间降序排列结果(最新的在前)。

‘numeric’ 说明符把完全由数字组成的名称当作数字处理,并按它们的数值排序(例如 “2” 排在 “10” 之前)。使用 ‘numeric’ 时,包含非数字字符的名称排在所有全数字名称之后,并按传统行为按名称排序。

排序说明符 nosort 会完全禁用排序;Bash 按从文件系统读取它们的顺序返回结果,并忽略任何开头的 ‘-’。

如果缺少排序说明符,它默认是 name,因此取值 ‘+’ 等同于空字符串,而取值 ‘-’ 会按名称降序排序。

任何非法取值都会恢复历史排序行为。

GROUPS 一个数组变量,包含当前用户所属组的列表。对 GROUPS 赋值没有效果。如果 GROUPS 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

histchars 控制历史展开、快速替换和词元化的两个或三个字符(参见 9.3 节“历史交互”)。第一个字符是历史展开字符,即开始历史展开的字符,通常是 ‘!’。第二个字符是快速替换字符,通常是 ‘^’。当它作为行首第一个字符出现时,历史替换会重复上一条命令,把其中一个字符串替换为另一个。可选的第三个字符是历史注释字符,通常是 ‘#’,当它作为一个单词的第一个字符出现时,表示该行剩余部分是注释。历史注释字符会为该行剩余的单词禁用历史替换。它并不一定使 shell 解析器把该行其余部分当作注释处理。

HISTCMD 当前命令的历史编号,即历史列表中的索引。对 HISTCMD 赋值没有效果。如果 HISTCMD 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

HISTCONTROL 以冒号分隔的值列表,控制命令如何保存到历史列表。如果值列表中包含 ‘ignorespace’,则以空格字符开头的行不会被保存到历史列表。取值 ‘ignoredups’ 会导致与上一条历史条目匹配的行不被保存。取值 ‘ignoreboth’ 是 ‘ignorespace’ 和 ‘ignoredups’ 的简写。取值 ‘erasedups’ 会导致在保存当前行之前,先移除历史列表中所有与当前行匹配的先前行。任何不在上述列表中的取值都会被忽略。如果 HISTCONTROL 被取消设置,或不包含合法取值,Bash 会把 shell 解析器读取的所有行保存到历史列表,但要受 HISTIGNORE 取值的影响。如果多行复合命令的第一行被保存了,第二行及后续行不会被测试,无论 HISTCONTROL 取值如何都会被添加到历史中。如果第一行未被保存,该命令的第二行及后续行也不会被保存。

HISTFILE 保存命令历史的文件名。Bash 赋予的默认值是 ~/.bash_history。如果 HISTFILE 被取消设置或为空,shell 在退出时不保存命令历史。

HISTFILESIZE 历史文件中包含的最大行数。当给该变量赋值时,如有必要,历史文件会被截断,通过移除最旧的条目,使其包含的历史条目总数不超过该行数。如果历史列表包含多行条目,历史文件包含的行数可能超过该最大值,以避免留下部分历史条目。当 shell 退出时或在 history 内建命令写入历史文件后,历史文件也会被截断到该大小。如果该值为 0,历史文件会被截断为零大小。非数字值以及小于零的数值会阻止截断。shell 在读取任何启动文件之后,把默认值设为 HISTSIZE 的值。

HISTIGNORE 以冒号分隔的模式列表,用于决定哪些命令行应保存到历史列表。如果某个命令行匹配 HISTIGNORE 取值中的某个模式,它就不会被保存到历史列表。每个模式都锚定在行首,并且必须匹配整行(Bash 不会隐式追加一个 ‘*’)。在应用 HISTCONTROL 指定的检查之后,每个模式才与行进行测试。除常规的 shell 模式匹配字符外,‘&’ 匹配上一条历史行。反斜杠可以转义 ‘&’;反斜杠会在尝试匹配之前被移除。如果多行复合命令的第一行被保存了,第二行及后续行不会被测试,无论 HISTIGNORE 取值如何都会被添加到历史中。如果第一行未被保存,该命令的第二行及后续行也不会被保存。模式匹配会遵循 extglob shell 选项的设置。

HISTIGNORE 涵盖了 HISTCONTROL 的部分功能。模式 ‘&’ 等同于 ignoredups,模式 ‘[ ]*’ 等同于 ignorespace。把这两个模式组合起来、用冒号分隔,即可提供 ignoreboth 的功能。

HISTSIZE 历史列表中要记住的最大命令数。如果该值为 0,命令不会被保存到历史列表。小于零的数值会导致每条命令都被保存到历史列表(没有上限)。shell 在读取任何启动文件之后把默认值设为 500。

HISTTIMEFORMAT 如果该变量被设置且不为空,其值会被用作 strftime(3) 的格式字符串,来打印 history 内建命令显示的每条历史条目的时间戳。如果该变量被设置,shell 会把时间戳写入历史文件,以便跨 shell 会话保留。这使用历史注释字符来区分时间戳与其他历史行。

HOSTFILE 包含一个文件名,其格式与 /etc/hosts 相同;当 shell 需要补全主机名时应读取该文件。可能的主机名补全列表可能在 shell 运行期间发生变化;在该值发生变化后下一次尝试主机名补全时,Bash 会把新文件的内容添加到已有列表中。如果 HOSTFILE 被设置但没有值,或者未指定一个可读文件,Bash 会尝试读取 /etc/hosts 以获取可能的主机名补全列表。当 HOSTFILE 被取消设置时,Bash 会清除主机名列表。

HOSTNAME 当前主机的名称。

HOSTTYPE 描述 Bash 所运行机器的字符串。

IGNOREEOF 控制 shell 在收到作为唯一输入的 EOF 字符时的动作。如果被设置,该值是 Bash 退出之前可以作为输入行第一个字符读取的连续 EOF 字符的个数。如果该变量被设置但没有数字值,或者取值为空,那么默认为 10。如果该变量被取消设置,那么 EOF 表示 shell 的输入结束。这只对交互式 shell 生效。

INPUTRC Readline 初始化文件的名称,覆盖默认的 ~/.inputrc。

INSIDE_EMACS 如果 shell 启动时 Bash 在环境中找到该变量,它会假定该 shell 运行在 Emacs shell 缓冲区中,并可能根据 TERM 的值禁用行编辑。

LANG 用于确定任何未由以 LC_ 开头的变量专门选择的类别的区域设置类别。

LC_ALL 该变量覆盖 LANG 的值以及任何其他指定区域设置类别的 LC_ 变量的值。

LC_COLLATE 该变量确定对文件名展开结果进行排序时使用的整理顺序,并确定文件名展开和模式匹配中范围表达式、等价类以及整理序列的行为(参见 3.5.8 节“文件名展开”)。

LC_CTYPE 该变量确定文件名展开和模式匹配中字符的解释以及字符类的行为(参见 3.5.8 节“文件名展开”)。

LC_MESSAGES 该变量确定用于翻译前面带 ‘$’ 的双引号字符串的区域设置(参见 3.1.2.5 节“区域设置翻译”)。

LC_NUMERIC 该变量确定用于数字格式化的区域设置类别。

LC_TIME 该变量确定用于日期和时间格式化的区域设置类别。

LINENO 当前正在执行的脚本或 shell 函数中的行号。行号从 1 开始。不在脚本或函数中时,该值不保证有意义。如果 LINENO 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

LINES 由 select 命令用于在打印选择列表时确定列长度。如果启用了 checkwinsize 选项(参见 4.3.2 节“shopt 内建命令”),或在交互式 shell 中收到 SIGWINCH 时,该变量会被自动设置。

MACHTYPE 一个字符串,以标准的 GNU cpu-company-system 格式完整描述 Bash 正在其上执行的系统类型。

MAILCHECK shell 应每隔多久(以秒为单位)检查 MAILPATH 或 MAIL 变量所指定的文件中是否有邮件。默认为 60 秒。到了检查邮件的时候,shell 会在显示主提示符之前进行。如果该变量被取消设置,或被设为一个并非大于或等于零的数字的取值,shell 会禁用邮件检查。

MAPFILE 一个数组变量,创建用于保存 mapfile 内建命令在未提供变量名时读取的文本。

OLDPWD 由 cd 内建命令设置的上一个工作目录。

OPTERR 如果被设为值 1,Bash 会显示 getopts 内建命令生成的错误消息。每次调用 shell 时,OPTERR 都会被初始化为 1。

OSTYPE 描述 Bash 所运行操作系统的字符串。

PIPESTATUS 一个数组变量(参见 6.7 节“数组”),包含最近执行的前台管道中各命令的退出状态值列表;该管道可能只由一个简单命令组成(参见 3.2 节“shell 命令”)。Bash 在执行多元素管道、带 time 和取反的管道、简单命令、用 ‘(’ 运算符创建的子 shell、[[ 和 (( 复合命令之后,以及在导致 shell 中止命令执行的错误情况之后,会设置 PIPESTATUS。

POSIXLY_CORRECT 如果 Bash 启动时该变量已在环境中,shell 会在读取启动文件之前进入 POSIX 模式(参见 6.11 节“Bash POSIX 模式”),就像提供了 --posix 调用选项一样。如果它在 shell 运行期间被设置,Bash 会启用 POSIX 模式,就像执行了命令 set -o posix 一样。当 shell 进入 POSIX 模式时,如果该变量此前未被设置,它会设置该变量。

PPID shell 父进程的进程 ID。该变量是只读的。

PROMPT_COMMAND 如果该变量被设置且是一个数组,其每个已设置元素的值都会被解释为在打印主提示符($PS1)之前要执行的命令。如果它被设置但不是数组变量,则其值会被用作要执行的命令。

PROMPT_DIRTRIM 如果被设为大于零的数字,该值会被用作展开 \w 和 \W 提示符字符串转义时要保留的末尾目录组件数(参见 6.9 节“控制提示符”)。被移除的字符会以省略号替换。

PS0 该参数的值像 PS1 那样展开,并由交互式 shell 在读取命令之后、执行该命令之前显示。

PS3 该变量的值被用作 select 命令的提示符。如果该变量未被设置,select 命令使用 #? 作为提示符。

PS4 该参数的值像 PS1 那样展开,展开后的值是在设置了 -x 选项时回显命令行之前打印的提示符(参见 4.3.1 节“set 内建命令”)。展开后值的第一个字符会按需重复多次,以表明多级间接层次。默认值是 +。

PWD 由 cd 内建命令设置的当前工作目录。

RANDOM 每次引用该参数时,它展开为一个 0 到 32767 之间的随机整数。给 RANDOM 赋值会初始化(播种)随机数序列。用相同的常量值播种随机数生成器会产生相同的取值序列。如果 RANDOM 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

READLINE_ARGUMENT 提供给一个使用 bind -x 定义的 Readline 命令的任何数字参数(在它被调用时,参见 4.2 节“Bash 内建命令”)。

READLINE_LINE Readline 行缓冲区的内容,供 bind -x 使用(参见 4.2 节“Bash 内建命令”)。

READLINE_MARK Readline 行缓冲区中标记(保存的插入点)的位置,供 bind -x 使用(参见 4.2 节“Bash 内建命令”)。插入点与标记之间的字符通常称为区域(region)。

READLINE_POINT Readline 行缓冲区中插入点的位置,供 bind -x 使用(参见 4.2 节“Bash 内建命令”)。

REPLY read 内建命令的默认变量;当 read 未提供变量名参数时,它被设为所读取的行。

SECONDS 该变量展开为自 shell 启动以来的秒数。给该变量赋值会把计数重置为所赋的值,而展开后的值变为所赋的值加上自赋值以来的秒数。shell 调用时的秒数和当前时间始终通过以一秒分辨率查询系统时钟来确定。如果 SECONDS 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

SHELL 该环境变量展开为 shell 的完整路径名。如果 shell 启动时它未被设置,Bash 会为它赋予当前用户登录 shell 的完整路径名。

SHELLOPTS 已启用 shell 选项的以冒号分隔的列表。列表中的每个词都是 set 内建命令的 -o 选项的合法参数(参见 4.3.1 节“set 内建命令”)。出现在 SHELLOPTS 中的选项就是被 set -o 报告为 “on” 的那些选项。如果 Bash 启动时该变量已在环境中,shell 会在读取任何启动文件之前启用列表中的每个选项。如果该变量被导出,子 shell 会启用列表中的每个选项。该变量是只读的。

SHLVL 每次启动一个新的 Bash 实例时加一。它旨在表示你的 Bash shell 嵌套的深度。

SRANDOM 每次引用该变量时,它展开为一个 32 位伪随机数。在支持 /dev/urandom 或 arc4random 的系统上,该随机数生成器不是线性的,因此每个返回的数与它之前的数没有关系。该随机数生成器无法播种,因此对该变量赋值没有效果。如果 SRANDOM 被取消设置,它将失去其特殊属性,即使随后重新设置也是如此。

TIMEFORMAT 该参数的值被用作格式字符串,指定以 time 保留字为前缀的管道的计时信息应如何显示。‘%’ 字符引入一个转义序列,它会展开为一个时间值或其他信息。转义序列及其含义如下;方括号表示可选部分。

可选的 p 是一个数字,指定精度,即小数点之后的小数位数。取值 0 会导致不输出小数点或小数部分。time 在小数点之后最多打印六位数字;p 大于 6 的取值会被改为 6。如果未指定 p,time 在小数点之后打印三位数字。

可选的 l 指定一种更长的格式,包含分钟,形如 MMmSS.FFs。p 的值决定是否包含小数部分。

如果该变量未被设置,Bash 的表现就像它具有如下取值:

$'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'

如果该值为空,Bash 不显示任何计时信息。显示格式字符串时会追加一个结尾换行符。

TMOUT 如果被设为大于零的值,read 内建命令会使用该值作为其默认超时(参见 4.2 节“Bash 内建命令”)。当输入来自终端时,如果在 TMOUT 秒之后输入仍未到达,select 命令(参见 3.2.5.2 节“条件结构”)会终止。

在交互式 shell 中,该值被解释为在发出主提示符之后等待一行输入的秒数。如果在该秒数内没有完整的输入行到达,Bash 会终止。

TMPDIR 如果被设置,Bash 会用其值作为一个目录的名称,Bash 在该目录中创建供 shell 使用的临时文件。

UID 当前用户的真实用户 id 的数字形式。该变量是只读的。

6 Bash 特性#

本章介绍 Bash 特有的功能。

6.1 调用 Bash#

bash [long-opt] [-ir] [-abefhkmnptuvxdBCDHP] [-o option]
     [-O shopt_option] [argument ...]
bash [long-opt] [-abefhkmnptuvxdBCDHP] [-o option]
     [-O shopt_option] -c string [argument ...]
bash [long-opt] -s [-abefhkmnptuvxdBCDHP] [-o option]
     [-O shopt_option] [argument ...]

所有可与 set 内建命令一起使用的单字符选项(参见 4.3.1 节“Set 内建命令”)都可以在调用 shell 时作为选项使用。此外,还有若干多字符选项可供使用。这些选项必须出现在命令行中单字符选项之前,才会被识别。

--debugger

安排在 shell 启动之前执行调试器 profile。这会开启扩展调试模式(关于 shopt 内建命令的 extdebug 选项的说明,参见 4.3.2 节“Shopt 内建命令”)。

--dump-po-strings

以 GNU gettext PO(portable object,可移植对象)文件格式在标准输出上打印所有以 $ 开头的双引号字符串的列表。除输出格式外,与 -D 等效。

--dump-strings

等效于 -D。

--help

在标准输出上显示用法信息并成功退出。

--init-file filename、--rcfile filename

在交互式 shell 中从 filename 执行命令(而不是从 ~/.bashrc)。

--login

等效于 -l。

--noediting

当 shell 为交互式时,不使用 GNU Readline 库(参见第 8 章“命令行编辑”)读取命令行。

--noprofile

当 Bash 作为登录 shell 被调用时,不加载系统级启动文件 /etc/profile,也不加载任何个人初始化文件 ~/.bash_profile、~/.bash_login 或 ~/.profile。

--norc

在交互式 shell 中不读取 ~/.bashrc 初始化文件。如果 shell 以 sh 之名调用,则此项默认开启。

--posix

启用 POSIX 模式;把默认行为与 POSIX 标准不一致的 Bash 行为改为与标准一致。这样做是意图使 Bash 表现为该标准的严格超集。关于 Bash POSIX 模式的说明,参见 6.11 节“Bash POSIX 模式”。

--restricted

等效于 -r。使 shell 成为受限 shell(参见 6.10 节“受限 shell”)。

--verbose

等效于 -v。在读取 shell 输入行时将其打印出来。

--version

在标准输出上显示此 Bash 实例的版本信息并成功退出。

还有若干可以在调用时提供、但不能与 set 内建命令一起使用的单字符选项。

-c

从第一个非选项参数 command_string 读取并执行命令,然后退出。如果 command_string 之后还有参数,则第一个参数赋给 $0,其余参数赋给位置参数。对 $0 的赋值会设置 shell 的名称,警告和错误消息中会用到该名称。

-i

强制 shell 以交互方式运行。交互式 shell 在 6.3 节“交互式 shell”中说明。

-l

使该 shell 表现得像被 login 直接调用一样。当 shell 为交互式时,这等效于用 exec -l bash 启动一个登录 shell。当 shell 不是交互式时,它会读取并执行登录 shell 的启动文件。exec bash -l 或 exec bash --login 会用 Bash 登录 shell 替换当前 shell。关于登录 shell 的特殊行为,参见 6.2 节“Bash 启动文件”。

-r

使 shell 成为受限 shell(参见 6.10 节“受限 shell”)。

-s

如果存在此选项,或者选项处理之后没有剩余参数,则 Bash 从标准输入读取命令。此选项允许在调用交互式 shell 时,或在通过管道读取输入时设置位置参数。

-D

在标准输出上打印所有以 $ 开头的双引号字符串的列表。当当前区域设置不是 C 或 POSIX 时,这些字符串需要进行语言翻译(参见 3.1.2.5 节“区域设置翻译”)。这隐含 -n 选项;不会执行任何命令。

[-+]O [shopt_option]

shopt_option 是 shopt 内建命令接受的 shell 选项之一(参见 4.3.2 节“Shopt 内建命令”)。如果给出 shopt_option,-O 设置该选项的值,+O 取消该设置。如果未提供 shopt_option,Bash 在标准输出上打印 shopt 所接受的 shell 选项的名称和值。如果调用选项为 +O,则输出的格式可以重新作为输入使用。

--

-- 表示选项结束,并禁用后续的选项处理。-- 之后的任何参数都被视为 shell 脚本文件名(参见 3.8 节“Shell 脚本”)以及传递给该脚本的参数。

-

等效于 --。

登录 shell 是其参数零的第一个字符为 - 的 shell,或者是以 --login 选项调用的 shell。

交互式 shell 是指在没有非选项参数的情况下(除非指定了 -s)、未指定 -c 选项而启动的 shell,且其标准输入和标准错误都连接到终端(由 isatty(3) 判定),或者是以 -i 选项启动的 shell。更多信息参见 6.3 节“交互式 shell”。

如果在选项处理之后还有参数剩余,且既未提供 -c 也未提供 -s 选项,则第一个参数被视为含有 shell 命令的文件的名称(参见 3.8 节“Shell 脚本”)。当 Bash 以这种方式被调用时,$0 被设为该文件的名称,位置参数被设为其余参数。Bash 读取并执行该文件中的命令,然后退出。Bash 的退出状态是脚本中执行的最后一条命令的退出状态。如果没有执行任何命令,退出状态为 0。Bash 首先尝试在当前目录中打开该文件;如果找不到文件,则在 PATH 的各目录中搜索该脚本。

6.2 Bash 启动文件#

本节说明 Bash 如何执行其启动文件。如果其中任何文件存在但无法读取,Bash 会报告错误。文件名中的波浪号按上文“波浪号展开”所述进行展开(参见 3.5.2 节“波浪号展开”)。

交互式 shell 在 6.3 节“交互式 shell”中说明。

作为交互式登录 shell 调用,或使用 --login 调用#

当 Bash 作为交互式登录 shell 被调用,或作为带 --login 选项的非交互式 shell 被调用时,它首先从文件 /etc/profile(如果该文件存在)读取并执行命令。读取该文件之后,它按顺序查找 ~/.bash_profile、~/.bash_login 和 ~/.profile,并从其中第一个存在且可读的文件读取并执行命令。--noprofile 选项会抑制此行为。

当交互式登录 shell 退出时,或非交互式登录 shell 执行 exit 内建命令时,Bash 从文件 ~/.bash_logout(如果存在)读取并执行命令。

作为交互式非登录 shell 调用#

当 Bash 作为非登录 shell 的交互式 shell 运行时,它从 ~/.bashrc(如果该文件存在)读取并执行命令。--norc 选项会抑制此行为。--rcfile file 选项使 Bash 使用 file,而不是 ~/.bashrc。

因此,通常你的 ~/.bash_profile 会包含这样一行

if [ -f ~/.bashrc ]; then . ~/.bashrc; fi

放在任何登录专用初始化之后(或之前)。

以非交互方式调用#

当 Bash 以非交互方式启动时(例如运行一个 shell 脚本),它会在环境中查找变量 BASH_ENV,如果该变量出现在环境中就展开其值,并把展开后的值作为要读取和执行的文件的名称。Bash 的行为如同执行了以下命令:

if [ -n "${BASH_ENV}" ]; then . "$BASH_ENV"; fi

但不会使用 PATH 变量的值来搜索该文件名。

如上所述,如果非交互式 shell 以 --login 选项调用,Bash 会尝试从登录 shell 的启动文件读取并执行命令。

以 sh 之名调用#

如果 Bash 以名称 sh 调用,它会尽力模仿历史版本 sh 的启动行为,同时也要遵循 POSIX 标准。

当作为交互式登录 shell 调用,或作为带 --login 选项的非交互式 shell 调用时,它首先按顺序尝试从 /etc/profile 和 ~/.profile 读取并执行命令。--noprofile 选项会抑制此行为。

当以名称 sh 作为交互式 shell 调用时,Bash 查找变量 ENV,如果它已定义就展开其值,并把展开后的值作为要读取和执行的文件的名称。由于以 sh 之名调用的 shell 不会尝试从任何其他启动文件读取和执行命令,--rcfile 选项不起作用。

以名称 sh 调用的非交互式 shell 不会尝试读取任何其他启动文件。

以 sh 之名调用时,Bash 在读取启动文件之后进入 POSIX 模式。

以 POSIX 模式调用#

当 Bash 以 POSIX 模式启动时(例如使用 --posix 命令行选项),它按 POSIX 标准处理启动文件。在此模式下,交互式 shell 会展开 ENV 变量,并从文件名为展开值的文件读取并执行命令。不会读取其他启动文件。

由远程 shell 守护进程调用#

Bash 会尝试判断自己是否在标准输入连接到网络连接的情况下运行,例如由历史悠久且很少见到的远程 shell 守护进程(通常是 rshd)或安全 shell 守护进程 sshd 执行时。如果 Bash 判断自己以这种方式非交互地运行,它会从 ~/.bashrc(如果该文件存在且可读)读取并执行命令。如果以 sh 之名调用,Bash 不读取此文件。--norc 选项会抑制此行为,--rcfile 选项使 Bash 改用其他文件而不是 ~/.bashrc,但 rshd 和 sshd 通常都不会用这些选项调用 shell,也不允许指定它们。

有效 UID/GID 与真实 UID/GID 不相等时调用#

如果启动 Bash 时有效用户(组)ID 不等于真实用户(组)ID,且未提供 -p 选项,则不读取任何启动文件,shell 函数不从环境继承,SHELLOPTS、BASHOPTS、CDPATH 和 GLOBIGNORE 变量(如果它们出现在环境中)被忽略,并且有效用户 ID 被设为真实用户 ID。如果在调用时提供了 -p 选项,启动行为相同,但有效用户 ID 不会被重置。

6.3 交互式 shell#

6.3.1 什么是交互式 shell?#

交互式 shell 是指在没有非选项参数的情况下(除非指定了 -s)、未指定 -c 选项而启动的 shell,它的输入和错误输出都连接到终端(由 isatty(3) 判定),或者是以 -i 选项启动的 shell。

交互式 shell 通常从用户终端读取并写入用户终端。

-s 调用选项可用于在交互式 shell 启动时设置位置参数。

6.3.2 此 shell 是否为交互式?#

要在启动脚本中判断 Bash 是否以交互方式运行,可以测试 - 特殊参数的值。当 shell 为交互式时,它包含 i。例如:

case "$-" in
  i*) echo This shell is interactive ;;
esac

或者,启动脚本可以检查变量 PS1;它在非交互式 shell 中未设置,在交互式 shell 中已设置。因此:

if [ -z "$PS1" ]; then
    echo This shell is not interactive
else
    echo This shell is interactive
fi

6.3.3 交互式 shell 行为#

当 shell 以交互方式运行时,它会在若干方面改变其行为。

  1. Bash 按 6.2 节“Bash 启动文件”所述读取并执行启动文件。

  2. 作业控制(参见第 7 章“作业控制”)默认启用。当作业控制生效时,Bash 忽略键盘产生的作业控制信号 SIGTTIN、SIGTTOU 和 SIGTSTP。

  3. Bash 在打印主提示符 $PS1 之前,把 PROMPT_COMMAND 数组变量中已设置的元素的值作为命令执行(参见 5.2 节“Bash 变量”)。

  4. Bash 在读取命令的第一行之前展开并显示 PS1,在读取多行命令的第二行及后续行之前展开并显示 PS2。Bash 在读取命令之后、执行之前展开并显示 PS0。提示符字符串转义序列的完整列表参见 6.9 节“控制提示符”。

  5. Bash 使用 Readline(参见第 8 章“命令行编辑”)从用户终端读取命令。

  6. 当读取命令时在标准输入上收到 EOF 时,Bash 会检查 ignoreeof 选项(通过 set -o 设置)的值,而不是立即退出(参见 4.3.1 节“Set 内建命令”)。

  7. Bash 默认启用命令历史(参见 9.1 节“Bash 历史功能”)和历史展开(参见 9.3 节“历史交互”)。当启用了历史的 shell 退出时,Bash 把命令历史保存到 $HISTFILE 命名的文件中。

  8. 默认执行别名展开(参见 6.6 节“别名”)。

  9. 在任何 trap 都未设置时,Bash 忽略 SIGTERM(参见 3.7.6 节“信号”)。

  10. 在任何 trap 都未设置时,SIGINT 会被捕获并处理(参见 3.7.6 节“信号”)。SIGINT 会中断某些 shell 内建命令。

  11. 如果启用了 huponexit shell 选项,交互式登录 shell 在退出时会向所有作业发送 SIGHUP(参见 3.7.6 节“信号”)。

  12. -n 选项不起作用,无论是在调用时还是在使用 set -n 时(参见 4.3.1 节“Set 内建命令”)。

  13. Bash 会定期检查邮件,具体取决于 MAIL、MAILPATH 和 MAILCHECK shell 变量的值(参见 5.2 节“Bash 变量”)。

  14. 启用 set -u 之后,因引用未绑定的 shell 变量而产生的展开错误不会使 shell 退出(参见 4.3.1 节“Set 内建命令”)。

  15. 在 ${var:?word} 展开中,因 var 未设置或为空而引起的展开错误不会使 shell 退出(参见 3.5.3 节“shell 参数展开”)。

  16. shell 内建命令遇到的重定向错误不会导致 shell 退出。

  17. 在 POSIX 模式下运行时,特殊内建命令返回错误状态不会导致 shell 退出(参见 6.11 节“Bash POSIX 模式”)。

  18. exec 失败不会导致 shell 退出(参见 4.1 节“Bourne Shell 内建命令”)。

  19. 解析器语法错误不会导致 shell 退出。

  20. 如果启用了 cdspell shell 选项,shell 会尝试对 cd 内建命令的目录参数做简单的拼写纠正(参见 4.3.2 节“Shopt 内建命令”中关于 shopt 内建命令 cdspell 选项的说明)。cdspell 选项仅在交互式 shell 中生效。

  21. shell 会检查 TMOUT 变量的值,如果在打印 $PS1 之后的规定秒数内没有读取到命令,就退出(参见 5.2 节“Bash 变量”)。

6.4 Bash 条件表达式#

条件表达式由 [[ 复合命令(参见 3.2.5.2 节“条件结构”),以及 test 和 [ 内建命令(参见 4.1 节“Bourne Shell 内建命令”)使用。test 和 [ 命令根据参数个数决定其行为;关于其他命令特有的行为,请参见这些命令各自的说明。

表达式可以是一元或二元的,由下面列出的原语构成。一元表达式常用于检查文件或 shell 变量的状态。二元运算符用于字符串、数值和文件属性比较。

当某些文件名在表达式中使用时,Bash 会对它们做特殊处理。如果运行 Bash 的操作系统提供这些特殊文件,Bash 就使用它们;否则在内部以如下方式模拟它们:如果某个原语的文件参数形如 /dev/fd/N,则 Bash 检查文件描述符 N。如果某个原语的文件参数是 /dev/stdin、/dev/stdout 或 /dev/stderr 之一,则 Bash 分别检查文件描述符 0、1 或 2。

与 [[ 一起使用时,< 和 > 运算符按当前区域设置作字典序排序。test 命令使用 ASCII 顺序。

除非另有说明,作用于文件的各个原语会跟随符号链接,作用于链接的目标,而不是链接本身。

-a file

如果 file 存在则为真。

-b file

如果 file 存在且为块特殊文件则为真。

-c file

如果 file 存在且为字符特殊文件则为真。

-d file

如果 file 存在且为目录则为真。

-e file

如果 file 存在则为真。

-f file

如果 file 存在且为普通文件则为真。

-g file

如果 file 存在且其 set-group-id 位已设置则为真。

-h file

如果 file 存在且为符号链接则为真。

-k file

如果 file 存在且其 “sticky” 位已设置则为真。

-p file

如果 file 存在且为命名管道(FIFO)则为真。

-r file

如果 file 存在且可读则为真。

-s file

如果 file 存在且大小大于零则为真。

-t fd

如果文件描述符 fd 已打开且指向终端则为真。

-u file

如果 file 存在且其 set-user-id 位已设置则为真。

-w file

如果 file 存在且可写则为真。

-x file

如果 file 存在且可执行则为真。

-G file

如果 file 存在且归有效组 ID 所有则为真。

-L file

如果 file 存在且为符号链接则为真。

-N file

如果 file 存在且自上次访问以来已被修改则为真。

-O file

如果 file 存在且归有效用户 ID 所有则为真。

-S file

如果 file 存在且为套接字则为真。

file1 -ef file2

如果 file1 和 file2 指向相同的设备号和 inode 号则为真。

file1 -nt file2

如果 file1 比 file2 新(按修改日期)则为真,或者 file1 存在而 file2 不存在则为真。

file1 -ot file2

如果 file1 比 file2 旧则为真,或者 file2 存在而 file1 不存在则为真。

-o optname

如果 shell 选项 optname 已启用则为真。选项列表出现在 set 内建命令 -o 选项的说明中(参见 4.3.1 节“Set 内建命令”)。

-v varname

如果 shell 变量 varname 已设置(已被赋过值)则为真。如果 varname 是用 @ 或 * 下标引用的索引数组变量名,则当该数组有任何已设置的元素时返回真。如果 varname 是用 @ 或 * 下标引用的关联数组变量名,则当具有该键的元素已设置时返回真。

-R varname

如果 shell 变量 varname 已设置且是名称引用(nameref)则为真。

-z string

如果 string 的长度为零则为真。

-n string、string

如果 string 的长度非零则为真。

string1 == string2、string1 = string2

如果两个字符串相等则为真。与 [[ 命令一起使用时,这会按上文所述执行模式匹配(参见 3.2.5.2 节“条件结构”)。

为符合 POSIX 规范,应使用 = 搭配 test 命令。

string1 != string2

如果两个字符串不相等则为真。

string1 < string2

如果按字典序 string1 排在 string2 之前则为真。

string1 > string2

如果按字典序 string1 排在 string2 之后则为真。

arg1 OP arg2

OP 是 -eq、-ne、-lt、-le、-gt 或 -ge 之一。这些算术二元运算符分别在 arg1 等于、不等于、小于、小于或等于、大于、大于或等于 arg2 时返回真。arg1 和 arg2 可以是正整数或负整数。与 [[ 命令一起使用时,arg1 和 arg2 会作为算术表达式求值(参见 6.5 节“shell 算术”)。由于 [[ 命令对 arg1 和 arg2 执行的展开可能会产生空字符串,算术表达式求值会把它们视为求值为 0 的表达式。

6.5 shell 算术#

shell 允许对算术表达式求值,这可以作为一种 shell 展开进行,也可以通过 (( 复合命令、let 和 declare 内建命令、算术 for 命令、[[ 条件命令,或 declare 内建命令的 -i 选项进行。

求值使用可用的最大定宽整数完成,不检查溢出,但除 0 会触发 trap 并被标记为错误。运算符及其优先级、结合性和取值与 C 语言相同。下面列出的运算符被分为若干相同优先级组,各组按优先级从高到低排列。

id++ id--   variable post-increment and post-decrement
++id --id   variable pre-increment and pre-decrement
- +         unary minus and plus
! ~         logical and bitwise negation
**          exponentiation
* / %       multiplication, division, remainder
+ -         addition, subtraction
<< >>       left and right bitwise shifts
<= >= < >   comparison
== !=       equality and inequality
&           bitwise AND
^           bitwise exclusive OR
|           bitwise OR
&&          logical AND
||          logical OR
expr ? if-true-expr : if-false-expr   conditional operator
= *= /= %= += -= <<= >>= &= ^= |=     assignment
expr1 , expr2                         comma

允许把 shell 变量用作操作数;在表达式求值之前会执行参数展开。在表达式内部,也可以不使用参数展开语法而按名称引用 shell 变量。这意味着你可以在算术表达式中使用 x(x 是某个 shell 变量名),shell 会把它的值作为表达式求值并使用结果。在表达式中按名称引用时,值为空或未设置的 shell 变量求值为 0。

当变量被引用时,或者当一个已用 declare -i 赋予整数属性的变量被赋值时,该变量的值会作为算术表达式求值。空值求值为 0。shell 变量不必开启整数属性就可以用于表达式。

整数常量遵循 C 语言定义,但不含后缀和字符常量。以 0 开头的常量被解释为八进制数。以 0x 或 0X 开头表示十六进制。否则,数字采用 [base#]n 的形式,其中可选的 base 是 2 到 64 之间的十进制数,表示算术进制,n 是该进制下的一个数。如果省略 base#,则使用十进制。指定 n 时,如果需要非数字字符,大于 9 的数字按小写字母、大写字母、@ 和 _ 的顺序表示。如果 base 小于或等于 36,则小写和大写字母可以互换使用,来表示 10 到 35 之间的数字。

运算符按优先级顺序求值。括号中的子表达式先求值,并且可以覆盖上述优先级规则。

6.6 别名#

别名允许用一个字符串替换输入中处于简单命令第一个词位置的某个词。别名有名称和对应的值,它们通过 alias 和 unalias 内建命令设置和取消设置(参见第 4 章“shell 内建命令”)。

如果 shell 在正确的位置读取到一个未被引用的词,它会检查该词是否匹配某个别名名称。如果匹配,shell 就用别名值替换该词,并把该值当作原来读到的词来读取。在尝试别名替换之前,shell 不查看该词之后的任何字符。

字符 /、$、`、= 以及上文列出的任何 shell 元字符或引用字符都不得出现在别名名称中。替换文本可以包含任何合法的 shell 输入,包括 shell 元字符。替换文本的第一个词会被检查是否为别名,但与被展开的别名完全相同的词不会再被展开第二次。这意味着,例如可以把 ls 设为 "ls -F" 的别名,而 Bash 不会尝试递归展开替换文本。

如果别名值的最后一个字符是空白,那么 shell 会检查别名之后的下一个命令词是否需要进行别名展开。

别名通过 alias 命令创建和列出,通过 unalias 命令删除。

与 csh 不同,这里没有在替换文本中使用参数的机制。如果需要参数,请改用 shell 函数(参见 3.3 节“shell 函数”)。

当 shell 不是交互式时,别名不会被展开,除非用 shopt 设置了 expand_aliases shell 选项(参见 4.3.2 节“Shopt 内建命令”)。

关于别名定义和使用的规则有些令人困惑。Bash 总是至少读取一整行输入,以及构成一个复合命令的所有行,然后才执行该行或该复合命令上的任何命令。别名在读取命令时展开,而不是在执行时展开。因此,与其他命令出现在同一行的别名定义,要等到 shell 读取下一行输入时才生效;复合命令中的别名定义,要等到 shell 解析并执行完整个复合命令时才生效。该行中位于别名定义之后的命令,以及复合命令其余部分的命令,都不受新别名影响。函数执行时也存在这个问题。别名在读取函数定义时展开,而不是在函数执行时展开,因为函数定义本身也是一条命令。因此,在函数中定义的别名要等到该函数执行之后才可用。为安全起见,应始终把别名定义单独放在一行,不要在复合命令中使用 alias。

就几乎所有用途而言,shell 函数都比别名更可取。

6.7 数组#

Bash 提供一维的索引数组和关联数组变量。任何变量都可以用作索引数组;declare 内建命令显式声明数组。数组大小没有上限,也不要求成员必须连续索引或连续赋值。索引数组使用必须展开为整数的算术表达式来引用(参见 6.5 节“shell 算术”),下标从零开始;关联数组使用任意字符串。除非另有说明,索引数组的下标必须是非负整数。

shell 会对索引数组下标执行参数和变量展开、算术展开、命令替换以及引号移除。由于这有可能产生空字符串,下标索引会把它们当作求值为 0 的表达式处理。

shell 会对关联数组下标执行波浪号展开、参数和变量展开、算术展开、命令替换以及引号移除。空字符串不能用作关联数组的键。

如果任何变量以下列语法被赋值,Bash 会自动创建索引数组

name[subscript]=value

下标被视为必须求值为大于或等于 0 的数的算术表达式。要显式声明索引数组,使用

declare -a name

(参见 4.2 节“Bash 内建命令”)。下列语法也被接受

declare -a name[subscript]

其中下标会被忽略。

关联数组使用下列方式创建

declare -A name

可以使用 declare 和 readonly 内建命令为数组变量指定属性。每个属性作用于数组的所有成员。

数组使用如下形式的复合赋值来赋值

name=(value1 value2 ...)

其中每个 value 的形式可以是 [subscript]=string。索引数组赋值除了 string 之外不需要任何东西。

列表中的每个 value 都会经历上文所述的 shell 展开(参见 3.5 节“shell 展开”),但那些包含方括号和下标的合法变量赋值,与单个变量赋值一样,不会经历花括号展开和单词拆分。

对索引数组赋值时,如果提供了可选的下标,就赋给该索引;否则,被赋值元素的索引是语句中上一次赋值的索引加一。索引从零开始。

对关联数组赋值时,复合赋值中的词可以是赋值语句形式(此时下标是必需的),也可以是一个词列表,被解释为交替出现的键和值序列:name=(key1 value1 key2 value2 ...)。它们与 name=([key1]=value1 [key2]=value2 ...) 的处理完全相同。列表中的第一个词决定其余的词如何解释;列表中的所有赋值必须是同一类型。使用键/值对时,键不能缺失或为空;末尾缺失的值按空字符串处理。

declare 内建命令也接受此语法。单个数组元素可以使用上文介绍的 name[subscript]=value 语法赋值。

对索引数组赋值时,如果 name 被负数下标引用,该数字被解释为相对于 name 最大索引加一的位置,因此负下标从数组末尾往前数,下标 -1 引用最后一个元素。

使用复合赋值语法赋值时,+= 运算符会向数组变量追加;参见上文 3.4 节“shell 参数”。

数组元素使用 ${name[subscript]} 引用。大括号是必需的,以避免与 shell 的文件名展开运算符冲突。如果下标是 @ 或 *,该词会展开为数组 name 的所有成员,除非某个内建命令或词展开的说明中另有说明。这两个下标只在词出现在双引号内时才有区别。如果该词被双引号括起,${name[*]} 展开为单个词,其中每个数组成员的值为 IFS 变量的第一个字符所分隔,而 ${name[@]} 把 name 的每个元素展开为单独的一个词。当数组没有成员时,${name[*]} 展开为空。如果双引号内的展开出现在某个词内部,则第一个参数的展开与原词展开的开头部分连接,最后一个参数的展开与原词展开的结尾部分连接。这与特殊参数 @ 和 * 的展开类似。

${#name[subscript]} 展开为 ${name[subscript]} 的长度。如果 subscript 是 @ 或 *,则该展开为数组中的元素个数。

如果用于引用索引数组某元素的下标求值为小于零的数,它被解释为相对于数组最大索引加一的位置,因此负下标从数组末尾往前数,下标 -1 引用最后一个元素。

不带下标引用数组变量等效于用下标 0 引用。使用合法下标对变量的任何引用都是合法的;必要时 Bash 会创建数组。

如果某个下标已被赋值,则数组变量被视为已设置。空字符串是合法值。

除了值之外,还可以获取数组的键(索引)。${!name[@]} 和 ${!name[*]} 展开为数组变量 name 中被赋值的索引。在双引号内的处理与双引号内特殊参数 @ 和 * 的展开类似。

使用 unset 内建命令销毁数组。unset name[subscript] 取消设置索引 subscript 处的数组元素。索引数组的负下标按上文所述解释。取消设置数组变量的最后一个元素并不会取消设置该变量。

unset name(其中 name 是数组)会移除整个数组。当下标是 * 或 @ 时,unset name[subscript] 的行为因数组类型而异。当 name 是关联数组时,它移除键为 * 或 @ 的元素。如果 name 是索引数组,unset 会移除所有元素,但不移除数组本身。

当把带下标的变量名作为命令参数使用(例如与 unset 一起使用),而不使用上文所述的词展开语法(如 unset a[4])时,该参数会受到 shell 文件名展开的影响。如果不希望进行路径名展开,请给参数加引号(如 unset 'a[4]')。

declare、local 和 readonly 内建命令都接受 -a 选项以指定索引数组、-A 选项以指定关联数组。如果同时提供这两个选项,-A 优先。read 内建命令接受 -a 选项,用于把从标准输入读取的词列表赋给数组,并且可以把标准输入中的值读入单个数组元素。set 和 declare 内建命令以可重新作为输入使用的方式显示数组值。其他内建命令也接受数组名参数(例如 mapfile);详情参见各个内建命令的说明。shell 还提供了若干内建数组变量。

6.8 目录栈#

目录栈是最近访问过的目录的列表。pushd 内建命令在改变当前目录的同时把目录加入栈,popd 内建命令从栈中移除指定的目录,并把当前目录改为被移除的那个目录。dirs 内建命令显示目录栈的内容。当前目录始终是目录栈的“顶部”。

目录栈的内容也可以作为 DIRSTACK shell 变量的值查看。

6.8.1 目录栈内建命令#

dirs

dirs
dirs [-clpv] [+N | -N]

不带选项时,显示当前记住的目录列表。目录通过 pushd 命令加入列表;popd 命令从列表中移除目录。当前目录始终是栈中的第一个目录。

如果提供了选项,其含义如下:

-c

通过删除所有元素清空目录栈。

-l

使用完整路径名生成列表;默认列表格式使用波浪号表示主目录。

-p

使 dirs 每行打印目录栈中的一个条目。

-v

使 dirs 每行打印目录栈中的一个条目,并在每个条目前加上它在栈中的索引。

+N

显示第 N 个目录(从 dirs 不带选项调用时所打印列表的左侧开始计数),从零开始。

-N

显示第 N 个目录(从 dirs 不带选项调用时所打印列表的右侧开始计数),从零开始。

popd

popd [-n] [+N | -N]

从目录栈中移除元素。元素从 dirs 列出的第一个目录开始以 0 编号;也就是说,popd 等效于 popd +0。

不给参数时,popd 从栈中移除顶部目录,并切换到新的顶部目录。

如果提供了参数,其含义如下:

-n

从栈中移除目录时抑制正常的目录切换,只操纵栈。

+N

从栈中移除第 N 个目录(从 dirs 打印列表的左侧开始计数),从零开始。

-N

从栈中移除第 N 个目录(从 dirs 打印列表的右侧开始计数),从零开始。

如果目录栈的顶部元素被修改,且未提供 -n 选项,popd 使用 cd 内建命令切换到栈顶的目录。如果 cd 失败,popd 返回非零值。

否则,如果指定了无效选项、目录栈为空,或 N 指定了不存在的目录栈条目,popd 返回不成功的状态。如果 popd 命令成功,Bash 运行 dirs 显示目录栈的最终内容,返回状态为 0。

pushd

pushd [-n] [+N | -N | dir]

把目录加入目录栈顶部,或轮换栈,使新的栈顶成为当前工作目录。不给参数时,pushd 交换目录栈顶部的两个元素。

如果提供了参数,其含义如下:

-n

轮换或向栈中加入目录时抑制正常的目录切换,只操纵栈。

+N

轮换栈,使第 N 个目录(从 dirs 打印列表的左侧开始计数,从零开始)位于栈顶。

-N

轮换栈,使第 N 个目录(从 dirs 打印列表的右侧开始计数,从零开始)位于栈顶。

dir

使 dir 成为栈顶。

栈被修改后,如果未提供 -n 选项,pushd 使用 cd 内建命令切换到栈顶的目录。如果 cd 失败,pushd 返回非零值。

否则,如果未提供参数,除非目录栈为空,pushd 返回零。轮换目录栈时,除非目录栈为空或 N 指定了不存在的目录栈元素,pushd 返回零。如果 pushd 命令成功,Bash 运行 dirs 显示目录栈的最终内容。

6.9 控制提示符#

此外,下表描述了可以出现在提示符变量 PS0、PS1、PS2 和 PS4 中的特殊字符:

转义序列 含义
\a 响铃字符。
\d 日期,采用 “Weekday Month Date” 格式(例如 “Tue May 26”)。
\D{format} format 被传给 strftime(3),结果被插入提示符字符串;空的 format 会产生与区域设置(locale)相关的时间表示。花括号是必需的。
\e 一个转义字符。
\h 主机名,直到第一个 . 为止。
\H 主机名。
\j shell 当前管理的作业数。
\l shell 终端设备名的基名(例如 “ttys0”)。
\n 换行符。
\r 回车符。
\s shell 的名称:$0 的基名(最后一个斜杠之后的部分)。
\t 时间,24 小时制 HH:MM:SS 格式。
\T 时间,12 小时制 HH:MM:SS 格式。
\@ 时间,12 小时制 am/pm 格式。
\A 时间,24 小时制 HH:MM 格式。
\u 当前用户的用户名。
\v Bash 版本(例如 2.00)。
\V Bash 发行版本,即 version + patchlevel(例如 2.00.0)。
\w PWD shell 变量的值($PWD),其中 $HOME 用波浪号缩写(使用 $PROMPT_DIRTRIM 变量)。
\W $PWD 的基名,其中 $HOME 用波浪号缩写。
\! 此命令的历史编号。
\# 此命令的命令编号。
\$ 如果有效 uid 为 0,则为 #,否则为 $。
\nnn ASCII 码为八进制值 nnn 的字符。
\\ 一个反斜杠。
\[ 开始一个非打印字符序列。这可以用来把终端控制序列嵌入提示符中。
\] 结束一个非打印字符序列。

命令编号和历史编号通常是不同的:一条命令的历史编号是它在历史列表中的位置,该列表可能包含从历史文件中恢复的命令(参见 9.1 节“Bash 历史设施”),而命令编号则是它在当前 shell 会话中执行的命令序列中的位置。

字符串被解码之后,会经过参数展开、命令替换、算术展开和引用移除进行展开,具体取决于 promptvars shell 选项的值(参见 4.3.2 节“Shopt 内建命令”)。如果字符串中被转义的部分出现在命令替换之内,或者包含对单词展开而言特殊的字符,这可能产生不希望的副作用。

6.10 受限 shell#

如果 Bash 以 rbash 这一名称启动,或者在调用时提供了 --restricted 或 -r 选项,shell 就会变为受限的。受限 shell 用来建立比标准 shell 更受控制的环境。受限 shell 的行为与 bash 完全相同,只是以下操作被禁止或不执行:

这些限制在任何启动文件被读取之后才会生效。

当一条被发现是 shell 脚本的命令被执行时(参见 3.8 节“Shell 脚本”),rbash 会为执行该脚本而派生的 shell 关闭所有限制。

受限 shell 模式只是有用的受限环境的一个组成部分。它还应配合以下措施使用:把 PATH 设置为只允许执行少数经过验证的命令的值(那些允许逃出 shell 的命令尤其脆弱),登录后把当前目录改为 $HOME 之外、不可写入的目录,不允许受限 shell 执行 shell 脚本,以及清理环境中有可能使某些命令改变其行为的变量(例如 VISUAL 或 PAGER)。

现代系统提供了实现受限环境的更安全方式,例如 jail、zone 或容器。

6.11 Bash 与 POSIX#

6.11.1 什么是 POSIX?#

POSIX 是一系列基于 Unix 的标准的名称。许多 Unix 服务、工具和函数都属于该标准的一部分,范围从基本的系统调用和 C 库函数,到常见的应用程序和工具,再到系统管理。

POSIX Shell 和 Utilities 标准最初由 IEEE 1003.2 工作组(POSIX.2)制定。1003.2 标准的第一版于 1992 年发布。它与最初的 IEEE 1003.1 工作组合并,目前由 Austin Group(IEEE、The Open Group 与 ISO/IEC SC22/WG15 的联合工作组)维护。如今,Shell 和 Utilities 是构成 IEEE Std 1003.1-2024 的那套文档中的一个分卷,因此原先的 POSIX.2(1992 年版本)现在已成为当前统一的 POSIX 标准的一部分。

Shell 和 Utilities 分卷着重于命令解释器接口以及通常从命令行或其他程序执行的实用程序。该标准可免费在线获取,网址为 https://pubs.opengroup.org/onlinepubs/9799919799/utilities/contents.html。

Bash 关注的是由 POSIX Shell and Utilities 分卷所定义的 shell 行为方面的内容。shell 命令语言当然已经标准化,包括基本的流程控制和程序执行构造、I/O 重定向和管道、参数处理、变量展开以及引用。

那些必须作为 shell 的一部分来实现以提供所需功能的特殊内建命令,被规定为 shell 的组成部分;这方面的例子有 eval 和 export。其他实用程序出现在 POSIX 中并非专门讲述 shell 的章节里,它们通常(在某些情况下是必须)以内建命令的形式实现,例如 read 和 test。POSIX 还规定了 shell 交互行为方面的内容,包括作业控制和命令行编辑。只有 vi 风格的行编辑命令被标准化了;emacs 编辑命令因有人反对而被排除在外。

6.11.2 Bash POSIX 模式#

尽管 Bash 是 POSIX shell 规范的一种实现,但在某些方面 Bash 的默认行为与该规范不同。Bash 的 posix 模式会在这些方面改变 Bash 的行为,使其更严格地符合该标准。

以 --posix 命令行选项启动 Bash,或者在 Bash 运行期间执行 set -o posix,都会使 Bash 在默认行为与 POSIX 不同之处改变行为以匹配 POSIX 的规定,从而更贴近 POSIX 标准。

当以 sh 之名被调用时,Bash 会在读取启动文件之后进入 POSIX 模式。

当 POSIX 模式生效时,以下内容会被改变:

  1. Bash 确保 POSIXly_CORRECT 变量被设置。

  2. Bash 读取并执行 POSIX 启动文件($ENV),而不是通常的 Bash 文件(参见 6.2 节“Bash 启动文件”)。

  3. 别名展开始终启用,即使在非交互式 shell 中也是如此。

  4. 出现在能识别保留字的上下文中的保留字,不进行别名展开。

  5. 在初次解析命令替换时就会执行别名展开。默认(非 posix)模式在别名展开启用时通常会将其推迟到命令替换执行之时。这意味着,命令替换不会展开那些在命令替换初次解析之后才定义的别名(例如在函数定义中定义的别名)。

  6. time 保留字可以单独用作简单命令。以这种方式使用时,它会显示 shell 及其已结束子进程的计时统计信息。TIMEFORMAT 变量控制计时信息的格式。

  7. 如果下一个词元以 - 开头,解析器不会把 time 识别为保留字。

  8. 在解析和展开出现在双引号之内的 ${...} 展开时,单引号不再具有特殊含义,也不能用来引用右花括号或其他特殊字符,除非该运算符是那些被定义为执行模式移除的运算符之一。在后一种情况下,它们不必成对出现。

  9. 重定向运算符不会对重定向中的单词执行文件名展开,除非 shell 是交互式的。

  10. 重定向运算符不会对重定向中的单词执行单词拆分。

  11. 函数名不得与某个 POSIX 特殊内建命令同名。

  12. 波浪号展开只对跟在命令名之前的赋值执行,而不是对该行上所有赋值语句执行。

  13. 虽然可以使用变量间接引用,但不得把它应用于 # 和 ? 特殊参数。

  14. 在展开被双引号引用的模式上下文中展开 * 特殊参数时,不会把 $* 当作被双引号引用那样处理。

  15. 在会经历展开的 here-document 正文中,当双引号字符(")出现在反引号命令替换中时,会被特殊处理。这意味着,例如,位于双引号字符之前的反斜杠会对它进行转义,并且该反斜杠会被移除。

  16. 命令替换不会设置 ? 特殊参数。没有命令词的简单命令的退出状态,仍然是在求值该命令中的变量赋值和重定向期间所发生的最后一个命令替换的退出状态,但这要等到所有赋值和重定向都完成之后才会发生。

  17. 作为 PATH 变量各元素中第一个字符出现的字面波浪号,不会按 3.5.2 节“波浪号展开”中所述的那样展开。

  18. 命令查找会先找到 POSIX 特殊内建命令,然后才找到 shell 函数,这包括 type 和 command 内建命令所打印的输出。

  19. 即使某个名称中含斜杠的 shell 函数是在进入 POSIX 模式之前定义的,shell 也不会执行名称中含有一个或多个斜杠的函数。

  20. 当哈希表中某条命令已不存在时,Bash 会重新搜索 $PATH 以找到新的位置。使用 shopt -s checkhash 也可获得这一行为。

  21. Bash 不会把未设置执行位的命令插入命令哈希表,即使它把该命令作为 $PATH 搜索的(最后一搏的)结果返回。

  22. 当某个作业以非零状态退出时,作业控制代码和内建命令所打印的消息为 Done(status)。

  23. 当某个作业被停止时,作业控制代码和内建命令所打印的消息为 Stopped(signame),其中 signame 例如为 SIGTSTP。

  24. 如果 shell 是交互式的,Bash 不会在以 ; 或换行分隔的列表中执行两条命令之间进行作业通知。非交互式 shell 会在列表中某个前台作业完成之后打印状态消息。

  25. 如果 shell 是交互式的,对于状态发生变化的作业或由于信号而终止的前台作业,Bash 会一直等到下一个提示符才打印其状态。非交互式 shell 会在前台作业完成后打印状态消息。

  26. 在通过 wait 或 jobs 内建命令通知用户某个作业已终止之后,Bash 会永久地将该作业从作业表中移除。它在通知用户某作业终止之后将该作业从作业列表中移除,但只要给 wait 提供 PID 参数,其状态仍可通过 wait 获得。

  27. 当运行 v 命令时,vi 编辑模式会直接调用 vi 编辑器,而不是去检查 $VISUAL 和 $EDITOR。

  28. 提示符展开会启用 POSIX 的 PS1 和 PS2 展开,把 ! 展开为历史编号、把 !! 展开为 !,并且无论 promptvars 选项如何设置,Bash 都会对 PS1 和 PS2 的值执行参数展开。

  29. 默认的历史文件是 ~/.sh_history(这是 shell 赋给 $HISTFILE 的默认值)。

  30. 即使在双引号字符串之内,! 字符也不会引入历史展开,即使 histexpand 选项已启用。

  31. 在打印 shell 函数定义时(例如通过 type),除非必要,Bash 不会打印 function 保留字。

  32. 如果算术展开中的语法错误导致产生非法表达式,非交互式 shell 会退出。

  33. 如果发生参数展开错误,非交互式 shell 会退出。

  34. 如果某个 POSIX 特殊内建命令返回错误状态,非交互式 shell 会退出。这些致命错误就是 POSIX 标准中所列的那些,包括传递不正确的选项、重定向错误、跟在命令名之前的赋值发生的变量赋值错误等等。

  35. 如果在赋值语句之后没有命令名跟随时发生变量赋值错误,非交互式 shell 会以错误状态退出。例如,试图给只读变量赋值时就会发生变量赋值错误。

  36. 如果在特殊内建命令之前的赋值语句中发生变量赋值错误,非交互式 shell 会以错误状态退出,但对任何其他简单命令则不会如此。对于任何其他简单命令,shell 会中止该命令的执行,并在顶层继续执行(“shell 不得对发生错误的那条命令做进一步处理”)。

  37. 如果 for 语句中的迭代变量或 select 语句中的选择变量是只读变量或名称非法,非交互式 shell 会以错误状态退出。

  38. 如果 . filename 中的 filename 找不到,非交互式 shell 会退出。

  39. 如果用 . 或 source 内建命令读取的脚本中存在语法错误,或者 eval 内建命令处理的字符串中存在语法错误,非交互式 shell 会退出。

  40. 如果 export、readonly 或 unset 内建命令得到一个不是合法标识符的参数,且它们不是在操作 shell 函数,非交互式 shell 会退出。这些错误会强制退出,因为它们都是特殊内建命令。

  41. 跟在 POSIX 特殊内建命令之前的赋值语句,在该内建命令完成之后仍保留在 shell 环境中。

  42. command 内建命令不会阻止那些把赋值语句当作参数的内建命令将其展开为赋值语句;当不在 POSIX 模式下时,声明类命令在被 command 置于之前时会失去其赋值语句展开特性。

  43. 启用 POSIX 模式的效果是设置 inherit_errexit 选项,因此为执行命令替换而派生的子 shell 会从父 shell 继承 -e 选项的值。当 inherit_errexit 选项未启用时,Bash 会在这样的子 shell 中清除 -e 选项。

  44. 启用 POSIX 模式的效果是设置 shift_verbose 选项,因此传给 shift 的数值参数若超出位置参数的数目,就会产生一条错误消息。

  45. 启用 POSIX 模式的效果是设置 interactive_comments 选项(参见 3.1.3 节“注释”)。

  46. 如果 . 和 source 内建命令通过搜索 PATH 找不到 filename 参数,它们不会在当前目录中搜索该参数。

  47. 当 alias 内建命令显示别名定义时,除非提供了 -p 选项,它不会以开头的 alias 来显示这些定义。

  48. bg 内建命令使用规定的格式来描述每个被放入后台的作业,该格式不包含该作业是当前作业还是前一个作业的指示。

  49. 当以逻辑模式调用 cd 内建命令,且由 $PWD 与作为参数提供的目录名构造出的路径名并不指向一个已存在的目录时,cd 会失败,而不会退回到物理模式。

  50. 当 cd 内建命令无法改变目录、原因是当规范化时由 $PWD 与作为参数提供的目录名构造出的路径名长度超过 PATH_MAX 时,cd 会尝试使用所提供的目录名。

  51. 当 xpg_echo 选项启用时,Bash 不会试图把传给 echo 的任何参数解释为选项。echo 在转换转义序列之后显示每个参数。

  52. export 和 readonly 内建命令以 POSIX 所要求的格式显示其输出。

  53. 在列出历史时,fc 内建命令不包含某一历史条目是否被修改过的指示。

  54. fc 所使用的默认编辑器是 ed。

  55. fc 把多余参数视为错误,而不是忽略它们。

  56. 如果传给 fc -s 的参数太多,fc 会打印一条错误消息并返回失败。

  57. kill -l 的输出把所有信号名打印在一行上,以空格分隔,且不带 SIG 前缀。

  58. kill 内建命令不接受带 SIG 前缀的信号名。

  59. 如果任何 pid 或作业参数无效,或者向其中任何一个发送指定信号失败,kill 内建命令会返回失败状态。在默认模式下,只要信号成功发送给所指定的任一进程,kill 就返回成功。

  60. printf 内建命令使用 double(经由 strtod)来转换与浮点转换说明符相对应的参数,而不是在可用时使用 long double。L 长度修饰符会强制 printf 在可用时使用 long double。

  61. pwd 内建命令会验证它所打印的值与当前目录相同,即使没有被要求用 -P 选项检查文件系统也是如此。

  62. read 内建命令可以被一个已设置 trap 的信号中断。如果 Bash 在执行 read 期间收到一个被 trap 捕获的信号,trap 处理程序会执行,而 read 返回一个大于 128 的退出状态。

  63. 当不带选项调用 set 内建命令时,它不会显示 shell 函数名和定义。

  64. 当不带选项调用 set 内建命令时,它显示变量值时不会加引号,除非这些值包含 shell 元字符,即使结果中包含非打印字符也是如此。

  65. 在求值 < 和 > 二元运算符时,test 内建命令使用当前区域设置(locale)来比较字符串。

  66. test 内建命令的一元主操作 -t 需要一个参数。历史版本的 test 在某些情况下把该参数设为可选,Bash 出于向后兼容而试图兼容那些做法。

  67. trap 内建命令显示信号名时不带开头的 SIG。

  68. trap 内建命令不会检查第一个参数是否可能是信号指定,并据此把信号处理恢复为原始处置方式,除非该参数完全由数字组成且是合法的信号编号。如果用户想把给定信号的处理程序重置为原始处置方式,应当使用 - 作为第一个参数。

  69. 不带参数的 trap -p 会显示处置方式被设为 SIG_DFL 的信号,以及 shell 启动时被忽略的那些信号,而不仅仅是已捕获的信号。

  70. type 和 command 内建命令不会把不可执行的文件报告为已找到,尽管如果它是 $PATH 中找到的唯一同名文件,shell 仍会尝试执行它。

  71. ulimit 内建命令对 -c 和 -f 选项使用 512 字节的块大小。

  72. 指定了 -v 选项的 unset 内建命令,如果试图取消设置一个只读或不可取消设置的变量,会返回一个致命错误,从而导致非交互式 shell 退出。

  73. 当被要求取消设置一个出现在命令之前的赋值语句中的变量时,unset 内建命令还会试图取消设置当前或前一个作用域中的同名变量。这实现了所要求的“如果被赋值的变量被该实用程序进一步修改,那么该实用程序所做的修改应当持续生效”这一行为。

  74. 当在 SIGCHLD 上设置了 trap 时,SIGCHLD 的到来不会中断 wait 内建命令并使其立即返回。对于每个退出的子进程,trap 命令都会运行一次。

  75. 在 wait 内建命令返回某个已退出后台进程的状态之后,Bash 会把该状态从这类状态列表中移除。

还有一些 POSIX 行为,即使在 POSIX 模式下 Bash 默认也不实现。具体而言:

  1. POSIX 要求单词拆分面向字节。也就是说,IFS 值中的每个字节都可能拆分一个单词,即使该字节是 IFS 中某个多字节字符的一部分,或者是单词中某个多字节字符的一部分。Bash 允许 IFS 的值中包含多字节字符,把合法的多字节字符视为单个分隔符,并且不会拆分一个合法的多字节字符,即使组成该字符的某个字节出现在 IFS 中。这是 POSIX 解释 1560,并由 issue 1924 进一步修改。

  2. 如果 FCEDIT 未设置,fc 内建命令会检查 $EDITOR 作为编辑历史条目的程序,而不是直接默认为 ed。如果 EDITOR 未设置,fc 使用 ed。

  3. 如上所述,Bash 要求启用 xpg_echo 选项,echo 内建命令才能完全符合规范。

通过在建时给 configure 指定 --enable-strict-POSIX-default 选项,可以把 Bash 配置为默认即符合 POSIX(参见 10.8 节“可选特性”)。

6.12 shell 兼容模式#

Bash-4.0 引入了 shell 兼容级别的概念,它被指定为 shopt 内建命令的一组选项(compat31、compat32、compat40、compat41 等等)。当前兼容级别只有一个——各选项互斥。兼容级别意在让用户在把脚本迁移到使用当前特性和行为的过程中,能够从较早版本中选择与较新版本不兼容的行为。它被设计为一种临时解决方案。

本节不涉及某个特定版本所固有的标准行为(例如,设置 compat32 意味着对正则表达式匹配运算符右侧的引用会引用单词中特殊的正则表达式字符,而这是 bash-3.2 及后续版本中的默认行为)。

如果用户启用了比如 compat32,它可能影响直到并包括当前兼容级别在内的其他兼容级别的行为。其思路是:每个兼容级别控制的是在该版本 Bash 中发生改变的行为,但该行为可能在此前版本中就已存在。例如,改用基于区域设置(locale)的比较来配合 [[ 命令是在 bash-4.1 中引入的,更早的版本使用基于 ASCII 的比较,所以启用 compat32 也会启用基于 ASCII 的比较。这种粒度对所有用途而言可能不够,因此用户应当谨慎使用兼容级别。要了解当前行为,请阅读特定特性的文档。

Bash-4.3 引入了一个新的 shell 变量:BASH_COMPAT。赋给该变量的值(像 4.2 这样的十进制版本号,或者像 42 这样的对应于 compatNN 选项的整数)决定兼容级别。

从 bash-4.4 开始,Bash 开始弃用较早的兼容级别。最终,这些选项将被移除,改用 BASH_COMPAT。

Bash-5.0 是最后一个为前一版本设置单独 shopt 选项的版本。在比 bash-5.0 更新的版本中,BASH_COMPAT 是控制兼容级别的唯一机制。

下表描述了由每个兼容级别设置所控制的行为改变。compatNN 这一标记用作把兼容级别设为 NN 的简写,可通过以下机制之一来设置。对于 bash-5.0 之前的版本,可以使用相应的 compatNN shopt 选项设置兼容级别。对于 bash-4.3 及更新版本,首选 BASH_COMPAT 变量,而对于 bash-5.1 及更新版本它是必需的。

compat31

compat40

compat41

compat42

compat43

compat44

compat50(通过 BASH_COMPAT 设置)

compat51(通过 BASH_COMPAT 设置)

unset 内建命令在给定像 a[@] 这样的参数时会取消设置数组 a。Bash-5.2 会取消设置键为 @ 的元素(关联数组),或者移除所有元素但不取消设置数组本身(索引数组)。

当 test -v 被给定 A[@] 这一参数、其中 A 是一个已存在的关联数组时,如果该数组有任何已设置的元素,它会返回真。Bash-5.2 会查找并报告名为 @ 的键。

compat52(通过 BASH_COMPAT 设置)

7 作业控制#

本章讨论什么是作业控制、它如何工作,以及 Bash 如何让你使用它的各项设施。

7.1 作业控制基础知识#

作业控制指的是有选择地停止(挂起)进程的执行,并在稍后继续(恢复)其执行的能力。用户通常通过操作系统内核的终端驱动程序与 Bash 共同提供的交互式界面来使用这一设施。

shell 把每个管道与一个作业关联起来。它维护一张当前正在执行的作业表,jobs 命令会显示这张表。每个作业都有一个作业号,jobs 会把它显示在方括号之间。作业号从 1 开始。当 Bash 异步启动一个作业时,它会打印类似下面的一行:

[1] 25647

这表示该作业是 1 号作业,并且与该作业相关联的管道中最后一个进程的进程 ID 是 25647。单个管道中的所有进程都是同一个作业的成员。Bash 以作业抽象作为作业控制的基础。

为便于实现作业控制的用户界面,每个进程都有一个进程组 ID,操作系统维护着当前终端进程组 ID 这一概念。这个终端进程组 ID 与所关联的控制终端相联系。

进程组 ID 相同的进程被称为属于同一个进程组。前台进程组(进程组 ID 等于当前终端进程组 ID 的那些进程)的成员会收到键盘产生的信号,例如 SIGINT。前台进程组中的进程被称为前台进程。后台进程是那些进程组 ID 与控制终端的不同的进程;这类进程不受键盘产生的信号的影响。只有前台进程才被允许从控制终端读取,或者按用户用 stty tostop 所指定的那样向控制终端写入。系统会向试图从终端读取(当 tostop 生效时则是写入终端)的后台进程发送 SIGTTIN(SIGTTOU)信号,除非该信号被捕获,否则会挂起该进程。

如果 Bash 所运行的操作系统支持作业控制,Bash 就包含使用它的各种设施。在进程运行时键入挂起字符(通常是 ^Z,即 Control-Z),会停止该进程并把控制权交回 Bash。键入延迟挂起字符(通常是 ^Y,即 Control-Y),会使该进程在试图从终端读取输入时停止,并把控制权交回 Bash。随后用户就可以操纵该作业的状态,使用 bg 命令让它在后台继续,使用 fg 命令让它在前台继续,或者使用 kill 命令杀死它。挂起字符会立即生效,并带有丢弃任何待处理输出和预输入的额外副作用。如果你想强制一个后台进程停止,或者停止一个不与你的终端会话相关联的进程,就用 kill 向它发送 SIGSTOP 信号。

在 shell 中有多种方式可以指代一个作业。% 字符引入一个作业规范(jobspec)。

作业号 n 可以写作 %n。也可以用启动该作业所用名称的前缀,或者用它命令行中出现的某个子串来指代一个作业。例如,%ce 指代命令名以 ce 开头的作业。另一方面,使用 %?ce 则指代命令行中包含字符串 ce 的任何作业。如果该前缀或子串匹配到多个作业,Bash 会报告错误。

符号 % 和 + 指代 shell 所认为的当前作业。单独一个 %(不带随附的作业规范)也指代当前作业。%- 指代前一个作业。当一个作业在后台启动、一个作业在前台停止,或者一个作业在后台被恢复时,它就成为当前作业。原先作为当前作业的那个作业就成为前一个作业。当当前作业终止时,前一个作业就成为当前作业。如果只有一个作业,%+ 和 %- 都可以用来指代该作业。在有关作业的输出中(例如 jobs 命令的输出),当前作业总是用 + 标记,前一个作业用 - 标记。

仅仅说出一个作业名,就可以把它带到前台:%1 是 fg %1 的同义词,把 1 号作业从后台带到前台。类似地,%1 & 在后台恢复 1 号作业,等价于 bg %1。

每当一个作业改变状态,shell 会立即得知。通常,Bash 会等到即将打印提示符时才通知用户作业状态的改变,以免打断其他输出,不过它会在列表中某个前台命令完成之后、在执行该列表中下一条命令之前,通知作业状态的改变。如果 set 内建命令的 -b 选项已启用,Bash 会立即报告状态改变(参见 4.3.1 节“Set 内建命令”)。Bash 会为每个终止的子进程执行 SIGCHLD 上的任何 trap。

当一个作业终止并且 Bash 就此通知用户时,Bash 会从作业表中移除该作业。它将不再出现在 jobs 的输出中,但只要把与该作业相关联的进程 ID 作为参数提供给 wait,wait 就会报告其退出状态。当该表为空时,作业号从 1 重新开始。

如果用户在有作业处于停止状态(或者,如果 checkjobs 选项已启用,则包括运行状态——参见 4.3.2 节“Shopt 内建命令”)时试图退出 Bash,shell 会打印一条警告消息;如果 checkjobs 选项已启用,还会列出这些作业及其状态。随后可以用 jobs 命令查看它们的状态。如果用户在没有中间执行任何命令的情况下立即再次试图退出,Bash 不会再次打印警告,并会终止任何处于停止状态的作业。

当 shell 使用 wait 内建命令等待一个作业或进程、并且作业控制已启用时,wait 会在该作业改变状态时返回。-f 选项使 wait 等到该作业或进程终止之后才返回。

7.2 作业控制内建命令#

bg#

bg [jobspec ...]

在后台恢复每个被挂起的作业 jobspec,就像它当初是以 & 启动的那样。如果未提供 jobspec,shell 使用它所认为的当前作业。除非在作业控制未启用时运行,或者在作业控制已启用时运行但某个 jobspec 未找到、或指定的作业是在未启用作业控制的情况下启动的,bg 返回零。

fg#

fg [jobspec]

在前台恢复作业 jobspec,并使其成为当前作业。如果未提供 jobspec,fg 恢复当前作业。返回状态是那个被放到前台的命令的状态;如果在作业控制被禁用时运行则返回非零,或者在作业控制已启用时运行但 jobspec 未指定一个合法作业、或指定的作业是在未启用作业控制的情况下启动的,也返回非零。

jobs#

jobs [-lnprs] [jobspec]
jobs -x command [arguments]

第一种形式列出活动作业。各选项含义如下:

如果提供了 jobspec,jobs 会把输出限制为关于该作业的信息。如果未提供 jobspec,jobs 列出所有作业的状态。除非遇到非法选项或提供了非法 jobspec,返回状态为零。

如果提供了 -x 选项,jobs 会把 command 或 arguments 中找到的任何 jobspec 替换为相应的进程组 ID,并执行 command,把 arguments 传给它,返回其退出状态。

kill#

kill [-s sigspec] [-n signum] [-sigspec] id [...]
kill -l|-L [exit_status]

把由 sigspec 或 signum 指定的信号发送给每个由 id 指名的进程。每个 id 可以是一个作业规范 jobspec 或进程 ID pid。sigspec 要么是像 SIGINT 这样大小写不敏感的信号名(带或不带 SIG 前缀),要么是信号编号;signum 是信号编号。如果未提供 sigspec 和 signum,kill 发送 SIGTERM。

-l 选项列出信号名。如果在提供 -l 时给出了任何参数,kill 会列出与这些参数相对应的信号名,并且返回状态为零。exit_status 是一个数字,指定一个信号编号或者某个被信号终止的进程的退出状态;如果提供了它,kill 会打印导致该进程终止的信号名。kill 假定进程退出状态大于 128;小于该值的一律视为信号编号。-L 选项等价于 -l。

如果至少有一个信号发送成功,返回状态为零;如果发生错误或遇到非法选项,则为非零。

wait#

wait [-fn] [-p varname] [id ...]

等待直到每个 id 所指定的子进程退出,并返回最后一个 id 的退出状态。每个 id 可以是一个进程 ID pid 或一个作业规范 jobspec;如果提供的是 jobspec,wait 会等待该作业中的所有进程。

如果未提供选项或 id,wait 等待所有正在运行的后台作业以及最近执行的进程替换(如果其进程 ID 与 $! 相同),并且返回状态为零。

如果提供了 -n 选项,wait 等待任意一个 id(或者,如果未提供 id,则等待任意一个作业或进程替换)完成并返回其退出状态。如果所提供的 id 都不是 shell 的子进程,或者未提供任何参数而 shell 又没有被等待的子进程,退出状态为 127。

如果提供了 -p 选项,wait 会把为其返回退出状态的那个作业的进程或作业标识符赋给由选项参数命名的变量 varname。该变量不能是只读的,并且在任何赋值之前会被初始化为未设置。这只有与 -n 选项一起使用时才有用。

在作业控制已启用时提供 -f 选项,会强制 wait 等待每个 id 终止之后才返回其状态,而不是在它改变状态时就返回。

如果没有任何 id 指的是 shell 的某个活动子进程,返回状态为 127。如果 wait 被信号中断,任何 varname 都将保持未设置,并且如前所述返回状态会大于 128(参见 3.7.6 节“信号”)。否则,返回状态是最后一个 id 的退出状态。

disown#

disown [-ar] [-h] [id ...]

不带选项时,从活动作业表中移除每个 id。每个 id 可以是一个作业规范 jobspec 或一个进程 ID pid;如果 id 是一个 pid,disown 就使用包含该 pid 的作业作为 jobspec。

如果提供了 -h 选项,disown 不会把每个 id 所对应的作业从作业表中移除,而是标记它们,使得 shell 在收到 SIGHUP 时不向该作业发送 SIGHUP。

如果未提供 id,-a 选项表示移除或标记所有作业;不带 id 参数的 -r 选项移除或标记正在运行的作业。如果未提供 id,并且既未提供 -a 也未提供 -r 选项,disown 移除或标记当前作业。

除非某个 id 未指定一个合法作业,返回值为 0。

suspend#

suspend [-f]

挂起此 shell 的执行,直到它收到 SIGCONT 信号。登录 shell,或者未启用作业控制的 shell,不能被挂起;-f 选项会覆盖这一点并强制挂起。除非 shell 是登录 shell,或者作业控制未启用且未提供 -f,返回状态为 0。

当作业控制未激活时,kill 和 wait 内建命令不接受 jobspec 参数。必须给它们提供进程 ID。

7.3 作业控制变量#

auto_resume

该变量控制 shell 如何与用户和作业控制交互。如果该变量存在,那么仅由单个单词组成、不带重定向的简单命令会被视为恢复已有作业的候选。这里不允许存在歧义;如果有多个作业以该单词开头或包含该单词,那么它会选择最近访问过的那个作业。在此上下文中,某个停止的作业的名称就是用来启动它的命令行,也就是 jobs 所显示的那样。如果该变量被设为值 exact,该单词必须与某个停止的作业的名称完全匹配;如果被设为 substring,该单词只需与某个停止的作业名称的某个子串匹配。substring 值提供了类似于 %?string 作业 ID 的功能(参见 7.1 节“作业控制基础知识”)。如果被设为任何其他值(例如 prefix),该单词必须是某个停止的作业名称的前缀;这提供了类似于 %string 作业 ID 的功能。

8 命令行编辑#

本章介绍 GNU 命令行编辑接口的基本特性。命令行编辑由 Readline 库提供,Bash 等若干不同的程序都使用该库。使用交互式 shell 时,命令行编辑默认启用,除非在启动 shell 时提供了 --noediting 选项。在使用 read 内建命令的 -e 选项时也会用到行编辑(参见 4.2 节“Bash 内建命令”)。默认情况下,行编辑命令与 Emacs 的命令类似;也可以使用 vi 风格的行编辑接口。可以随时用 set 内建命令的 -o emacs 或 -o vi 选项启用行编辑(参见 4.3.1 节“set 内建命令”),或用 set 的 +o emacs 或 +o vi 选项禁用行编辑。

8.1 行编辑简介#

以下段落采用 Emacs 风格来描述表示按键的记法。

C-k 读作 “Control-K”,描述在按住 Control 键的同时按下 k 键所产生的字符。

M-k 读作 “Meta-K”,描述在按住 Meta 键(如果你有的话)的同时按下 k 键(得到一个元字符)后再同时松开两者所产生的字符。在许多键盘上,Meta 键标为 ALT 或 Option。对于有两个标为 ALT 的键的键盘(通常位于空格键两侧),左侧的 ALT 一般被设为充当 Meta 键。其中一个 ALT 键也可能被配置为其他修饰键,例如用于输入带重音字符的 Compose 键。

在某些键盘上,Meta 修饰键会产生第八位(0200)被置位的字符。如果键盘允许,可以用 enable-meta-key 变量控制它是否这样做。在许多其他键盘上,终端或终端模拟器会把被元化的键转换为以 ESC 开头的键序列,如下一段所述。

如果你没有 Meta 或 ALT 键,也没有其他充当 Meta 键的键,一般可以先输入 ESC、再输入 k 来达到后一种效果。ESC 字符称为元前缀(meta prefix)。

上述两种过程都称为对 k 键进行元化(metafying)。

如果你的 Meta 键产生的是带 ESC 元前缀的键序列,可以通过设置 force-meta-prefix 变量,让你指定的 M- 键绑定(参见 8.3.1 节“Readline 初始化文件语法”中的“键绑定”)做同样的事。

M-C-k 读作 “Meta-Control-k”,描述对 C-k 进行元化所产生的字符。

此外,若干按键有自己的名称。具体来说,DEL、ESC、LFD、SPC、RET 和 TAB 在本文或初始化文件中出现时都代表它们自身(参见 8.3 节“Readline 初始化文件”)。如果你的键盘没有 LFD 键,输入 C-j 会输出相应的字符。RET 键在某些键盘上可能标为 Return 或 Enter。

8.2 Readline 交互#

在交互式会话中,你常常输入了很长的一行文本,然后才发现这一行的第一个单词拼错了。Readline 库提供了一组命令,让你在输入的同时操作文本,从而只需修正拼写错误,而不必重新输入该行的大部分内容。使用这些编辑命令,你可以把光标移到需要修正的位置,然后删除或插入修正后的文本。当你对这一行满意时,只需按 RET。你不必位于行尾才能按 RET;无论光标在该行中的位置如何,整行都会被接受。

8.2.1 Readline 基础必备#

要把字符输入到行中,直接输入它们即可。输入的字符会出现在光标所在处,然后光标向右移动一格。如果你输错了某个字符,可以用擦除字符回退并删除输错的字符。

有时你可能输错了一个字符,直到又输入了若干其他字符才发现。这时,你可以输入 C-b 把光标向左移动,然后修正错误。之后,可以用 C-f 把光标向右移动。

当你在一行中间添加文本时,会注意到光标右侧的字符被“推开”,为你插入的文本腾出空间。同样,当你删除光标左侧的文本时,光标右侧的字符会被“拉回”,填补因删除文本而产生的空白。以下是编辑输入行文本所需的基础必备操作:

C-b 向后移动一个字符。

C-f 向前移动一个字符。

DEL 或 Backspace 删除光标左侧的字符。

C-d 删除光标下方的字符。

可打印字符 在光标处把该字符插入行中。

C-_ 或 C-x C-u 撤销上一条编辑命令。你可以一直撤销到空行。

取决于你的配置,Backspace 键可能被设为删除光标左侧的字符,而 DEL 键被设为像 C-d 那样删除光标下方的字符,而不是删除光标左侧的字符。

8.2.2 Readline 移动命令#

上表描述了编辑输入行所需的最基本的按键。为方便起见,除 C-b、C-f、C-d 和 DEL 之外,还有许多其他命令可用。以下是在行内更快速地移动的一些命令。

C-a 移到行的开头。

C-e 移到行的末尾。

M-f 向前移动一个单词,此处的单词由字母和数字组成。

M-b 向后移动一个单词。

C-l 清屏,并在屏幕顶部重新显示当前行。

注意 C-f 是向前移动一个字符,而 M-f 是向前移动一个单词。一个约定俗成的惯例是:控制键序列作用于字符,而元键序列作用于单词。

8.2.3 Readline 剪切命令#

剪切(kill)文本是指把文本从行中删除,但又把它保存起来以备后用,通常是通过拽回(yank,即重新插入)把它放回行中。(“剪切”和“粘贴”是“kill”和“yank”较新的行话。)

如果某个命令的说明中写它“剪切”文本,那么你可以确信以后能够在另一个位置(或同一位置)把该文本取回。

当你使用剪切命令时,文本被保存在一个剪切环(kill ring)中。任意多次连续的剪切会把所有被剪切的文本一起保存,因此当你把它拽回时会得到全部内容。剪切环不与特定的行绑定;你在之前输入过的某行上剪切的文本,在以后输入另一行时仍可被拽回。

以下是剪切文本的命令列表。

C-k 剪切从当前光标位置到行尾的文本。

M-d 从光标剪切到当前单词的末尾;如果光标位于单词之间,则剪切到下一个单词的末尾。单词边界与 M-f 使用的边界相同。

M-DEL 从光标剪切到当前单词的开头;如果光标位于单词之间,则剪切到前一个单词的开头。单词边界与 M-b 使用的边界相同。

C-w 从光标剪切到前一个空白字符。这与 M-DEL 不同,因为单词边界不同。

以下是把文本拽回行中的方法。拽回(yank)是指把最近剪切的文本从剪切缓冲区复制到行中当前光标位置。

C-y 把最近剪切的文本拽回到光标处的缓冲区中。

M-y 轮转剪切环,并拽回新的环顶内容。只有当上一条命令是 C-y 或 M-y 时才能这样做。

8.2.4 Readline 参数#

你可以向 Readline 命令传递数字参数。有时参数用作重复次数,有时起作用的是参数的符号。如果你向一个通常向前执行的命令传递负数参数,该命令将向后执行。例如,若要向后剪切到行的开头,你可以输入 M-- C-k。

向命令传递数字参数的一般方法是在命令之前输入元数字。如果输入的第一个“数字”是减号(-),那么参数的符号将为负。一旦你输入了一个元数字来启动参数,就可以输入其余数字,然后是命令。例如,要给 C-d 命令一个 10 的参数,你可以输入 M-10 C-d,它将删除输入行中接下来的十个字符。

8.2.5 在历史记录中搜索命令#

Readline 提供了在命令历史记录中搜索包含指定字符串的行的命令(参见 9.1 节“Bash 历史设施”)。有两种搜索模式:增量式和非增量式。

增量式搜索在用户输入完搜索字符串之前就开始。随着搜索字符串的每个字符被输入,Readline 会显示历史记录中与目前已输入的字符串匹配的下一个条目。增量式搜索只需输入足以找到所需历史条目的字符数即可。使用 emacs 编辑模式时,输入 C-r 可在历史记录中向后搜索特定字符串。输入 C-s 则在历史记录中向前搜索。isearch-terminators 变量的值中所包含的字符用于终止增量式搜索。如果该变量没有被赋值,则 ESC 和 C-j 字符会终止增量式搜索。C-g 会中止增量式搜索并恢复原来的行。当搜索被终止时,包含搜索字符串的那个历史条目将成为当前行。

要在历史列表中查找其他匹配的条目,按相应方向输入 C-r 或 C-s。这会在历史记录中向后或向前搜索与目前已输入的搜索字符串匹配的下一个条目。任何绑定到某个 Readline 命令的其他键序列都会终止搜索并执行该命令。例如,RET 会终止搜索并接受该行,从而执行历史列表中的那条命令。移动命令会终止搜索,把最后找到的那一行设为当前行,并开始编辑。

Readline 会记住最后一次的增量式搜索字符串。如果在两次 C-r 之间没有输入任何定义新搜索字符串的字符,Readline 会使用所记住的搜索字符串。

非增量式搜索在开始搜索匹配的历史条目之前,先读取整个搜索字符串。搜索字符串可以由用户输入,也可以是当前行内容的一部分。

8.3 Readline 初始化文件#

尽管 Readline 库默认安装了一套类似 Emacs 的键绑定,但仍可以使用另一套键绑定。任何用户都可以通过把命令放入一个 inputrc 文件(按惯例放在自己的主目录中)来定制使用 Readline 的程序。该文件的名字取自 shell 变量 INPUTRC 的值。如果该变量未设置,默认为 ~/.inputrc。如果该文件不存在或无法读取,Readline 会查找 /etc/inputrc。也可以使用 bind 内建命令设置 Readline 键绑定和变量。参见 4.2 节“Bash 内建命令”。

当使用 Readline 库的程序启动时,Readline 会读取初始化文件,并设置其中包含的所有变量和键绑定。

此外,C-x C-r 命令会重新读取该初始化文件,从而纳入你可能对它所做的任何修改。

8.3.1 Readline 初始化文件语法#

Readline 初始化文件中只允许少数几种基本构造。空行被忽略。以 # 开头的行是注释。以 $ 开头的行表示条件构造(参见 8.3.2 节“条件初始化构造”)。其他行表示变量设置和键绑定。

变量设置#

你可以在初始化文件中用 set 命令修改 Readline 中各变量的值,从而改变 Readline 的运行时行为。语法很简单:

set variable value

例如,下面是从默认的类 Emacs 键绑定改为使用 vi 行编辑命令的方法:

set editing-mode vi

变量名和值(在适用时)在识别时不区分大小写。无法识别的变量名会被忽略。

布尔变量(即那些可以设为 on 或 off 的变量)在值为 null 或空、为 on(不区分大小写)或为 1 时被设为 on。任何其他值都会使该变量被设为 off。

bind -V 命令会列出当前 Readline 变量名及其值。参见 4.2 节“Bash 内建命令”。

大量运行时行为都可以通过以下变量改变。

active-region-start-color 一个字符串变量,用于控制在显示活动区域中的文本时的文本颜色和背景(参见下文 enable-active-region 的说明)。该字符串不得占用显示上的任何物理字符位置,因此它只应由终端转义序列组成。它在显示活动区域中的文本之前被输出到终端。每当终端类型改变时,该变量会被重置为默认值。默认值是把终端置为突出显示(standout)模式的字符串,取自终端的 terminfo 描述。一个示例值可能是 '\e[01;33m'。

active-region-end-color 一个字符串变量,用于“撤销” active-region-start-color 的效果,并在显示活动区域中的文本之后恢复“正常”的终端显示外观。该字符串不得占用显示上的任何物理字符位置,因此它只应由终端转义序列组成。它在显示活动区域中的文本之后被输出到终端。每当终端类型改变时,该变量会被重置为默认值。默认值是把终端从突出显示模式恢复的字符串,取自终端的 terminfo 描述。一个示例值可能是 '\e[0m'。

bell-style 控制当 Readline 想要让终端响铃时会发生什么。如果设为 'none',Readline 从不响铃。如果设为 'visible',Readline 在可用时使用可见铃声。如果设为 'audible'(默认值),Readline 会尝试让终端的铃响铃。

completion-prefix-display-length 对可能补全列表的公共前缀在显示时不作修改的最大字符长度。当设为大于零的值时,Readline 在显示可能的补全时会把长于该值的公共前缀替换为省略号。如果某个补全以句点开头,且 Readline 正在补全文件名,它会使用三个下划线来代替省略号。

completion-map-case 如果设为 'on',且启用了 completion-ignore-case,Readline 在执行不区分大小写的文件名匹配和补全时,会把连字符(-)和下划线(_)当作等同。默认值为 'off'。

completion-ignore-case 如果设为 'on',Readline 以不区分大小写的方式执行文件名匹配和补全。默认值为 'off'。

completion-display-width 执行补全时用于显示可能匹配项的屏幕列数。如果该值小于 0 或大于终端屏幕宽度,则被忽略。值为 0 会使匹配项每行显示一个。默认值为 -1。

comment-begin 由 insert-comment 命令插入到行首的字符串。默认值为 "#"。

colored-stats 如果设为 'on',Readline 会用不同的颜色显示可能的补全,以指示它们的文件类型。颜色定义取自 LS_COLORS 环境变量的值。默认值为 'off'。

colored-completion-prefix 如果设为 'on',在列出补全时,Readline 会用不同的颜色显示可能补全集合的公共前缀。颜色定义取自 LS_COLORS 环境变量的值。如果 LS_COLORS 中为自定义后缀 'readline-colored-completion-prefix' 定义了颜色,Readline 会用该颜色代替默认颜色来显示公共前缀。默认值为 'off'。

bind-tty-special-chars 如果设为 'on'(默认值),Readline 会尝试把内核终端驱动特殊对待的控制字符绑定到它们在 Readline 中的对应等价键。这些绑定会覆盖此处描述的默认 Readline 绑定。在 Bash 提示符下输入 stty -a 可以查看当前的终端设置,包括特殊的控制字符(通常是 cchars)。

completion-query-items 用于决定何时询问用户是否应显示可能性列表的可能补全数目。如果可能补全的数目大于或等于该值,Readline 会询问用户是否希望查看它们;否则 Readline 直接列出补全。该变量必须设为大于或等于零的整数值。值为零表示 Readline 从不询问;负值按零处理。默认上限为 100。

convert-meta 如果设为 'on',Readline 会把读到的第八位被置位的字符转换为 ASCII 键序列:清除第八位并加上 ESC 前缀,从而把它们转换为带元前缀的键序列。默认值为 'on',但如果区域设置包含编码中可能带有第八位被置位字节的字符,Readline 会把它设为 'off'。该变量依赖于 LC_CTYPE 区域设置类别,若区域设置发生变化,它可能随之改变。该变量也会影响键绑定;参见下文 force-meta-prefix 的说明。

disable-completion 如果设为 'on',Readline 会禁止单词补全。补全字符会像被映射到 self-insert 那样被插入到行中。默认值为 'off'。

echo-control-characters 当设为 'on' 时,在那些声明支持该特性的操作系统上,Readline 会回显与键盘产生的信号相对应的字符。默认值为 'on'。

editing-mode editing-mode 变量控制默认的键绑定集合。默认情况下,Readline 以 emacs 编辑模式启动,此时按键与 Emacs 最为相似。该变量可以设为 'emacs' 或 'vi'。

emacs-mode-string 如果启用了 show-mode-in-prompt 变量,当 emacs 编辑模式处于活动状态时,该字符串会紧接在主提示符最后一行的前面显示。该值像键绑定那样被展开,因此可用的有标准的元前缀、控制前缀以及反斜杠转义序列。'\1' 和 '\2' 转义分别开始和结束非打印字符序列,可用于把终端控制序列嵌入模式字符串中。默认值为 '@'。

enable-active-region point 指当前光标位置,mark 指保存的光标位置(参见 8.4.1 节“移动命令”)。point 与 mark 之间的文本称为区域(region)。当该变量设为 'on' 时,Readline 允许某些命令把该区域指定为活动区域。当区域处于活动状态时,Readline 会用 active-region-start-color 的值来高亮该区域中的文本,该值默认为启用终端突出显示模式的字符串。活动区域显示由 bracketed-paste 插入的文本,以及增量式和非增量式历史搜索找到的任何匹配文本。默认值为 'on'。

enable-bracketed-paste 当设为 'on' 时,Readline 会把终端配置为把每次粘贴作为单个字符串插入编辑缓冲区,而不是把每个字符都当作从键盘读入来处理。这称为把终端置于括号粘贴(bracketed paste)模式;它可防止 Readline 执行绑定到所粘贴文本中出现的键序列的任何编辑命令。默认值为 'on'。

enable-keypad 当设为 'on' 时,Readline 在被调用时尝试启用应用小键盘。某些系统需要这样做才能启用方向键。默认值为 'off'。

enable-meta-key 当设为 'on' 时,Readline 在被调用时会尝试启用终端声称支持的任何元修饰键。在许多终端上,Meta 键用于发送八位字符;该变量检查终端的一项能力,该能力表明:如果在输入字符时按住 Meta 键(得到元字符),终端可以启用和禁用把字符的第八位(0200)置位的模式。默认值为 'on'。

expand-tilde 如果设为 'on',Readline 在尝试单词补全时会尝试波浪号展开。默认值为 'off'。

force-meta-prefix 如果设为 'on',Readline 在绑定包含 \M- 或 Meta- 的键序列时会改变其行为(参见 8.3.1 节“Readline 初始化文件语法”中的“键绑定”),把形如 \M-C 或 Meta-C 的键序列转换为双字符序列 ESC C(即添加元前缀)。如果 force-meta-prefix 设为 'off'(默认值),Readline 会用 convert-meta 变量的值来决定是否执行这种转换:如果 convert-meta 为 'on',Readline 执行上述转换;如果为 'off',Readline 通过置位第八位(0200)把 C 转换为元字符。默认值为 'off'。

history-preserve-point 如果设为 'on',历史记录代码会尝试把 point(当前光标位置)放在用 previous-history 或 next-history 取回的每个历史行中的相同位置。默认值为 'off'。

history-size 设置历史列表中保存的历史条目最大数目。如果设为 0,则删除所有已存在的历史条目,且不再保存新条目。如果设为小于 0 的值,则历史条目数目不受限制。默认情况下,Bash 把历史条目的最大数目设为 HISTSIZE shell 变量的值。如果你试图把 history-size 设为非数字值,历史条目的最大数目将被设为 500。

horizontal-scroll-mode 把该变量设为 'on' 意味着,当正在编辑的行比屏幕宽度更长时,这些行的文本会在单个屏幕行上水平滚动,而不是折行到新的屏幕行。对于高度为 1 的终端,该变量会自动设为 'on'。默认情况下,该变量被设为 'off'。

input-meta 如果设为 'on',Readline 启用八位输入(也就是说,它不会清除所读字符的第八位),无论终端声称自己支持什么。默认值为 'off',但如果区域设置包含编码中可能带有第八位被置位字节的字符,Readline 会把它设为 'on'。该变量依赖于 LC_CTYPE 区域设置类别,若区域设置发生变化,它的值可能随之改变。名字 meta-flag 是 input-meta 的同义词。

isearch-terminators 用于终止增量式搜索、且随后不把该字符作为命令执行的字符所组成的字符串(参见 8.2.5 节“在历史记录中搜索命令”)。如果该变量没有被赋值,则 ESC 和 C-j 字符会终止增量式搜索。

keymap 设置 Readline 对键绑定命令所用的当前键映射的看法。内置键映射名有 emacs、emacs-standard、emacs-meta、emacs-ctlx、vi、vi-move、vi-command 和 vi-insert。vi 等价于 vi-command(vi-move 也是同义词);emacs 等价于 emacs-standard。应用程序可以添加额外的名字。默认值为 emacs;editing-mode 变量的值也会影响默认键映射。

keyseq-timeout 指定 Readline 在读入一个有歧义的键序列(即只用目前已读入的输入就能构成一个完整的键序列,或者可以再接受输入以构成更长的键序列)时等待一个字符的持续时间。如果 Readline 在超时时间内没有收到任何输入,它就使用较短但完整的键序列。Readline 用该值来判断当前输入源(默认为 rl_instream)上是否有可用输入。该值以毫秒为单位指定,因此值 1000 表示 Readline 会等待一秒以获取更多输入。如果该变量被设为小于或等于零的值,或设为非数字值,Readline 会一直等到按下另一个键才决定要完成哪个键序列。默认值为 500。

mark-directories 如果设为 'on',补全的目录名会附加一个斜杠。默认值为 'on'。

mark-modified-lines 当该变量设为 'on' 时,Readline 会在已被修改的历史行开头显示一个星号('*')。该变量默认为 'off'。

mark-symlinked-directories 如果设为 'on',补全的、指向目录的符号链接名称会附加一个斜杠,具体受 mark-directories 的值影响。默认值为 'off'。

match-hidden-files 该变量当设为 'on' 时,会强制 Readline 在执行文件名补全时也匹配名字以 . 开头的文件(隐藏文件)。如果设为 'off',用户必须在待补全的文件名中包含开头的 .。该变量默认为 'on'。

menu-complete-display-prefix 如果设为 'on',菜单式补全会在轮转整个列表之前先显示可能补全列表的公共前缀(该前缀可能为空)。默认值为 'off'。

output-meta 如果设为 'on',Readline 会直接显示第八位被置位的字符,而不是把它们显示为带元前缀的转义序列。默认值为 'off',但如果区域设置包含编码中可能带有第八位被置位字节的字符,Readline 会把它设为 'on'。该变量依赖于 LC_CTYPE 区域设置类别,若区域设置发生变化,它的值可能随之改变。

page-completions 如果设为 'on',Readline 会使用一个类似 more(1) 的内部分页器,一次显示一屏可能的补全。该变量默认为 'on'。

prefer-visible-bell 参见 bell-style。

print-completions-horizontally 如果设为 'on',Readline 会把补全项按字母顺序水平排序显示,而不是沿屏幕向下排列。默认值为 'off'。

revert-all-at-newline 如果设为 'on',Readline 在执行 accept-line 返回之前,会撤销对历史行的所有更改。默认情况下,历史行可能被修改,并在多次调用 readline() 之间保留各自的撤销列表。默认值为 'off'。

search-ignore-case 如果设为 'on',Readline 以不区分大小写的方式执行增量式和非增量式历史列表搜索。默认值为 'off'。

show-all-if-ambiguous 这会改变补全函数的默认行为。如果设为 'on',对于有不止一种可能补全的单词,会立即列出匹配项,而不是响铃。默认值为 'off'。

show-all-if-unmodified 这会以类似于 show-all-if-ambiguous 的方式改变补全函数的默认行为。如果设为 'on',对于有不止一种可能补全、且没有任何可能的部分补全(即各可能补全不共享公共前缀)的单词,会立即列出匹配项,而不是响铃。默认值为 'off'。

show-mode-in-prompt 如果设为 'on',会在提示符开头添加一个字符串以指示编辑模式:emacs、vi 命令模式或 vi 插入模式。模式字符串由用户设定(例如 emacs-mode-string)。默认值为 'off'。

skip-completed-text 如果设为 'on',这会改变把单个匹配项插入行中时的默认补全行为。它仅在一行中间执行补全时才有效。如果启用,Readline 不会插入补全结果中与正在补全的单词里 point 之后的字符相同的那些字符,因此光标之后的那部分单词不会被重复。例如,如果启用了它,当光标位于 Makefile 中第一个 e 之后时尝试补全,结果会是 Makefile 而不是 Makefilefile(假定只有一种可能的补全)。默认值为 'off'。

vi-cmd-mode-string 如果启用了 show-mode-in-prompt 变量,当 vi 编辑模式处于活动状态且处于命令模式时,该字符串会紧接在主提示符最后一行的前面显示。该值像键绑定那样被展开,因此可用的有标准的元前缀、控制前缀以及反斜杠转义序列。'\1' 和 '\2' 转义分别开始和结束非打印字符序列,可用于把终端控制序列嵌入模式字符串中。默认值为 '(cmd)'。

vi-ins-mode-string 如果启用了 show-mode-in-prompt 变量,当 vi 编辑模式处于活动状态且处于插入模式时,该字符串会紧接在主提示符最后一行的前面显示。该值像键绑定那样被展开,因此可用的有标准的元前缀、控制前缀以及反斜杠转义序列。'\1' 和 '\2' 转义分别开始和结束非打印字符序列,可用于把终端控制序列嵌入模式字符串中。默认值为 '(ins)'。

visible-stats 如果设为 'on',在列出可能的补全时,会在文件名后附加一个表示文件类型的字符。默认值为 'off'。

键绑定#

在初始化文件中控制键绑定的语法很简单。首先需要找到你想要更改的命令名。以下几节包含命令名、默认键绑定(如果有)以及该命令作用的简短说明的表格。

一旦知道了命令名,只需在初始化文件的某一行上放置你希望把该命令绑定到的键名、一个冒号,然后是命令名。键名与冒号之间不能有空格——那会被解释为键名的一部分。键名可以用不同的方式表达,取决于你觉得哪种最舒服。

除命令名外,Readline 还允许把键绑定到一个字符串(宏),当该键被按下时插入该字符串。宏与命令的区别在于:宏被括在单引号或双引号中。

bind -p 命令会以可以直接放入初始化文件的格式显示 Readline 函数名及其绑定。参见 4.2 节“Bash 内建命令”。

keyname: function-name or macro keyname 是用英文写出的键名。例如:

Control-u: universal-argument
Meta-Rubout: backward-kill-word
Control-o: "> output"

在上面的例子中,C-u 绑定到函数 universal-argument,M-DEL 绑定到函数 backward-kill-word,而 C-o 绑定到执行右侧所表达宏(也就是把文本 '>output' 插入到行中)。

这种键绑定语法可识别若干符号形式的字符名:DEL、ESC、ESCAPE、LFD、NEWLINE、RET、RETURN、RUBOUT(破坏性退格)、SPACE、SPC 和 TAB。

"keyseq": function-name or macro keyseq 与上面的 keyname 不同之处在于:通过把键序列放在双引号中,可以指定表示整个键序列的字符串。可以使用一些 GNU Emacs 风格的键转义,如下例所示,但不会识别任何特殊字符名。

"\C-u": universal-argument
"\C-x\C-r": re-read-init-file
"\e[11~": "Function Key 1"

在上面的例子中,C-u 再次绑定到函数 universal-argument(与第一个例子中一样),C-x C-r 绑定到函数 re-read-init-file,而 ESC [11~ 绑定到插入文本 Function Key 1。

在指定键序列时,可以使用以下 GNU Emacs 风格的转义序列:

\C- 控制前缀。

\M- 添加元前缀,或按下文 force-meta-prefix 所述把后面的字符转换为元字符(参见 8.3.1 节“Readline 初始化文件语法”中的“变量设置”)。

\e 转义字符。

\\ 反斜杠。

\" 双引号。

\' 单引号或撇号。

除 GNU Emacs 风格的转义序列外,还有第二组反斜杠转义可用:

\a 警报(响铃)

\b 退格

\d 删除

\f 换页

\n 换行

\r 回车

\t 水平制表符

\v 垂直制表符

\nnn 值为八进制值 nnn(一至三位数字)的八位字符。

\xHH 值为十六进制值 HH(一或两位十六进制数字)的八位字符。

在输入宏的文本时,必须使用单引号或双引号来表明这是一个宏定义。未加引号的文本被假定为函数名。上述反斜杠转义会在宏体中被展开。反斜杠会引用宏文本中的任何其他字符,包括 " 和 '。例如,下面的绑定会让 C-x \ 向行中插入单个 \:

"\C-x\\": "\\"

8.3.2 条件初始化构造#

Readline 实现了一种在精神上类似于 C 预处理器条件编译特性的机制,它允许根据测试结果来进行键绑定和变量设置。共有四个解析器指令可用。

$if $if 构造允许根据编辑模式、所使用的终端,或使用 Readline 的应用程序来进行绑定。测试的文本(在任何比较运算符之后)一直延伸到行尾;除非另有说明,不需要任何字符把它隔开。

$if 指令的 mode= 形式用于测试 Readline 是处于 emacs 模式还是 vi 模式。例如,它可以与 'set keymap' 命令配合使用,以便仅在 Readline 以 emacs 模式启动时才在 emacs-standard 和 emacs-ctlx 键映射中设置绑定。

term= 形式可用于包含特定于终端的键绑定,也许是为了绑定终端功能键所输出的键序列。= 右侧的词会同时与终端的全名以及终端名中第一个 - 之前的部分进行比较。例如,这使得 xterm 既能匹配 xterm,也能匹配 xterm-256color。

version version 测试可用于与特定的 Readline 版本进行比较。version 展开为当前的 Readline 版本。比较运算符集合包括 =(以及 ==)、!=、<=、>=、< 和 >。运算符右侧提供的版本号由一个主版本号、一个可选的小数点和可选的次版本号组成(例如 '7.1')。如果省略次版本号,则默认为 '0'。运算符可以借空白字符与字符串 version 以及版本号参数分隔开。下面的例子在所使用的 Readline 版本为 7.0 或更新时设置一个变量:

$if version >= 7.0
set show-mode-in-prompt on

application application 构造用于包含特定于应用程序的设置。每个使用 Readline 库的程序都会设置应用程序名,你可以测试某个特定值。这可用于把键序列绑定到对某个特定程序有用的函数。例如,下面的命令添加一个键序列,用于在 Bash 中引用当前或前一个单词:

$if Bash
# Quote the current or previous word
"\C-xq": "\eb\"\ef\""
$endif

variable variable 构造为 Readline 变量和值提供简单的相等测试。允许的比较运算符是 =、== 和 !=。变量名必须借空白字符与比较运算符分隔开;运算符可以借空白字符与右侧的值分隔开。字符串变量和布尔变量都可以测试。布尔变量必须针对值 on 和 off 进行测试。下面的例子等价于上文描述的 mode=emacs 测试:

$if editing-mode == emacs
$endif

$else 如果测试失败,则执行 $if 指令这一分支中的命令。

$endif 如前面的例子所示,这条命令终止一个 $if 命令。

$include 该指令接受一个文件名作为参数,并从该文件读取命令和键绑定。例如,下面的指令从 /etc/inputrc 读取:

$include /etc/inputrc

8.3.3 示例初始化文件#

下面是一个 inputrc 文件的例子。它演示了键绑定、变量赋值和条件语法。

# This file controls the behavior of line input editing for
# programs that use the GNU Readline library. Existing
# programs include FTP, Bash, and GDB.
#
# You can re-read the inputrc file with C-x C-r.
# Lines beginning with '#' are comments.
#
# First, include any system-wide bindings and variable
# assignments from /etc/Inputrc
$include /etc/Inputrc

set editing-mode emacs

$if mode=emacs

# Arrow keys in keypad mode
#
#"\M-OD": backward-char
#"\M-OC": forward-char
#"\M-OA": previous-history
#"\M-OB": next-history
#
# Arrow keys in ANSI mode
#
"\M-[D": backward-char
"\M-[C": forward-char
"\M-[A": previous-history
"\M-[B": next-history
#
# Arrow keys in 8 bit keypad mode
#
#"\M-\C-OD": backward-char
#"\M-\C-OC": forward-char
#"\M-\C-OA": previous-history
#"\M-\C-OB": next-history
#
# Arrow keys in 8 bit ANSI mode
#
#"\M-\C-[D": backward-char
#"\M-\C-[C": forward-char

#"\M-\C-[A": previous-history
#"\M-\C-[B": next-history

C-q: quoted-insert

$endif

# An old-style binding. This happens to be the default.
TAB: complete

# Macros that are convenient for shell interaction
$if Bash
# edit the path
"\C-xp": "PATH=${PATH}\e\C-e\C-a\ef\C-f"
# prepare to type a quoted word --
# insert open and close double quotes
# and move to just after the open quote
"\C-x\"": "\"\C-b"
# insert a backslash (testing backslash escapes
# in sequences and macros)
"\C-x'": "\'"
# Quote the current or previous word
"\C-xq": "\eb\"\ef\""
# Add a binding to refresh the line, which is unbound
"\C-xr": redraw-current-line
# Edit variable on current line.
"\M-\C-v": "\C-a\C-k$\C-y\M-\C-e\C-a\C-y"
$endif

# use a visible bell if one is available
set bell-style visible

# don't strip characters to 7 bits when reading
set input-meta on

# allow iso-latin1 characters to be inserted rather
# than converted to prefix-meta sequences
set convert-meta off

# display characters with the eighth bit set directly
# rather than as meta-prefixed characters
set output-meta on

# if there are 150 or more possible completions for a word
# ask whether or not the user wants to see all of them
set completion-query-items 150

# For FTP
$if Ftp
"\C-xg": "get \M-?"
"\C-xt": "put \M-?"
"\M-.": yank-last-arg
$endif

8.4 可绑定的 Readline 命令#

本节介绍可以绑定到按键序列的 Readline 命令。你可以通过执行 bind -P 列出你的按键绑定,或者使用更适合 inputrc 文件的更简洁格式 bind -p。(参见 4.2 节“Bash 内建命令”。)没有附带按键序列的命令名默认不绑定。

在下面的说明中,point 指当前光标位置,mark 指由 set-mark 命令保存的光标位置。point 与 mark 之间的文本称为 region(区域)。Readline 有“活动区域”(active region)的概念:当区域处于活动状态时,Readline 重新显示时会用 active-region-start-color 变量的值高亮该区域。enable-active-region 变量控制该功能的开关。有若干命令会把区域设为活动状态,这些命令在下面会标明。

8.4.1 用于移动的命令#

beginning-of-line (C-a) 移动到当前行的开头。在某些键盘上也可以绑定到 Home 键。

end-of-line (C-e) 移动到行的末尾。在某些键盘上也可以绑定到 End 键。

forward-char (C-f) 向前移动一个字符。在某些键盘上也可以绑定到右方向键。

backward-char (C-b) 向后移动一个字符。在某些键盘上也可以绑定到左方向键。

forward-word (M-f) 向前移动到下一个词的末尾。词由字母和数字组成。

backward-word (M-b) 向后移动到当前词或前一个词的开头。词由字母和数字组成。

shell-forward-word (M-C-f) 向前移动到下一个词的末尾。词以未被引用的 shell 元字符为界。

shell-backward-word (M-C-b) 向后移动到当前词或前一个词的开头。词以未被引用的 shell 元字符为界。

previous-screen-line () 尝试把 point 移到上一个物理屏幕行中相同的物理屏幕列。如果当前 Readline 行不超过一个物理行,或者 point 不大于提示符长度加上屏幕宽度,该命令不会产生预期效果。

next-screen-line () 尝试把 point 移到下一个物理屏幕行中相同的物理屏幕列。如果当前 Readline 行不超过一个物理行,或者当前 Readline 行的长度不大于提示符长度加上屏幕宽度,该命令不会产生预期效果。

clear-display (M-C-l) 清屏,并尽可能清除终端的回滚缓冲区,然后重绘当前行,把当前行留在屏幕顶部。

clear-screen (C-l) 清屏,然后重绘当前行,把当前行留在屏幕顶部。如果给定数值参数,则刷新当前行而不清屏。

redraw-current-line () 刷新当前行。默认情况下,该命令未绑定。

8.4.2 用于操作历史的命令#

accept-line (Newline 或 Return) 无论光标在何处都接受该行。如果该行非空,则根据 HISTCONTROL 和 HISTIGNORE 变量的设置把它加入历史列表。如果该行是修改过的历史行,则把该历史行恢复为原始状态。

previous-history (C-p) 在历史列表中“向后”移动,取出前一条命令。在某些键盘上也可以绑定到上方向键。

next-history (C-n) 在历史列表中“向前”移动,取出下一条命令。在某些键盘上也可以绑定到下方向键。

beginning-of-history (M-<) 移动到历史中的第一行。

end-of-history (M->) 移动到输入历史的末尾,即当前正在输入的那一行。

reverse-search-history (C-r) 从当前行开始向后搜索,必要时沿历史“向上”移动。这是增量搜索。该命令把区域设为匹配的文本并激活该区域。

forward-search-history (C-s) 从当前行开始向前搜索,必要时沿历史“向下”移动。这是增量搜索。该命令把区域设为匹配的文本并激活该区域。

non-incremental-reverse-search-history (M-p) 从当前行开始向后搜索,必要时沿历史“向上”移动,对用户提供的字符串执行非增量搜索。搜索字符串可以匹配历史行中的任意位置。

non-incremental-forward-search-history (M-n) 从当前行开始向前搜索,必要时沿历史“向下”移动,对用户提供的字符串执行非增量搜索。搜索字符串可以匹配历史行中的任意位置。

history-search-backward () 在历史中向后搜索当前行开头到 point 之间的字符串。搜索字符串必须匹配历史行的开头。这是非增量搜索。默认情况下,该命令未绑定,但在某些键盘上可以绑定到 Page Down 键。

history-search-forward () 在历史中向前搜索当前行开头到 point 之间的字符串。搜索字符串必须匹配历史行的开头。这是非增量搜索。默认情况下,该命令未绑定,但在某些键盘上可以绑定到 Page Up 键。

history-substring-search-backward () 在历史中向后搜索当前行开头到 point 之间的字符串。搜索字符串可以匹配历史行中的任意位置。这是非增量搜索。默认情况下,该命令未绑定。

history-substring-search-forward () 在历史中向前搜索当前行开头到 point 之间的字符串。搜索字符串可以匹配历史行中的任意位置。这是非增量搜索。默认情况下,该命令未绑定。

yank-nth-arg (M-C-y) 把前一条命令的第一个参数(通常是前一行的第二个词)插入 point 处。带参数 n 时,插入前一条命令的第 n 个词(前一条命令中的词从第 0 个词开始计数)。负参数则从后往前数插入第 n 个词。一旦计算出参数 n,该命令就使用历史展开功能提取第 n 个词,如同指定了 !n 历史展开一样。

yank-last-arg (M-. 或 M-_) 把前一条命令的最后一个参数(上一条历史条目的最后一个词)插入。带数值参数时,行为与 yank-nth-arg 完全相同。

连续调用 yank-last-arg 会沿历史列表向后移动,依次插入每一行的最后一个词(或第一次调用时参数指定的那个词)。对这些连续调用提供的任何数值参数决定沿历史移动的方向。负参数会切换沿历史移动的方向(向后或向前)。该命令使用历史展开功能提取最后一个词,如同指定了 !$ 历史展开一样。

operate-and-get-next (C-o) 接受当前行并把它返回给调用它的应用,如同输入了换行;同时从历史中取出相对于当前行的下一行用于编辑。如果提供数值参数,则指定使用哪条历史条目代替当前行。

fetch-history () 带数值参数时,从历史列表中取出该条目并把它设为当前行。不带参数时,向后移动到历史列表中的第一个条目。

8.4.3 用于修改文本的命令#

end-of-file (通常为 C-d) 表示文件结束的字符,例如由 stty 设置的字符。当行上没有任何字符、且 point 位于行首时读到该字符,Readline 会把它解释为输入结束并返回 EOF。

delete-char (C-d) 删除 point 处的字符。如果该功能绑定到与 tty EOF 字符相同的字符(C-d 通常如此),其效果见上文。在某些键盘上也可以绑定到 Delete 键。

backward-delete-char (Rubout) 删除光标后面的字符。数值参数表示剪切这些字符并把它们保存在剪切环上,而不是删除它们。

forward-backward-delete-char () 删除光标下的字符,除非光标位于行末,此时删除光标后面的字符。默认情况下,该命令不绑定到任何键。

quoted-insert (C-q 或 C-v) 把接下来输入的字符原样加入行中。例如,这就是插入像 C-q 这样的按键序列的方法。

self-insert (a、b、A、1、!、……) 插入所输入的字符。

bracketed-paste-begin () 该功能预期绑定到某些终端发送的“括号粘贴”(bracketed paste)转义序列,默认会分配这样的绑定。它允许 Readline 把粘贴进来的文本作为单个整体插入,而不把每个字符当作从键盘读取的那样处理。这些字符被插入时,如同每一个都绑定到 self-insert,而不是执行任何编辑命令。

括号粘贴会把区域(point 与 mark 之间的字符)设为插入的文本,并把该区域设为活动状态。

transpose-chars (C-t) 把光标前的字符拖到光标处字符的后面,同时把光标向前移动。如果插入点位于行末,则交换该行最后两个字符。负参数没有效果。

transpose-words (M-t) 把 point 前的词拖到 point 后的词之后,同时把 point 移过那个词。如果插入点位于行末,则交换该行最后两个词。

shell-transpose-words (M-C-t) 把 point 前的词拖到 point 后的词之后,同时把 point 移过那个词。如果插入点位于行末,则交换该行最后两个词。词边界与 shell-forward-word 和 shell-backward-word 相同。

upcase-word (M-u) 把当前(或后续)词转为大写。带负参数时,把前一个词转为大写,但不移动光标。

downcase-word (M-l) 把当前(或后续)词转为小写。带负参数时,把前一个词转为小写,但不移动光标。

capitalize-word (M-c) 把当前(或后续)词的首字母大写。带负参数时,把前一个词的首字母大写,但不移动光标。

overwrite-mode () 切换改写模式。带显式正数值参数时切换到改写模式。带显式非正数值参数时切换到插入模式。该命令只影响 emacs 模式;vi 模式的改写方式与此不同。每次调用 readline() 都从插入模式开始。

在改写模式下,绑定到 self-insert 的字符会替换 point 处的文本,而不是把文本向右推。绑定到 backward-delete-char 的字符会用空格替换 point 前的字符。

默认情况下,该命令未绑定,但在某些键盘上可以绑定到 Insert 键。

8.4.4 剪切与粘贴#

kill-line (C-k) 剪切从 point 到当前行末尾的文本。带负数值参数时,从光标向行首反向剪切。

backward-kill-line (C-x Rubout) 从光标向当前行开头反向剪切。带负数值参数时,从光标向行末正向剪切。

unix-line-discard (C-u) 从光标向当前行开头反向剪切。

kill-whole-line () 剪切当前行上的所有字符,不论 point 在哪里。默认情况下,该命令未绑定。

kill-word (M-d) 剪切从 point 到当前词末尾的文本;如果位于词之间,则剪切到下一个词的末尾。词边界与 forward-word 相同。

backward-kill-word (M-DEL) 剪切 point 后面的词。词边界与 backward-word 相同。

shell-kill-word (M-C-d) 剪切从 point 到当前词末尾的文本;如果位于词之间,则剪切到下一个词的末尾。词边界与 shell-forward-word 相同。

shell-backward-kill-word () 剪切 point 后面的词。词边界与 shell-backward-word 相同。

unix-word-rubout (C-w) 剪切 point 后面的词,以空白字符作为词边界,并把剪切的文本保存在剪切环上。

unix-filename-rubout () 剪切 point 后面的词,以空白字符和斜杠字符作为词边界,并把剪切的文本保存在剪切环上。

delete-horizontal-space () 删除 point 周围的所有空格和制表符。默认情况下,该命令未绑定。

kill-region () 剪切当前区域中的文本。默认情况下,该命令未绑定。

copy-region-as-kill () 把区域中的文本复制到剪切缓冲区,以便可以立即粘贴。默认情况下,该命令未绑定。

copy-backward-word () 把 point 前的词复制到剪切缓冲区。词边界与 backward-word 相同。默认情况下,该命令未绑定。

copy-forward-word () 把 point 后的词复制到剪切缓冲区。词边界与 forward-word 相同。默认情况下,该命令未绑定。

yank (C-y) 把剪切环顶部的文本粘贴到 point 处的缓冲区中。

yank-pop (M-y) 旋转剪切环,并粘贴新的顶部内容。只有当上一条命令是 yank 或 yank-pop 时才能这样做。

8.4.5 指定数值参数#

digit-argument (M-0、M-1、…… M--) 把该数字加入已经累积的参数中,或者开始一个新参数。M-- 开始一个负参数。

universal-argument () 这是指定参数的另一种方式。如果该命令后面跟着一个或多个数字(可以带一个前导负号),这些数字就定义了参数。如果该命令后面跟着数字,再次执行 universal-argument 会结束数值参数,除此之外会被忽略。作为一种特殊情况,如果该命令后面紧跟一个既不是数字也不是负号的字符,则下一条命令的参数计数乘以 4。参数计数初始为 1,因此第一次执行该功能会令参数计数为 4,第二次为 16,依此类推。默认情况下,该命令不绑定到任何键。

8.4.6 让 Readline 替你输入#

complete (TAB) 尝试对 point 之前的文本执行补全。实际执行的补全依应用而定。Bash 尝试补全时,首先检查该命令词是否有任何可编程补全(参见 8.6 节“可编程补全”),否则依次把该文本当作变量(如果文本以 $ 开头)、用户名(如果文本以 ~ 开头)、主机名(如果文本以 @ 开头)或命令(包括别名、函数和内建命令)来处理。如果这些都没有产生匹配,就回退到文件名补全。

possible-completions (M-?) 列出 point 之前文本的所有可能补全。显示补全时,Readline 依次使用 completion-display-width 的值、环境变量 COLUMNS 的值或屏幕宽度来设置用于显示的列数。

insert-completions (M-*) 插入 possible-completions 会生成的所有针对 point 之前文本的补全,以空格分隔。

menu-complete () 与 complete 类似,但用可能补全列表中的单个匹配项替换待补全的词。反复执行 menu-complete 会在可能补全列表中逐步前进,依次插入每个匹配项。到达补全列表末尾时,menu-complete 会响铃(受 bell-style 设置影响)并恢复原始文本。参数 n 会在匹配列表中向前移动 n 个位置;负参数则在列表中向后移动。该命令预期绑定到 TAB,但默认未绑定。

menu-complete-backward () 与 menu-complete 相同,但在可能补全列表中向后移动,就像给 menu-complete 传了负参数一样。该命令默认未绑定。

export-completions () 如上所述对 point 之前的词执行补全,并按以下格式把可能补全列表写入 Readline 的输出流,各项信息各占一行:

如果没有匹配项,第一行会是“0”,并且该命令在 S:E 之后不打印任何输出。如果只有一个匹配项,则打印包含它的一行。如果有多个匹配项,则在 S:E 之后的第一行打印这些匹配项的公共前缀(可能为空),然后在后续行打印各个匹配项。在这种情况下,N 将包含带有公共前缀的那一行。

用户或应用应当能够容忍出现空行的情况。其意图是:用户或应用在包含 S:E 的那一行之后读取 N 行以获得匹配列表。该命令默认未绑定。

delete-char-or-list () 如果不在行首或行末,则删除光标下的字符(与 delete-char 相同)。在行末时,其行为与 possible-completions 完全相同。该命令默认未绑定。

complete-filename (M-/) 尝试对 point 之前的文本执行文件名补全。

possible-filename-completions (C-x /) 列出 point 之前文本的可能补全,把它当作文件名处理。

complete-username (M-~) 尝试对 point 之前的文本执行补全,把它当作用户名处理。

possible-username-completions (C-x ~) 列出 point 之前文本的可能补全,把它当作用户名处理。

complete-variable (M-$) 尝试对 point 之前的文本执行补全,把它当作 shell 变量处理。

possible-variable-completions (C-x $) 列出 point 之前文本的可能补全,把它当作 shell 变量处理。

complete-hostname (M-@) 尝试对 point 之前的文本执行补全,把它当作主机名处理。

possible-hostname-completions (C-x @) 列出 point 之前文本的可能补全,把它当作主机名处理。

complete-command (M-!) 尝试对 point 之前的文本执行补全,把它当作命令名处理。命令补全依次尝试把该文本与别名、保留字、shell 函数、shell 内建命令以及最后与可执行文件名匹配。

possible-command-completions (C-x !) 列出 point 之前文本的可能补全,把它当作命令名处理。

dynamic-complete-history (M-TAB) 尝试对 point 之前的文本执行补全,把该文本与历史列表条目比较以寻找可能的补全匹配。

dabbrev-expand () 尝试对 point 之前的文本执行菜单补全,把该文本与历史列表中的行比较以寻找可能的补全匹配。

complete-into-braces (M-{) 执行文件名补全,并把可能补全的列表用花括号括起来插入,使该列表可供 shell 使用(参见 3.5.1 节“花括号展开”)。

8.4.7 键盘宏#

start-kbd-macro (C-x () 开始把输入的字符保存到当前键盘宏中。

end-kbd-macro (C-x)) 停止把输入的字符保存到当前键盘宏中,并保存该定义。

call-last-kbd-macro (C-x e) 重新执行最后定义的键盘宏,使宏中的字符看起来像是从键盘输入的。

print-last-kbd-macro () 以适合 inputrc 文件的格式打印最后定义的键盘宏。

8.4.8 一些杂项命令#

re-read-init-file (C-x C-r) 读入 inputrc 文件的内容,并采纳其中找到的任何绑定或变量赋值。

abort (C-g) 中止当前编辑命令并使终端的铃响(受 bell-style 设置影响)。

do-lowercase-version (M-A、M-B、M-x、……) 如果带 Meta 前缀的字符 x 是大写,则运行绑定到相应的带 Meta 前缀的小写字符的命令。如果 x 本身已经是小写,则行为未定义。

prefix-meta (ESC) 给接下来输入的字符加上 Meta 前缀。输入 ESC f 等同于输入 M-f。

undo (C-_ 或 C-x C-u) 增量撤销,每行分别记忆。

revert-line (M-r) 撤销对该行所做的所有修改。这相当于执行足够多次 undo 命令以回到初始状态。

tilde-expand (M-&) 对当前词执行波浪号展开。

set-mark (C-@) 把 mark 设为 point。如果提供数值参数,则把 mark 设为该位置。

exchange-point-and-mark (C-x C-x) 交换 point 与 mark。把当前光标位置设为保存的位置,然后把 mark 设为原来的光标位置。

character-search-backward (M-C-]) 读取一个字符并把 point 移到该字符上一次出现的位置。负参数则搜索之后出现的位置。

skip-csi-sequence () 读取足够的字符以消费多键序列,例如为 Home 和 End 这类键定义的序列。CSI 序列以控制序列引导符(Control Sequence Indicator,CSI)开始,通常是 ESC [。如果该序列绑定到 "\e[",则产生 CSI 序列的键除非显式绑定到某个 Readline 命令,否则不会产生任何效果,而不会把杂散字符插入编辑缓冲区。该命令默认未绑定,但通常会绑定到 ESC [。

insert-comment (M-#) 不带数值参数时,把 comment-begin 变量的值插入当前行的开头。如果提供数值参数,该命令表现为开关:如果行开头的字符与 comment-begin 的值不匹配,则插入该值;否则从行开头删除 comment-begin 中的字符。无论哪种情况,该行都会如同输入了换行一样被接受。comment-begin 的默认值会使该命令把当前行变成 shell 注释。如果数值参数导致注释字符被移除,该行将由 shell 执行。

dump-functions () 把所有函数及其按键绑定打印到 Readline 输出流。如果提供数值参数,输出的格式会使其可以被放入 inputrc 文件。该命令默认未绑定。

dump-variables () 把所有可设置的变量及其值打印到 Readline 输出流。如果提供数值参数,输出的格式会使其可以被放入 inputrc 文件。该命令默认未绑定。

dump-macros () 把所有绑定到宏的 Readline 按键序列以及它们输出的字符串打印到 Readline 输出流。如果提供数值参数,输出的格式会使其可以被放入 inputrc 文件。该命令默认未绑定。

execute-named-command (M-x) 从输入中读取一个可绑定的 Readline 命令名,并执行它所绑定到的函数,如同它绑定到的按键序列出现在输入中一样。如果给该功能提供数值参数,它会把该参数传给所执行的函数。

spell-correct-word (C-x s) 对当前词执行拼写纠正,把它当作目录或文件名,方式与 cdspell shell 选项相同。词边界与 shell-forward-word 使用的边界相同。

glob-complete-word (M-g) 把 point 之前的词当作路径名展开的模式,隐式在末尾追加一个星号,然后用该模式生成匹配的文件名列表以供可能的补全。

glob-expand-word (C-x *) 把 point 之前的词当作路径名展开的模式,并插入匹配的文件名列表,替换该词。如果提供数值参数,则在路径名展开之前追加一个 *。

glob-list-expansions (C-x g) 显示 glob-expand-word 会生成的那些展开的列表,并重新显示该行。如果提供数值参数,则在路径名展开之前追加一个 *。

shell-expand-line (M-C-e) 通过执行 shell 词展开来展开该行。它会执行别名展开和历史展开、$'string' 和 $"string" 引用、波浪号展开、参数和变量展开、算术展开、命令替换和进程替换、单词拆分以及引号移除。显式提供参数会抑制命令替换和进程替换。

history-expand-line (M-^) 对当前行执行历史展开。

magic-space () 对当前行执行历史展开并插入一个空格(参见 9.3 节“历史交互”)。

alias-expand-line () 对当前行执行别名展开(参见 6.6 节“别名”)。

history-and-alias-expand-line () 对当前行执行历史展开和别名展开。

insert-last-argument (M-. 或 M-_) yank-last-arg 的同义词。

edit-and-execute-command (C-x C-e) 对当前命令行调用编辑器,并把结果作为 shell 命令执行。Bash 依次尝试调用 $VISUAL、$EDITOR 和 emacs 作为编辑器。

display-shell-version (C-x C-v) 显示当前 Bash 实例的版本信息。

8.5 Readline vi 模式#

虽然 Readline 库并没有一整套完整的 vi 编辑功能,但它包含的功能足以对行进行简单的编辑。Readline 的 vi 模式的行为符合 POSIX 标准中 sh 描述的规定。

你可以使用 set -o emacs 和 set -o vi 命令(参见 4.3.1 节“set 内建命令”)在 emacs 和 vi 编辑模式之间交互式地切换。Readline 默认为 emacs 模式。

在 vi 模式下输入一行时,你已经处于“插入”模式,就像输入了 i 一样。按 ESC 会切换到“命令”模式,在该模式下你可以用标准的 vi 移动键编辑该行的文本,用 k 移动到之前的历史行、用 j 移动到之后的行,等等。

8.6 可编程补全#

当用户尝试对某个命令或某个命令的参数进行词补全,而该命令已经用 complete 内建命令定义了补全规范(compspec)时(参见 8.7 节“可编程补全内建命令”),Readline 会调用可编程补全功能。

首先,Bash 识别命令名。如果为该命令定义了 compspec,则使用该 compspec 生成该词的可能补全列表。如果命令词是空字符串(在空行开头尝试补全),Bash 使用任何用 -E 选项定义的 compspec 来补全。complete 的 -I 选项表示命令词是行上第一个非赋值词,或者位于诸如 ; 或 | 这样的命令分隔符之后。这通常表示命令名补全。

如果命令词是一个完整路径名,Bash 首先查找针对该完整路径名的 compspec。如果没有针对该完整路径名的 compspec,Bash 尝试为最后一个斜杠之后的部分查找 compspec。如果这些查找没有找到 compspec,或者该命令词没有 compspec,Bash 就使用任何用 -D 选项定义的 compspec 作为默认补全。如果没有默认 compspec,Bash 作为最后手段对命令词执行别名展开,并尝试为任何成功展开后得到的命令词查找 compspec。

如果没有找到 compspec,Bash 执行上文描述的默认补全(参见 8.4.6 节“用于补全的命令”)。否则,一旦找到 compspec,Bash 就用它生成匹配词列表。

首先,Bash 执行 compspec 指定的动作。这只会返回所补全的词的前缀形式的匹配。当使用 -f 或 -d 选项进行文件名或目录名补全时,Bash 使用 shell 变量 FIGNORE 过滤匹配。FIGNORE 的说明参见 5.2 节“Bash 变量”。

接下来,可编程补全生成由作为 -G 选项参数提供的路径名展开模式所指定的匹配。该模式生成的词不必匹配正在补全的词。Bash 使用 FIGNORE 变量过滤匹配,但不使用 GLOBIGNORE shell 变量。

接下来,补全会考虑作为 -W 选项参数指定的字符串。该字符串首先用 IFS 特殊变量中的字符作为分隔符进行拆分。这会遵循字符串内的 shell 引用,从而提供一种机制,使词中可以包含 shell 元字符或 IFS 值中的字符。然后每个词都按上文所述使用花括号展开、波浪号展开、参数和变量展开、命令替换和算术展开进行展开(参见 3.5 节“shell 展开”)。结果按上文描述的规则进行拆分(参见 3.5.7 节“单词拆分”)。展开结果会与正在补全的词做前缀匹配,匹配的词成为可能补全。

在这些匹配生成之后,Bash 执行用 -F 和 -C 选项指定的任何 shell 函数或命令。当调用该命令或函数时,Bash 按上文所述为 COMP_LINE、COMP_POINT、COMP_KEY 和 COMP_TYPE 变量赋值(参见 5.2 节“Bash 变量”)。如果调用的是 shell 函数,Bash 还会设置 COMP_WORDS 和 COMP_CWORD 变量。当调用该函数或命令时,第一个参数($1)是正在补全其参数的命令名,第二个参数($2)是正在补全的词,第三个参数($3)是当前命令行上该词之前的那个词。不会针对正在补全的词对生成的补全做任何过滤;该函数或命令在生成匹配时拥有完全的自由,匹配不必是该词的前缀。

任何用 -F 指定的函数会最先被调用。该函数可以使用任何 shell 设施(包括下文所述的 compgen 和 compopt 内建命令,参见 8.7 节“可编程补全内建命令”)来生成匹配。它必须把可能补全放入 COMPREPLY 数组变量,每个数组元素一个。

接下来,用 -C 选项指定的任何命令会在等同于命令替换的环境中调用。它应当把补全列表逐个打印到标准输出,每行一个。必要时,反斜杠可以转义换行。这些补全会被加入可能补全集合。

在生成所有可能补全之后,Bash 会把用 -X 选项指定的任何过滤器应用到列表中的补全上。该过滤器是用于路径名展开的模式;模式中的 & 会被替换为正在补全的词的文本。字面意义上的 & 可以用反斜杠转义;在尝试匹配之前反斜杠会被移除。任何匹配该模式的补全都会从列表中移除。前导的 ! 会取反该模式;在这种情况下,Bash 会移除任何不匹配该模式的补全。如果启用了 nocasematch shell 选项(参见 4.3.2 节“shopt 内建命令”中对 shopt 的说明),Bash 会在匹配时不考虑字母字符的大小写。

最后,可编程补全把用 -P 和 -S 选项分别指定的前缀和后缀加到每个补全上,并把结果作为可能补全列表返回给 Readline。

如果先前执行的动作没有生成任何匹配,并且在定义 compspec 时给 complete 提供了 -o dirnames 选项,Bash 会尝试目录名补全。

如果在定义 compspec 时给 complete 提供了 -o plusdirs 选项,Bash 会尝试目录名补全并把任何匹配加入可能补全集合。

默认情况下,如果找到了 compspec,它生成的任何内容都会作为完整的可能补全集合返回给补全代码。Bash 的默认补全和 Readline 默认的文件名补全都被禁用。如果在定义 compspec 时给 complete 提供了 -o bashdefault 选项,而 compspec 没有生成匹配,Bash 会尝试它的默认补全。如果 compspec 以及(如果尝试了)默认 Bash 补全都没有生成匹配,并且在定义 compspec 时给 complete 提供了 -o default 选项,则可编程补全会执行 Readline 的默认补全。

提供给 complete 和 compopt 的选项可以控制 Readline 如何处理补全。例如,-o fullquote 选项告诉 Readline 像对待文件名那样引用匹配项。详情参见对 complete 的说明(参见 8.7 节“可编程补全内建命令”)。

当某个 compspec 表示它需要目录名补全时,可编程补全函数会强制 Readline 对作为目录符号链接的已补全名称追加一个斜杠,这取决于 mark-directories Readline 变量的值,而不管 mark-symlinked-directories Readline 变量的设置如何。

对补全进行动态修改有一些支持。当与用 -D 指定的默认补全结合使用时,这最为有用。作为补全函数执行的 shell 函数可以通过返回退出状态 124 来表示应当重试补全。如果某个 shell 函数返回 124,并且改变了与正在尝试补全的命令相关联的 compspec(执行该函数时作为第一个参数提供),可编程补全会从头开始,尝试为该命令查找新的 compspec。这可用于在尝试补全时动态构建一组补全,而不是一次性全部加载。

例如,假设有一个 compspec 库,每个 compspec 保存在与命令名对应的文件中,下面的默认补全函数会动态加载补全:

_completion_loader()
{
    . "/etc/bash_completion.d/$1.sh" >/dev/null 2>&1 && return 124
}
complete -D -F _completion_loader -o bashdefault -o default

8.7 可编程补全内建命令#

有三种内建命令可用于操作可编程补全设施:一种用于指定某个特定命令的参数应如何补全,另外两种用于在补全进行过程中修改补全行为。

compgen#

compgen [-V varname] [option] [word]

根据选项为 word 生成可能的补全匹配项;这些选项可以是 complete 内建命令所接受的任何选项,但 -p、-r、-D、-E 和 -I 除外;并把匹配项写到标准输出。

如果提供了 -V 选项,compgen 会把生成的补全项存入索引数组变量 varname,而不是写到标准输出。使用 -F 或 -C 选项时,可编程补全设施所设置的各种 shell 变量虽然可用,但不会有有用的值。

匹配项的生成方式,与可编程补全代码用相同的标志直接从某个补全规范生成时相同。如果指定了 word,则只显示或存储那些与 word 匹配的补全项。

除非提供了无效选项,或者没有生成任何匹配项,否则返回值为真。

complete#

complete [-abcdefgjkSUV] [-o comp-option] [-DEI] [-A action]
[-G globPat] [-W wordlist] [-F function] [-C command]
[-X filterPat] [-P prefix] [-S suffix] name [name ...]
complete -pr [-DEI] [name ...]

指定每个 name 的参数应如何补全。

如果提供了 -p 选项,或者未提供任何选项或 name,则以可重新用作输入的方式打印现有的补全规范。-r 选项会移除每个 name 的补全规范;如果未提供 name,则移除所有补全规范。

-D 选项表示所提供的其他选项和动作应作用于“默认”命令补全,即尝试为之前未定义补全的命令进行补全。-E 选项表示所提供的其他选项和动作应作用于“空”命令补全,即尝试在空行上进行补全。-I 选项表示所提供的其他选项和动作应作用于对行首第一个非赋值单词的补全,或者对 ; 或 | 等命令分隔符之后的单词的补全,这通常是命令名补全。如果提供了多个选项,则 -D 选项优先于 -E,且二者都优先于 -I。如果提供了 -D、-E 或 -I 中的任何一个,则其他任何 name 参数都会被忽略;这些补全只适用于该选项所指定的情形。

在尝试进行单词补全时应用这些补全规范的过程已在上文描述(参见 8.6 节“可编程补全”)。

其他选项(如果指定)具有以下含义。-G、-W 和 -X 选项的参数(必要时还有 -P 和 -S 选项的参数)应加引用,以便在调用 complete 内建命令之前保护它们免于展开。

-o comp-option comp-option 控制 compspec 行为的若干方面,而不仅仅是生成补全项。comp-option 可以是下列之一:

bashdefault 如果 compspec 未生成任何匹配项,则执行其余默认的 Bash 补全。

default 如果 compspec 未生成任何匹配项,则使用 Readline 的默认文件名补全。

dirnames 如果 compspec 未生成任何匹配项,则执行目录名补全。

filenames 告知 Readline 该 compspec 生成的是文件名,以便它能执行任何特定于文件名的处理(例如为目录名添加斜杠、引用特殊字符或抑制尾随空格)。此选项旨在与用 -F 指定的 shell 函数一起使用。

fullquote 告知 Readline 引用所有已补全的单词,即使它们不是文件名。

noquote 告知 Readline 在已补全的单词是文件名时不要引用它们(引用文件名是默认行为)。

nosort 告知 Readline 不要按字母顺序对可能的补全列表排序。

nospace 告知 Readline 不要为在行尾补全的单词追加空格(这是默认行为)。

plusdirs 在生成 compspec 定义的任何匹配项之后,尝试进行目录名补全,并把任何匹配项添加到其他动作的结果中。

-A action action 可以为下列之一,用于生成可能的补全项列表:

alias 别名名称。也可指定为 -a。

arrayvar 数组变量名。

binding Readline 键绑定名称(参见 8.4 节“可绑定的 Readline 命令”)。

builtin shell 内建命令的名称。也可指定为 -b。

command 命令名。也可指定为 -c。

directory 目录名。也可指定为 -d。

disabled 已禁用的 shell 内建命令的名称。

enabled 已启用的 shell 内建命令的名称。

export 已导出的 shell 变量的名称。也可指定为 -e。

file 文件名和目录名,类似于 Readline 的文件名补全。也可指定为 -f。

function shell 函数的名称。

group 组名。也可指定为 -g。

helptopic help 内建命令所接受的帮助主题(参见 4.2 节“Bash 内建命令”)。

hostname 主机名,取自 HOSTFILE shell 变量所指定的文件(参见 5.2 节“Bash 变量”)。

job 作业名称(如果作业控制处于活动状态)。也可指定为 -j。

keyword shell 保留字。也可指定为 -k。

running 正在运行的作业的名称(如果作业控制处于活动状态)。

service 服务名称。也可指定为 -s。

setopt set 内建命令 -o 选项的有效参数(参见 4.3.1 节“set 内建命令”)。

shopt shopt 内建命令所接受的 shell 选项名(参见 4.2 节“Bash 内建命令”)。

signal 信号名称。

stopped 已停止的作业的名称(如果作业控制处于活动状态)。

user 用户名。也可指定为 -u。

variable 所有 shell 变量的名称。也可指定为 -v。

-C command command 在子 shell 环境中执行,其输出用作可能的补全项。参数的传递方式与 -F 选项相同。

-F function shell 函数 function 在当前 shell 环境中执行。执行时,第一个参数($1)是正在为其参数进行补全的命令名,第二个参数($2)是正在补全的单词,第三个参数($3)是正在补全单词之前的单词,如上文所述(参见 8.6 节“可编程补全”)。当 function 结束时,可编程补全会从 COMPREPLY 数组变量的值中取回可能的补全项。

-G globpat 展开文件名展开模式 globpat,以生成可能的补全项。

-P prefix 在所有其他选项都已应用之后,把 prefix 添加到每个可能补全项的开头。

-S suffix 在所有其他选项都已应用之后,把 suffix 追加到每个可能补全项的末尾。

-W wordlist 以 IFS 特殊变量中的字符为分隔符拆分 wordlist,并展开所得的每个单词。wordlist 内会遵循 shell 引用,以便提供一种机制让这些单词可以包含 shell 元字符或 IFS 值中的字符。可能的补全项是所得列表中与正在补全的单词的前缀相匹配的那些成员。

-X filterpat filterpat 是用于文件名展开的模式。它作用于前面选项和参数所生成的可能的补全项列表,列表中每个匹配 filterpat 的补全项都会被移除。filterpat 开头的 ! 会对该模式取反;在这种情况下,任何不匹配 filterpat 的补全项都会被移除。

除非出现以下情况,否则返回值为真:提供了无效选项;提供了除 -p、-r、-D、-E 或 -I 之外的选项却未提供 name 参数;试图移除某个不存在补全规范的 name 的补全规范;或者在添加补全规范时发生错误。

compopt#

根据选项为每个 name 修改补全选项;如果未提供 name,则为当前正在执行的补全修改补全选项。如果未给出任何选项,则显示每个 name 或当前补全的补全选项。option 的可能取值就是上文所述的 complete 内建命令的有效取值。

-D 选项表示所提供的其他选项应作用于“默认”命令补全;-E 选项表示所提供的其他选项应作用于“空”命令补全;-I 选项表示所提供的其他选项应作用于对行首单词的补全。这些的确定方式与 complete 内建命令相同。

如果提供了多个选项,则 -D 选项优先于 -E,且二者都优先于 -I。

除非出现以下情况,否则返回值为真:提供了无效选项;试图修改某个不存在补全规范的 name 的选项;或者发生输出错误。

8.8 可编程补全示例#

除了 complete 和 compgen 提供的默认动作之外,获取额外补全功能最常见的方式是使用一个 shell 函数,并用 complete -F 把它绑定到某个特定命令。

下面的函数为 cd 内建命令提供补全。它是一个相当不错的示例,展示了 shell 函数在用于补全时必须要做的事。该函数使用作为 $2 传入的单词来确定要补全的目录名。你也可以使用 COMP_WORDS 数组变量;当前单词由 COMP_CWORD 变量索引。

该函数依靠 complete 和 compgen 内建命令完成大部分工作,只添加了 Bash 的 cd 在接受基本目录名之外所做的那些事:波浪号展开(参见 3.5.2 节“波浪号展开”)、在 $CDPATH 中搜索目录(上文已描述,参见 4.1 节“Bourne Shell 内建命令”),以及对 cdable_vars shell 选项的基本支持(参见 4.3.2 节“shopt 内建命令”)。_comp_cd 会修改 IFS 的值,使其只包含一个换行符,以适应包含空格和制表符的文件名 —— compgen 会把它生成的可能的补全项每行打印一个。

可能的补全项会进入 COMPREPLY 数组变量,每个数组元素一个补全项。当该函数返回时,可编程补全系统会从那里取回补全项。

# A completion function for the cd built-in
# based on the cd completion function from the bash_completion package
_comp_cd()
{
    local IFS=$' \t\n'    # normalize IFS
    local cur _skipdot _cdpath
    local i j k

    # Tilde expansion, which also expands tilde to full pathname
    case "$2" in
    \~*) eval cur="$2" ;;
    *) cur=$2 ;;
    esac

    # no cdpath or absolute pathname -- straight directory completion
    if [[ -z "${CDPATH:-}" ]] || [[ "$cur" == @(/*|./*|../*) ]]; then
        # compgen prints paths one per line; could also use while loop
        IFS=$'\n'
        COMPREPLY=( $(compgen -d -- "$cur") )
        IFS=$'\t\n'
    # CDPATH+directories in the current directory if not in CDPATH
    else
        IFS=$'\n'
        _skipdot=false
        # preprocess CDPATH to convert null directory names to .
        _cdpath=${CDPATH/#:/.:}
        _cdpath=${_cdpath//::/:.:}
        _cdpath=${_cdpath/%:/:.}
        for i in ${_cdpath//:/$'\n'}; do
            if [[ $i -ef . ]]; then _skipdot=true; fi
            k="${#COMPREPLY[@]}"
            for j in $( compgen -d -- "$i/$cur" ); do
                COMPREPLY[k++]=${j#$i/}    # cut off directory
            done
        done
        $_skipdot || COMPREPLY+=( $(compgen -d -- "$cur") )
        IFS=$'\t\n'
    fi
    # variable names if appropriate shell option set and no completions
    if shopt -q cdable_vars && [[ ${#COMPREPLY[@]} -eq 0 ]]; then
        COMPREPLY=( $(compgen -v -- "$cur") )
    fi
    return 0
}

我们用 complete 的 -F 选项来安装该补全函数:

# Tell readline to quote appropriate and append slashes to directories;
# use the bash default completion for other arguments
complete -o filenames -o nospace -o bashdefault -F _comp_cd cd

由于我们希望 Bash 和 Readline 为我们处理其他一些细节,所以我们使用了另外几个选项来告诉 Bash 和 Readline 该做什么。-o filenames 选项告诉 Readline,可能的补全项应被视为文件名,并适当地加引用。该选项还会使 Readline 为它能判定为目录的文件名追加一个斜杠(这就是为什么如果我们使用的是通过 CDPATH 找到的目录,可能想要扩展 _comp_cd 以追加一个斜杠:Readline 无法判断那些补全项是目录)。-o nospace 选项告诉 Readline 不要为目录名追加空格字符,以防我们想要往后面继续追加。-o bashdefault 选项引入其余“Bash 默认”补全项 —— 即 Bash 添加到默认 Readline 集合中的可能的补全项。这些包括诸如命令名补全、对以 $ 或 $f 开头的单词的变量补全、包含路径名展开模式的补全(参见 3.5.8 节“文件名展开”)等等。

一旦使用 complete 安装后,每次我们尝试为 cd 命令进行单词补全时都会调用 _comp_cd。

还有多得多的示例 —— 一个针对大多数常见 GNU、Unix 和 Linux 命令的、内容详尽的补全集合 —— 作为 bash_completion 项目的一部分提供。许多 GNU/Linux 发行版默认安装了它。该项目最初由 Ian Macdonald 编写,现在位于 https://github.com/scop/bash-completion/。它还有其他系统(如 Solaris 和 Mac OS X)的移植版本。

较老版本的 bash_completion 包随 bash 一起分发,位于 examples/complete 子目录中。

9 交互式使用历史#

本章从用户的角度描述如何以交互方式使用 GNU History Library。它应被视为一份用户指南。关于在其他程序中使用 GNU History Library 的信息,请参见 GNU Readline Library Manual。

9.1 Bash 历史设施#

当 set 内建命令的 -o history 选项被启用时(参见 4.3.1 节“set 内建命令”),shell 就提供对命令历史(即此前键入的命令列表)的访问。HISTSIZE shell 变量的值用作要保存在历史列表中的命令数目:shell 会保存最后 $HISTSIZE 条命令的文本(默认为 500)。shell 在参数展开和变量展开之前、但在历史展开执行之后,把每条命令存入历史列表,并受 HISTIGNORE 和 HISTCONTROL 这两个 shell 变量的值约束。

当 shell 启动时,Bash 通过从 HISTFILE 变量所命名的文件(默认为 ~/.bash_history)中读取历史条目来初始化历史列表。该文件称为历史文件。历史文件必要时会被截断,使其包含的历史条目不超过 HISTFILESIZE 变量的值所指定的数目。如果 HISTFILESIZE 未设置,或被设置为空、非数值、或小于零的数值,则历史文件不会被截断。

读取历史文件时,以历史注释字符起头、紧接着一个数字的行会被解释为紧随其后的历史条目的时间戳。这些时间戳是否显示取决于 HISTTIMEFORMAT 变量的值(参见 5.2 节“Bash 变量”)。时间戳存在时,它们会界定历史条目,从而使多行条目成为可能。

当一个启用了历史的 shell 退出时,Bash 会把历史列表中最后 $HISTSIZE 个条目复制到 $HISTFILE 所命名的文件。如果设置了 histappend shell 选项(参见 4.2 节“Bash 内建命令”),Bash 会把这些条目追加到历史文件,否则它会覆盖历史文件。如果 HISTFILE 未设置或为空,或者历史文件不可写,则历史不会被保存。在保存历史之后,Bash 会如上所述把历史文件截断,使其包含的行数不超过 $HISTFILESIZE 行。

如果设置了 HISTTIMEFORMAT 变量,shell 会把与每个历史条目相关联的时间戳信息写入历史文件,并用历史注释字符标记,这样时间戳就能跨 shell 会话保留。读取历史文件时,以历史注释字符起头、紧接着一个数字的行会被解释为紧随其后的历史条目的时间戳。如上所述,使用 HISTTIMEFORMAT 时,时间戳会界定多行历史条目。

fc 内建命令会列出或编辑并重新执行历史列表的一部分。history 内建命令可以显示或修改历史列表,并操作历史文件。使用命令行编辑时,每种编辑模式中都提供了可访问历史列表的搜索命令(参见 8.4.2 节“历史相关命令”)。

shell 允许控制哪些命令被保存到历史列表上。HISTCONTROL 和 HISTIGNORE 变量用于只保存所输入命令的一个子集。如果启用了 cmdhist shell 选项,shell 会尝试把多行命令的每一行保存到同一个历史条目中,必要时添加分号以保持语法正确。lithist shell 选项则修改 cmdhist,改为保存带有内嵌换行符(而非分号)的命令。shopt 内建命令用于设置这些选项。关于 shopt 的说明,参见 4.3.2 节“shopt 内建命令”。

9.2 Bash 历史内建命令#

Bash 提供了两个内建命令来操作历史列表和历史文件。

fc#

fc [-e ename] [-lnr] [first] [last]
fc -s [pat=rep] [command]

第一种形式从历史列表中选择从 first 到 last 的一段命令,并显示或编辑并重新执行它们。first 和 last 都可以指定为字符串(用于定位以该字符串开头的最新命令),也可以指定为数字(历史列表中的索引,其中负数用作相对于当前命令号的偏移量)。

列出时,first 或 last 为 0 等价于 -1,-0 等价于当前命令(通常是 fc 命令);除此之外,0 等价于 -1,而 -0 无效。

如果未指定 last,则列出时它被设为当前命令,否则被设为 first。如果未指定 first,则编辑时它被设为前一条命令,列出时被设为 -16。

如果提供了 -l 标志,命令会列在标准输出上。-n 标志在列出时抑制命令编号。-r 标志反转列出的顺序。

否则,fc 会在一个包含那些命令的文件上调用由 ename 命名的编辑器。如果未提供 ename,fc 会使用以下变量展开的值:${FCEDIT:-${EDITOR:-vi}}。这表示:如果设置了 FCEDIT 变量就使用它的值,否则如果设置了 EDITOR 变量就使用它的值,如果两者都未设置则使用 vi。编辑完成后,fc 会读取已编辑命令的文件,回显并执行它们。

在第二种形式中,fc 会在把所选命令中每一处 pat 替换为 rep 之后重新执行 command。command 的解释方式与上文中的 first 相同。

与 fc 命令一起使用的一个有用的别名是 r='fc -s',这样键入 r cc 会运行最近一条以 cc 开头的命令,而键入 r 会重新执行最后一条命令(参见 6.6 节“别名”)。

如果使用第一种形式,除非遇到无效选项,或者 first 或 last 指定的历史行超出范围,否则返回值为零。在编辑并重新执行一个命令文件时,返回值是最后执行的命令的值;如果临时文件出错,则为失败。如果使用第二种形式,返回状态是重新执行的那条命令的返回状态;除非 command 未指定有效的历史条目,这种情况下 fc 会返回非零状态。

history#

history [n]
history -c
history -d offset
history -d start-end
history [-anrw] [filename]
history -ps arg

不带选项时,显示带编号的历史列表。以 * 为前缀的条目已被修改。参数 n 只列出最后 n 个条目。如果 shell 变量 HISTTIMEFORMAT 已设置且不为空,它会被用作 strftime(3) 的格式字符串,以显示与每个所显示历史条目相关联的时间戳。如果 history 使用了 HISTTIMEFORMAT,它不会在格式化后的时间戳与历史条目之间打印间隔空格。

选项(如果提供)具有以下含义:

-c 清空历史列表。这可以与其他选项组合使用,以替换历史列表。

-d offset 删除位于位置 offset 的历史条目。如果 offset 为正,则应按历史显示时它出现的形式指定。如果 offset 为负,则解释为相对于最后一个历史位置加一,因此负索引从历史末尾往回计数,索引 -1 指的是当前的 history -d 命令。

-d start-end 删除位置 start 和 end 之间(含两端)的历史条目范围。start 和 end 的正值和负值按上文所述解释。

-a 把“新的”历史行追加到历史文件。这些是自当前 Bash 会话开始以来输入、但尚未追加到历史文件的历史行。

-n 读取尚未从历史文件读取的历史行,并把它们加入当前历史列表。这些是自当前 Bash 会话开始以来追加到历史文件的行。

-r 读取历史文件,并把其内容追加到历史列表。

-W 把当前历史列表写入历史文件,覆盖历史文件。

-p 对 args 执行历史替换并把结果显示在标准输出上,但不把结果存入历史列表。

-s 把 args 作为单个条目添加到历史列表末尾。在添加 args 之前,会移除历史列表中的最后一条命令。

如果随 -w、-r、-a 或 -n 中任一选项提供了 filename 参数,Bash 会把 filename 用作历史文件;否则,它使用 HISTFILE 变量的值。如果 HISTFILE 未设置或为空,这些选项不起作用。

如果设置了 HISTTIMEFORMAT 变量,history 会把与每个历史条目相关联的时间戳信息写入历史文件,并如上所述用历史注释字符标记。读取历史文件时,以历史注释字符起头、紧接着一个数字的行会被解释为紧随其后的历史条目的时间戳。

除非遇到无效选项、在读写历史文件时发生错误、作为 -d 的参数提供了无效的 offset 或范围、或者作为 -p 的参数提供的历史展开失败,否则返回值为 0。

9.3 历史展开#

shell 提供了一种历史展开特性,它类似于 csh 提供的历史展开(在适当的地方也称为历史替换)。本节描述用于操作历史信息的语法。

对交互式 shell 而言,历史展开默认启用,并且可以用 set 内建命令的 +H 选项禁用(参见 4.3.1 节“set 内建命令”)。非交互式 shell 默认不执行历史展开,但可以用 set -H 启用。

历史展开把历史列表中的单词引入输入流,使得重复执行命令、把先前命令的参数插入当前输入行、或者快速修正先前命令中的错误都变得容易。

历史展开在一整行被读取之后、在 shell 把它拆分为单词之前立即进行,并且是对每一行分别进行的。Bash 会尝试把仍然有效的、来自先前各行的引用情况告知历史展开函数。

历史展开分两部分进行。第一部分是确定在替换中应使用历史列表中的哪个条目。第二部分是选择该条目的哪些部分纳入当前条目。

从历史中选出的条目称为事件(event),该条目中被作用的部分称为单词(word)。有各种修饰符可用于操作所选单词。该条目按 Bash 读取输入时相同的方式拆分为单词,因此被引号包围的若干单词会被视为一个单词。事件指示符选择事件,可选的单词指示符从事件中选择单词,还有各种可选的修饰符可用于操作所选单词。

历史展开由历史展开字符的出现引入,该字符默认为 !。历史展开可以出现在输入中的任何位置,但不能嵌套。

历史展开实现了类似 shell 的引用约定:反斜杠可用于去除对下一个字符的特殊处理;单引号括起逐字字符序列,并可用于抑制历史展开;而双引号内的字符可能仍会经历历史展开,因为反斜杠可以转义历史展开字符,但单引号不行,因为它们在双引号内不被特殊对待。

在使用 shell 时,只有 \ 和 ' 可以用来转义历史展开字符;不过,如果历史展开字符紧挨着双引号字符串中的闭合双引号之前,它也会被视为已引用。

有几个字符如果紧跟在历史展开字符之后出现,即使未被引用也会抑制历史展开:空格、制表符、换行符、回车符、=,以及其他 shell 元字符。

有一种用于替换的特殊缩写,当快速替换字符(上文在 histchars 下描述)是行上的第一个字符时生效。它选中前一个历史列表条目(使用等价于 !! 的事件指示符),并在该条目中用一个字符串替换另一个字符串。下文对它进行了描述(参见 9.3.1 节“事件指示符”)。这是唯一一种不以历史展开字符开头的历史展开。

有几个可以用 shopt 内建命令设置的 shell 选项(参见 4.3.2 节“shopt 内建命令”)会修改历史展开行为。如果启用了 histverify shell 选项,并且正在使用 Readline,则历史替换不会立即传递给 shell 解析器。取而代之的是,展开后的行会被重新载入 Readline 编辑缓冲区,以便进一步修改。如果正在使用 Readline,并且启用了 histreedit shell 选项,则失败的历史展开会被重新载入 Readline 编辑缓冲区以便修正。

history 内建命令的 -p 选项展示了在使用历史展开之前它将做什么。history 内建命令的 -s 选项可用于把命令添加到历史列表末尾而不实际执行它们,使它们可供后续调回。这在配合 Readline 使用时最为有用。

shell 允许用 histchars 变量来控制历史展开机制所使用的各种字符,如上所述(参见 5.2 节“Bash 变量”)。shell 在写历史文件时使用历史注释字符来标记历史时间戳。

9.3.1 事件指示符#

事件指示符是对历史列表中某个条目的引用。事件指示符由单词中从历史展开字符开始、到单词指示符(如果有的话)为止或到单词结尾的那部分构成。除非引用是绝对的,否则事件是相对于历史列表中当前的位置的。

! 开始一次历史替换,除非其后紧跟着空格、制表符、行尾、=,或上文定义的其他 shell 元字符(参见第 2 章“定义”)。

!n 引用历史列表条目 n。

!-n 引用倒数第 n 个历史条目。

!! 引用前一个条目。这是 !-1 的同义词。

!string 引用历史列表中当前位置之前、最近一条以 string 开头的命令。

!?string[?] 引用历史列表中当前位置之前、最近一条包含 string 的命令。如果 string 后面紧跟着一个换行符,则末尾的 ? 可以省略。如果缺少 string,则使用最近一次搜索所用的字符串;如果没有先前的搜索字符串,则视为错误。

^string1^string2^ 快速替换。重复最后一条命令,把 string1 替换为 string2。等价于 !!:s^string1^string2^。

!# 目前已经键入的整条命令行。

9.3.2 单词指示符#

单词指示符用于从事件中选择所需的单词。它们是可选的;如果未提供单词指示符,历史展开会使用整个事件。: 把事件规范与单词指示符分隔开。如果单词指示符以 -、$、*、- 或 % 开头,则该 : 可以省略。单词从行首开始编号,第一个单词用 0(零)表示。这第一个单词通常是命令单词,参数从第二个单词开始。单词插入当前行时以单个空格分隔。

例如,

!! 指定前一条命令。当你键入它时,前一条命令会被原封不动地重复。

!!:$ 指定前一条命令的最后一个单词。这可以缩短为 !$。

!fi:2 指定最近一条以字母 fi 开头的命令的第二个参数。

以下是各个单词指示符:

0(零)第 0 个单词。对 shell 以及许多其他应用来说,这就是命令单词。

n 第 n 个单词。

^ 第一个参数,即单词 1。

$ 最后一个单词。通常它是最后一个参数,但如果该行中只有一个单词,则展开为第 0 个单词。

% 最近一次 ?string? 搜索所匹配到的第一个单词(前提是搜索字符串以一个属于某单词的字符开头)。默认情况下,搜索从每行末尾开始并向开头进行,因此第一个匹配到的单词是离行尾最近的那个。

x-y 一个单词范围;-y 是 0-y 的简写。

`*` 除第 0 个单词之外的所有单词。这是 1-$ 的同义词。如果事件中只有一个单词,使用 * 也不算错误;在这种情况下它会展开为空字符串。

`x*` 是 x-$ 的简写。

x- 像 x* 一样是 x-$ 的简写,但省略最后一个单词。如果缺少 x,则默认为 0。

如果提供了单词指示符而未提供事件规范,则把前一条命令用作事件,等价于 !!。

9.3.3 修饰符#

在可选的单词指示符之后,你可以添加一个或多个下列修饰符组成的序列,每个修饰符前面都有一个 :。这些修饰符修改或编辑从历史事件中选出的单词。

h 去除末尾的文件名组成部分,只留下头部。

t 去除所有开头的文件名组成部分,只留下尾部。

r 去除形如 .suffix 的末尾后缀,只留下基本名。

e 去除除末尾后缀之外的所有内容。

p 打印新命令但不执行它。

q 给替换后的单词加引用,转义后续的替换。

x 像 q 一样给替换后的单词加引用,但在空格、制表符和换行符处拆分为单词。q 和 x 修饰符互斥;展开使用最后提供的那个。

s/old/new/ 在事件行中用 new 替换 old 第一次出现的地方。任何字符都可以代替 / 用作分隔符。分隔符可以在 old 和 new 中用单个反斜杠引用。如果 new 中出现 &,它会被替换为 old。单个反斜杠可以引用 old 和 new 中的 &。如果 old 为空,它会被设为上一次替换的 old;或者,如果此前没有进行过历史替换,则设为 !?string[?] 搜索中的最后一个字符串。如果 new 为空,则每个匹配的 old 都会被删除。如果末尾的分隔符是输入行上的最后一个字符,则它可省略。

& 重复前一次替换。

g 使更改作用于整个事件行。这要与 s 配合使用,如 gs/old/new/,或与 & 配合使用。

G 把随后的 s 或 & 修饰符对事件中的每个单词各应用一次。

10 安装 Bash#

本章提供在各种受支持平台上安装 Bash 的基本说明。该发行版支持 GNU 操作系统、几乎所有版本的 Unix,以及若干非 Unix 系统,例如 BeOS 和 Interix。Windows 平台还存在其他独立的移植版本。

10.1 基本安装#

以下是 Bash 的安装说明。

编译 Bash 最简单的方式是:

  1. cd 到包含源代码的目录,输入 ./configure 来为你的系统配置 Bash。如果你在旧版 System V 上使用 csh,可能需要改输入 sh ./configure,以免 csh 试图自行执行 configure。

    运行 configure 需要一些时间。运行过程中它会打印消息,告诉你正在检查哪些特性。

  2. 输入 make 编译 Bash 并构建 bashbug 缺陷报告脚本。

  3. 可选地,输入 make tests 运行 Bash 测试套件。

  4. 输入 make install 安装 bash 和 bashbug。这还会安装手册页和 Info 文件、消息翻译文件、一些补充文档、若干可加载内建命令示例,以及一组用于开发可加载内建命令的头文件。把 bash 安装到期望的目标位置可能需要额外的权限,也许要用 sudo make install。有关控制 bash 及其他文件安装位置的更多信息见下文(参见 10.4 节“安装名”)。

configure shell 脚本会尝试为编译期间使用的各种与系统相关的变量猜测正确的值。它用这些值在包的每个目录中创建 Makefile(顶层目录,以及 builtins、doc、po 和 support 目录,lib 下的每个目录,还有另外几个目录)。它还会创建一个 config.h 文件,其中包含与系统相关的定义。最后,它创建一个名为 config.status 的 shell 脚本,你以后可以运行它来重新创建当前配置;创建一个 config.cache 文件,保存其测试结果以加快重新配置;还创建一个 config.log 文件,包含编译器输出(主要用于调试 configure)。如果某个时候 config.cache 中包含你不想保留的结果,你可以删除或编辑它。

要了解 configure 脚本能识别的选项和参数,请在你的 Bash 源代码目录中的 Bash 提示符下输入

bash-4.2$ ./configure --help

如果你想在与源代码目录分开的目录中构建 Bash —— 例如为多种体系结构构建 —— 只需使用 configure 脚本的完整路径。以下命令会根据 /usr/local/src/bash-4.4 中的源代码,在 /usr/local/build 下的目录中构建 Bash:

mkdir /usr/local/build/bash-4.4
cd /usr/local/build/bash-4.4
bash /usr/local/src/bash-4.4/configure
make

有关在与源代码分开的目录中构建的更多信息,参见 10.3 节“为多种体系结构编译”。

如果你需要做一些不同寻常的事情才能编译 Bash,请试着弄清 configure 如何检查是否要做这些事,并把差异补丁或说明寄到 bash-maintainers@gnu.org,以便在下一个发行版中予以考虑。

文件 configure.ac 由名为 Autoconf 的程序用来创建 configure。只有当你想要修改它,或者使用更新版本的 Autoconf 重新生成 configure 时,才需要 configure.ac。如果你这样做,请确保使用的是 Autoconf 2.69 或更高版本。

你可以在源代码目录中输入 make clean 删除程序二进制文件和目标文件。若还要删除 configure 创建的文件(以便为不同类型的计算机编译 Bash),请输入 make distclean。

10.2 编译器与选项#

有些系统编译或链接时需要 configure 脚本并不知道的特殊选项。你可以通过在环境中设置变量来给 configure 传初始值。使用兼容 Bourne 的 shell 时,可以在命令行上这样做:

CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure

在有 env 程序的系统上,可以这样做:

env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure

如果 GCC 可用,配置过程会使用 GCC 来构建 Bash。

10.3 为多种体系结构编译#

通过把每种体系结构的目标文件放在各自的目录中,你可以同时为多种计算机编译 Bash。为此,必须使用支持 VPATH 变量的 make 版本,例如 GNU make。cd 到希望存放目标文件和可执行文件的目录,并从源代码目录运行 configure 脚本(参见 10.1 节“基本安装”)。你可能需要提供 --srcdir=PATH 参数,告诉 configure 源文件在哪里。configure 会自动在 configure 所在的目录以及 .. 中检查源代码。

如果你不得不使用不支持 VPATH 变量的 make,可以在源代码目录中一次为一种体系结构编译 Bash。在为一种体系结构安装好 Bash 之后,重新为另一种体系结构配置之前,请先使用 make distclean。

另一种办法是,如果你的系统支持符号链接,可以使用 support/mkclone 脚本创建一个构建树,其中每个文件都是指向源代码目录中对应文件的符号链接。下面这个例子根据源目录 /usr/gnu/src/bash-2.0 在当前目录中创建构建目录:

bash /usr/gnu/src/bash-2.0/support/mkclone -s /usr/gnu/src/bash-2.0 .

mkclone 脚本需要 Bash,因此在为其他体系结构创建构建目录之前,你必须已经为至少一种体系结构构建好 Bash。

10.4 安装名#

默认情况下,make install 会安装到 /usr/local/bin、/usr/local/man 等目录;也就是说,安装前缀默认为 /usr/local。你可以通过给 configure 传入 --prefix=PATH 选项,或者在运行 make install 时为 prefix make 变量指定一个值(例如 make install prefix=PATH),来指定 /usr/local 之外的安装前缀。prefix 变量为 exec_prefix 以及安装 Bash 时使用的其他变量提供默认值。

你可以为与体系结构相关的文件和与体系结构无关的文件指定不同的安装前缀。如果你给 configure 传入 --exec-prefix=PATH 选项,make install 将使用 PATH 作为安装程序和库的前缀。文档和其他数据文件仍使用常规前缀。

如果你只想为单次运行改变安装位置,可以把这些变量作为参数传给 make:make install exec_prefix=/ 会把 bash 和 bashbug 安装到 /bin,而不是默认的 /usr/local/bin。

如果你想在不改动系统任何内容的前提下查看 Bash 将安装哪些文件、安装到哪里,请把变量 DESTDIR 作为参数传给 make。它的值应是你想用作示例安装树根目录的绝对目录路径。例如,

mkdir /fs1/bash-install
make install DESTDIR=/fs1/bash-install

会把 bash 安装到 /fs1/bash-install/usr/local/bin/bash,把文档安装到 /fs1/bash-install/usr/local/share 下的各个目录,把可加载内建命令示例安装到 /fs1/bash-install/usr/local/lib/bash,等等。你可以照常使用 exec_prefix 和 prefix 变量来改变 DESTDIR 值之下的目录路径。

GNU Makefile 标准对这些变量及其作用提供了更完整的描述。

10.5 指定系统类型#

有些特性 configure 无法自动推断,而需要根据 Bash 将要运行的主机类型来确定。通常 configure 能推断出来,但如果它打印消息说无法猜测主机类型,就给它 --host=TYPE 选项。TYPE 既可以是系统类型的简称,例如 sun4,也可以是带三个字段的规范名称:CPU-COMPANY-SYSTEM(例如 i386-unknown-freebsd4.2)。

每个字段可能有哪些取值,见文件 support/config.sub。

10.6 共享默认值#

如果你想为 configure 脚本设置可共享的默认值,可以创建一个名为 config.site 的站点 shell 脚本,为 CC、cache_file 和 prefix 之类的变量给出默认值。configure 会查找 PREFIX/share/config.site(如果它存在),然后查找 PREFIX/etc/config.site(如果它存在)。或者,你也可以把 CONFIG_SITE 环境变量设置为该站点脚本的位置。提醒一句:Bash 的 configure 会查找站点脚本,但并非所有 configure 脚本都这样做。

10.7 操作控制#

configure 识别以下选项来控制它的运作方式。

--cache-file=file 在 file 中保存并使用测试结果,而不是 ./config.cache。将 file 设为 /dev/null 可禁用缓存,用于调试 configure。

--help 打印 configure 选项摘要并退出。

--quiet、--silent、-q 不打印表明正在执行哪些检查的消息。

--srcdir=dir 在目录 dir 中查找 Bash 源代码。通常 configure 能自动确定该目录。

--version 打印用于生成 configure 脚本的 Autoconf 版本,然后退出。

configure 还接受其他一些不常用的样板(boilerplate)选项。configure --help 会打印完整列表。

10.8 可选特性#

Bash 的 configure 有许多 --enable-feature 选项,其中 feature 表示 Bash 的可选部分。还有若干 --with-package 选项,其中 package 类似于 bash-malloc 或 afs。要关闭对某个包的默认使用,使用 --without-package。要配置出一个禁用了某个默认启用特性的 Bash,使用 --disable-feature。

以下是 Bash 的 configure 能识别的 --enable- 和 --with- 选项的完整列表。

--with-afs 若你使用 Transarc 的 Andrew File System,则定义此项。

--with-bash-malloc 在 lib/malloc 目录中使用 Bash 版本的 malloc。它不同于 GNU libc 中出现的 malloc,而是一个最初源自 4.2 BSD malloc 的自定义版本。这个 malloc 非常快,但每次分配都会浪费一些空间,尽管它用了若干技术来把浪费降到最低。该选项默认启用。NOTES 文件列出了应当关闭它的系统,对若干系统 configure 会自动禁用该选项。

--with-curses 使用 curses 库而不是 termcap 库。configure 通常会自动选择它,因为大多数系统的 termcap 函数都包含在 curses 库中。

--with-gnu-malloc --with-bash-malloc 的同义词。

--with-installed-readline[=PREFIX] 定义此项可让 Bash 链接本地已安装的 Readline 版本,而不是 lib/readline 中的版本。这仅对 Readline 5.0 及更高版本有效。如果 PREFIX 为 yes 或未提供,configure 会使用 make 变量 includedir 和 libdir 的值(默认情况下它们是 prefix 的子目录),在已安装版本的 Readline 不在标准系统 include 和库目录中时找到它。如果 PREFIX 为 no,Bash 链接 lib/readline 中的版本。如果 PREFIX 设为其他任何值,configure 将其视为目录路径名,并在该目录的子目录中查找已安装的 Readline 版本(头文件在 PREFIX/include,库文件在 PREFIX/lib)。Bash 默认链接构建目录 lib/readline 子目录中构建的静态库。

--with-libintl-prefix[=PREFIX] 定义此项可让 Bash 链接本地已安装的 libintl 库,而不是 lib/intl 中的版本。

--with-libiconv-prefix[=PREFIX] 定义此项可让 Bash 在 PREFIX 中而不是标准系统位置查找 libiconv。Bash 发行版不包含该库。

--enable-minimal-config 这会生成一个特性极少的 shell,更接近历史上的 Bourne shell。

有几个 --enable- 选项改变的并非运行时特性,而是 Bash 的编译、链接和安装方式。

--enable-largefile 如果操作系统需要特殊的编译器选项来构建能访问大文件的程序,则启用对大文件的支持(http://www.unix.org/version2/whatsnew/lfs20mar.html)。如果操作系统提供大文件支持,则默认启用。该链接(Open Group 的大文件支持说明)现已下线,返回 HTTP 404;此处照录原书链接供对照。

--enable-profiling 这会构建一个 Bash 二进制文件,它每次执行时都会产生由 gprof 处理的剖析(profiling)信息。

--enable-separate-helpfiles 为 help 内建命令显示的文档使用外部文件,而不是把文本存放在内部。

--enable-static-link 如果使用 gcc,这会让 Bash 静态链接。可以用它构建一个作为 root 的 shell 使用的版本。

minimal-config 选项可用于禁用以下所有选项,但它最先被处理,因此可以用 enable-feature 逐个启用个别选项。

除 alt-array-implementation、disabled-builtins、direxpand-default、strict-posix-default 和 xpg-echo-default 之外,以下所有选项默认启用,除非操作系统未提供必要的支持。

--enable-alias 允许别名展开,并包含 alias 和 unalias 内建命令(参见 6.6 节“别名”)。

--enable-alt-array-implementation 这会让 Bash 使用一种替代的数组实现(参见 6.7 节“数组”)来构建,它以占用更多内存为代价提供更快的访问(有时会多很多倍,取决于数组有多稀疏)。

--enable-arith-for-command 包含对 for 命令另一种形式的支持,其行为类似 C 语言的 for 语句(参见 3.2.5.1 节“循环结构”)。

--enable-array-variables 包含对一维数组 shell 变量的支持(参见 6.7 节“数组”)。

--enable-bang-history 包含对类似 csh 的历史替换的支持(参见 9.3 节“历史交互”)。

--enable-bash-source-fullpath-default 把上文 4.3.2 节“shopt 内建命令”中描述的 bash_source_fullpath shell 选项的默认值设为启用。它控制如何为 BASH_SOURCE 数组变量赋予文件名。

--enable-brace-expansion 包含类似 csh 的花括号展开(b{a,b}c → bac bbc)。完整描述参见 3.5.1 节“花括号展开”。

--enable-casemod-attributes 包含对 declare 内建命令和赋值语句中大小写修改属性的支持。例如,带 uppercase 属性的变量在赋值时其值会被转换为大写。

--enable-casemod-expansion 包含对大小写修改单词展开的支持。

--enable-command-timing 包含把 time 识别为保留字、并为紧随其后的管道显示计时统计信息的支持(参见 3.2.3 节“管道”)。这样可以对管道、shell 复合命令、shell 内建命令和 shell 函数计时,而外部命令很难做到这一点。

--enable-cond-command 包含对 [[ 条件命令的支持。(参见 3.2.5.2 节“条件结构”)。

--enable-cond-regexp 包含对在 [[ 条件命令中使用 =~ 二元运算符匹配 POSIX 正则表达式的支持。(参见 3.2.5.2 节“条件结构”)。

--enable-coprocesses 包含对协进程和 coproc 保留字的支持(参见 3.2.3 节“管道”)。

--enable-debugger 包含对 Bash 调试器的支持(单独发布)。

--enable-dev-fd-stat-broken 如果对 /dev/fd/N 调用 stat 与对文件描述符 N 调用 fstat 返回的结果不同,提供此选项可启用一种变通方法。这会影响测试文件属性的条件命令。

--enable-direxpand-default 使 shell 启动时默认启用 direxpand shell 选项(参见 4.3.2 节“shopt 内建命令”)。它通常默认禁用。

--enable-directory-stack 包含对类似 csh 的目录栈以及 pushd、popd 和 dirs 内建命令的支持(参见 6.8 节“目录栈”)。

--enable-disabled-builtins 即使 xxx 已用 enable -n xxx 禁用,也允许通过 builtin xxx 调用该内建命令。有关 builtin 和 enable 内建命令的细节,参见 4.2 节“Bash 内建命令”。

--enable-dparen-arithmetic 包含对 ((...)) 命令的支持(参见 3.2.5.2 节“条件结构”)。

--enable-extended-glob 包含对上文 3.5.8.1 节“模式匹配”中描述的扩展模式匹配特性的支持。

--enable-extended-glob-default 把上文 4.3.2 节“shopt 内建命令”中描述的 extglob shell 选项的默认值设为启用。

--enable-function-import 包含对从环境中导入由 shell 另一实例导出的函数定义的支持。该选项默认启用。

--enable-glob-asciiranges-default 把上文 4.3.2 节“shopt 内建命令”中描述的 globasciiranges shell 选项的默认值设为启用。它控制模式匹配方括号表达式中使用的字符范围的行为。

--enable-help-builtin 包含 help 内建命令,它显示关于 shell 内建命令和变量的帮助(参见 4.2 节“Bash 内建命令”)。

--enable-history 包含命令历史以及 fc 和 history 内建命令(参见 9.1 节“Bash 历史功能”)。

--enable-job-control 如果操作系统支持,这会启用作业控制特性(参见第 7 章“作业控制”)。

--enable-multibyte 如果操作系统提供必要的支持,这会启用对多字节字符的支持。

--enable-net-redirections 这会启用在重定向中使用形如 /dev/tcp/host/port 和 /dev/udp/host/port 的文件名时的特殊处理(参见 3.6 节“重定向”)。

--enable-process-substitution 如果操作系统提供必要的支持,这会启用进程替换(参见 3.5.6 节“进程替换”)。

--enable-progcomp 启用可编程补全设施(参见 8.6 节“可编程补全”)。如果未启用 Readline,该选项无效。

--enable-prompt-string-decoding 打开对 $PS0、$PS1、$PS2 和 $PS4 提示符字符串中若干反斜杠转义字符的解释。提示符字符串转义序列的完整列表参见 6.9 节“控制提示符”。

--enable-readline 包含使用 Bash 版本 Readline 库的命令行编辑和历史支持(参见第 8 章“命令行编辑”)。

--enable-restricted 包含对受限 shell 的支持。如果启用,当以 rbash 调用时,Bash 进入受限模式。受限模式的描述参见 6.10 节“受限 shell”。

--enable-select 包含 select 复合命令,它允许生成简单菜单(参见 3.2.5.2 节“条件结构”)。

--enable-single-help-strings 把 help 内建命令显示的文本按每个帮助主题存为单个字符串。这有助于把文本翻译成不同语言。如果你的编译器无法处理很长的字符串字面量,你可能需要禁用此项。

--enable-strict-posix-default 让 Bash 默认符合 POSIX(参见 6.11 节“Bash POSIX 模式”)。

--enable-translatable-strings 启用对 $"string" 可翻译字符串的支持(参见 3.1.2.5 节“区域设置翻译”)。

--enable-usg-echo-default --enable-xpg-echo-default 的同义词。

--enable-xpg-echo-default 让 echo 内建命令默认展开反斜杠转义字符,不再需要 -e 选项。这会把 xpg_echo shell 选项的默认值设为 on,使 Bash 的 echo 行为更接近 Single Unix Specification 第 3 版所规定的版本。echo 能识别的转义序列的描述参见 4.2 节“Bash 内建命令”。

文件 config-top.h 包含针对无法从 configure 设置的选项的 C 预处理器 #define 语句。其中有些不打算被修改;如果要修改,请当心后果。关于每个定义的效果,请阅读与之相关的注释。

附录 A 报告缺陷#

请报告你在 Bash 中发现的所有缺陷。但首先,你应当确认它确实是一个缺陷,并且出现在最新版本的 Bash 中。Bash 最新发布的版本始终可以通过 FTP 从 ftp://ftp.gnu.org/pub/gnu/bash/ 获取,也可以从 http://git.savannah.gnu.org/cgit/bash.git/snapshot/bash-master.tar.gz 获取。

一旦你确定某个缺陷确实存在,就使用 bashbug 命令提交缺陷报告,或使用 Bash 项目页面上的表单(https://savannah.gnu.org/projects/bash/)。如果你有修复方案,也非常鼓励一并提交!建议和“哲理性”缺陷报告可以邮寄到 bug-bash@gnu.org 或 help-bash@gnu.org。

所有缺陷报告都应包含:

bashbug 会自动把前三项插入它为提交缺陷报告提供的模板中。

请把所有关于本手册的报告发送到 bug-bash@gnu.org。

附录 B 与 Bourne shell 的主要差异#

Bash 实现的语法、参数和变量展开、重定向以及引用与 Bourne shell 基本相同。Bash 以 POSIX 标准作为这些特性应如何实现、应如何表现的规范。传统 Bourne shell 与 Bash 之间存在一些差异;本节简要详述其中重要的差异。这些差异中有许多在之前各节中有更深入的说明。本节以 SVR4.2 中包含的 sh 版本(历史上最后一个 Bourne shell 版本)作为基线参照。

Bash 符合 POSIX,即使在 POSIX 规范与传统 sh 行为不同的地方也是如此(参见 6.11 节“Bash POSIX 模式”)。

Bash 具有多字符调用选项(参见 6.1 节“调用 Bash”)。

更多 Bash 独有的特性可以在第 6 章“Bash 特性”中找到。

B.1 与 SVR4.2 shell 的实现差异#

由于 Bash 是全新实现,它不受 SVR4.2 shell 许多限制的困扰。例如:

附录 C GNU 自由文档许可证#

版本 1.3,2008 年 11 月 3 日

版权所有 © 2000、2001、2002、2007、2008 自由软件基金会(Free Software Foundation, Inc.)http://fsf.org/

任何人都被允许复制和分发本许可证文档的逐字副本,但不允许修改它。

0. 序言#

本许可证的目的是使手册、教科书或其他功能性且实用的文档在“自由”的意义上成为自由的:确保每个人都切实拥有复制和再分发它的自由,无论是否对它作出修改,无论是出于商业目的还是非商业目的。其次,本许可证为作者和出版者保留了一种因其作品而获得认可的方式,同时不必为他人的修改承担责任。

本许可证是一种“copyleft”(著佐权),这意味着文档的衍生作品本身也必须以同样的意义是自由的。它补充了 GNU 通用公共许可证(GNU General Public License),后者是为自由软件设计的 copyleft 许可证。

我们设计本许可证,是为了把它用于自由软件的手册,因为自由软件需要自由的文档:一个自由的程序应当随附提供与软件本身同样自由的手册。但本许可证并不限于软件手册;它可以用于任何文本作品,无论其主题为何,也无论它是否作为印刷书籍出版。我们主要推荐把本许可证用于以指导或参考为目的的作品。

1. 适用范围与定义#

本许可证适用于任何介质中的任何手册或其他作品,只要其中包含版权所有者放置的声明,说明该作品可以依据本许可证的条款进行分发。此类声明授予一项全球范围、免版税、无期限限制的许可,允许在本文所述条件下使用该作品。下文所称的“文档”(Document)指任何此类手册或作品。公众中的任何成员都是被许可人,并被称作“你”(you)。如果你以依据版权法需要获得许可的方式复制、修改或分发该作品,即表示你接受本许可证。

文档的“修改版本”(Modified Version)指任何包含该文档或其一部分的作品,无论是逐字复制的,还是经过修改和/或翻译成另一种语言的。

“次要章节”(Secondary Section)是文档中一个具名的附录或前言部分;它专门论述文档的出版者或作者与该文档整体主题(或相关事项)之间的关系,并且不包含任何可直接归入该整体主题的内容。(因此,如果文档有一部分是数学教科书,次要章节不得解释任何数学内容。)这种关系可以是与该主题或相关事项之间的历史关联,也可以是就它们所持的法律、商业、哲学、伦理或政治立场。

“不变章节”(Invariant Sections)是某些次要章节,其标题在说明文档依据本许可证发布的声明中被指定为不变章节的标题。如果某一章节不符合上文对次要章节的定义,则不允许把它指定为不变章节。文档可以包含零个不变章节。如果文档未指明任何不变章节,则不存在不变章节。

“封面文字”(Cover Texts)是某些简短的文本段落,在说明文档依据本许可证发布的声明中被列为前封面文字(Front-Cover Texts)或后封面文字(Back-Cover Texts)。前封面文字最多可以为 5 个词,后封面文字最多可以为 25 个词。

文档的“透明”(Transparent)副本指机器可读的副本;它采用的格式规范对公众可用,适合用通用文本编辑器直接修订文档,或(对于由像素构成的图像)适合用通用绘图程序修订,或(对于绘图)适合用某种广泛可得的绘图编辑器修订,并且适合作为文本格式处理器的输入,或适合自动转换为各种适合作为文本格式处理器输入的格式。以其他方面属于透明格式制作、但其标记或缺少标记被安排成阻止或打消读者后续修改念头的副本,不是透明的。如果图像格式被用于承载任何大量文本,则它不是透明的。不是“透明”的副本称为“不透明”(Opaque)。

适合透明副本的格式示例包括不带标记的纯 ASCII、Texinfo 输入格式、LaTeX 输入格式、使用公开可得的 DTD 的 SGML 或 XML,以及为便于人工修改而设计的符合标准的简单 HTML、PostScript 或 PDF。透明图像格式的示例包括 PNG、XCF 和 JPG。不透明格式包括只能由专有字处理软件读取和编辑的专有格式、其 DTD 和/或处理工具并非普遍可得的 SGML 或 XML,以及某些字处理软件仅出于输出目的而生成的机器生成的 HTML、PostScript 或 PDF。

“标题页”(Title Page)对于印刷书籍而言,指标题页本身,以及为清晰容纳本许可证要求在标题页上出现的材料所需的后续若干页。对于本身没有标题页的格式的作品,“标题页”指作品标题最显著出现位置附近的文字,位于正文开始之前。

“出版者”(publisher)指任何向公众分发文档副本的个人或实体。

章节“题为 XYZ”(Entitled XYZ)指文档中一个具名的子单元:其标题恰好是 XYZ,或者在译成另一种语言的文字之后以圆括号包含 XYZ。(此处 XYZ 代表下文提到的某个具体章节名,例如“致谢”(Acknowledgements)、“题献”(Dedications)、“背书”(Endorsements)或“历史”(History)。)当你修改文档时“保留该章节的标题”(Preserve the Title),是指它依照本定义仍然是一个“题为 XYZ”的章节。

文档可以在声明本许可证适用于该文档的通知旁边包含免责声明(Warranty Disclaimers)。这些免责声明被视为以引用方式纳入本许可证,但仅就免除担保而言:这些免责声明可能具有的任何其他含义均属无效,对本许可证的含义没有任何影响。

2. 逐字复制#

你可以以任何介质复制和分发文档,无论是出于商业目的还是非商业目的,只要在所有副本中复制本许可证、版权声明以及说明本许可证适用于该文档的许可通知,并且你不得对本许可证的条件附加任何其他条件。你不得使用技术手段阻碍或控制你所制作或分发的副本的阅读或进一步复制。但是,你可以接受以副本作为交换的报酬。如果你分发的副本数量足够多,你还必须遵守第 3 节中的条件。

你也可以在上述相同条件下出借副本,并且你可以公开展示副本。

3. 批量复制#

如果你出版文档的印刷副本(或通常带有印刷封面的介质中的副本),数量超过 100 份,并且文档的许可通知要求有封面文字,那么你必须把这些副本封装在清晰可辨地载有所有这些封面文字的封面中:前封面文字印在前封面,后封面文字印在后封面。两个封面还必须清晰可辨地标明你为这些副本的出版者。前封面必须呈现完整标题,且标题的所有词都同样醒目可见。你还可以在封面上附加其他材料。仅对封面作出改动、只要保留文档标题并满足这些条件的复制,在其他方面可以视为逐字复制。

如果任一封面所需的文字过多、无法清晰容纳,你应当把列在最前面的部分(在合理范围内尽可能多)放在实际封面上,把其余部分续到相邻的页面上。

如果你出版或分发文档的不透明副本,数量超过 100 份,你必须随每份不透明副本附带一份机器可读的透明副本,或者在每份不透明副本中或随附说明一个计算机网络位置,使一般使用网络的公众能够使用公共标准网络协议从该位置下载到完整且不含附加材料的文档透明副本。如果你选择后一种方式,那么在你开始批量分发不透明副本时,必须采取合理审慎的步骤,确保该透明副本在所述位置保持可访问,直至你(直接或通过你的代理人或零售商)向公众分发该版本的某份不透明副本的最后一次之后至少满一年。

我们请求(但不要求)你在再分发任何大量副本之前尽早联系文档的作者,以便给他们机会向你提供该文档的更新版本。

4. 修改#

你可以在上文第 2 节和第 3 节的条件下复制和分发文档的修改版本,只要你完全依照本许可证发布该修改版本,由该修改版本承担文档的角色,从而向任何持有其副本的人许可对该修改版本的分发和修改。此外,你必须在修改版本中完成以下事项:

A. 在标题页(以及封面上,如果有封面)使用一个与文档标题以及与先前各版本标题都不同的标题(先前各版本的标题如果有的话,应列在文档的“历史”章节中)。如果该先前版本的原出版者给予许可,你可以使用与先前版本相同的标题。

B. 在标题页上列出对修改版本中的修改负有作者责任的一个或多个个人或实体,作为作者;同时列出文档至少五位主要作者(如果文档的主要作者少于五位,则列出其全部主要作者),除非他们免除你的这项要求。

C. 在标题页上说明修改版本的出版者名称,作为出版者。

D. 保留文档的所有版权声明。

E. 在其他版权声明旁边,为你的修改添加一项适当的版权声明。

F. 在版权声明之后紧接着包含一份许可通知,采用下文附录中所示的形式,依据本许可证的条款授予公众使用修改版本的许可。

G. 在该许可通知中保留文档许可通知中所给出的不变章节和所需封面文字的完整清单。

H. 包含本许可证的一份未经改动的副本。

I. 保留题为“历史”的章节,保留其标题,并在其中添加一项,至少说明标题页上给出的修改版本的标题、年份、新作者和出版者。如果文档中没有题为“历史”的章节,则创建一个,说明标题页上给出的文档的标题、年份、作者和出版者,然后按前一句所述添加一项描述修改版本的内容。

J. 保留文档中给出的、用于公众访问文档透明副本的网络位置(如果有的话);同样保留文档中给出的、它所依据的先前各版本的网络位置。这些可以放在“历史”章节中。对于在文档本身至少四年前发表的作品,你可以省略网络位置;如果它所指向的版本的原出版者给予许可,也可以省略。

K. 对于任何题为“致谢”或“题献”的章节,保留该章节的标题,并在该章节中保留其中给出的每一项贡献者致谢和/或题献的全部实质内容和语气。

L. 保留文档的所有不变章节,其文本和标题均不得改动。章节编号或等同物不被视为章节标题的组成部分。

M. 删除任何题为“背书”的章节。此类章节不得包含在修改版本中。

N. 不得把任何现有章节重新命名为“背书”,也不得使其标题与任何不变章节相冲突。

O. 保留任何免责声明。

如果修改版本包含符合次要章节条件、且不包含从文档复制的材料的新前言章节或附录,你可以自行选择把这些章节中的部分或全部指定为不变的。为此,请把它们的标题添加到修改版本许可通知中的不变章节清单里。这些标题必须与任何其他章节标题不同。

你可以添加一个题为“背书”的章节,只要它除了各方对你的修改版本的背书之外不包含任何内容——例如同行评审的声明,或者文本已被某个组织批准为某项标准的权威定义的声明。

你可以在修改版本的封面文字清单末尾添加一段最多五个词的前封面文字,以及一段最多 25 个词的后封面文字。任何一个实体(或通过其作出的安排)只能添加一段前封面文字和一段后封面文字。如果文档已经为同一封面包含了先前由你添加、或由你代为行事的同一实体通过安排添加的封面文字,你不得再添加另一段;但你可以根据添加旧封面文字的原出版者的明确许可替换旧的那一段。

文档的作者和出版者并不通过本许可证授予任何人使用其姓名进行宣传、或主张或暗示对任何修改版本的背书的许可。

5. 合并文档#

你可以依据上文第 4 节针对修改版本所规定的条款,把文档与依据本许可证发布的其他文档合并,只要你在合并结果中包含所有原始文档的所有不变章节且不加修改,并在其许可通知中把它们全部列为你的合并作品的不变章节,同时保留它们所有的免责声明。

合并作品只需包含本许可证的一份副本,多个相同的不变章节可以替换为单一副本。如果有多个名称相同但内容不同的不变章节,请通过在这些章节各自标题末尾以圆括号添加该章节的原作者或原出版者的名称(如果已知),或者添加一个唯一编号,使每个这样的章节标题唯一。对合并作品许可通知中不变章节清单里的章节标题也作同样的调整。

在合并结果中,你必须把各原始文档中任何题为“历史”的章节合并成一个题为“历史”的章节;同样合并任何题为“致谢”的章节,以及任何题为“题献”的章节。你必须删除所有题为“背书”的章节。

6. 文档汇编#

你可以制作一个由文档和依据本许可证发布的其他文档组成的汇编,并把各文档中本许可证的各份副本替换为该汇编中所包含的单一副本,只要你在所有其他方面对汇编中每个文档的逐字复制遵循本许可证的规则。

你可以从此类汇编中抽取单个文档,并依据本许可证单独分发它,只要你把本许可证的一份副本插入所抽取的文档,并在其他所有方面遵循本许可证关于该文档逐字复制的规则。

7. 与独立作品的聚合#

文档或其衍生作品与其他彼此独立的文档或作品一起、放在一个存储或分发介质的卷中或卷上构成的汇编,如果由该汇编产生的版权没有被用来超出各作品所允许的范围限制汇编用户的法律权利,则称为“聚合体”(aggregate)。当文档被包含在一个聚合体中时,本许可证不适用于该聚合体中本身并非文档衍生作品的其他作品。

如果第 3 节的封面文字要求适用于文档的这些副本,那么当文档不足整个聚合体的一半时,文档的封面文字可以放在把文档框在聚合体之内的封面上;如果文档是电子形式,则放在与之等同的电子封面上。否则,它们必须出现在把整个聚合体框起来的印刷封面上。

8. 翻译#

翻译被视为一种修改,因此你可以依据第 4 节的条款分发文档的译文。用译文替换不变章节需要获得其版权所有者的特别许可,但你可以在不变章节的原始版本之外,附加包含部分或全部不变章节的译文。你可以包含本许可证的译文,以及文档中的所有许可通知和任何免责声明,只要你同时包含本许可证的原始英文版本,以及那些通知和免责声明的原始版本。如果译文与本许可证、某通知或免责声明的原始版本之间出现分歧,以原始版本为准。

如果文档中某一章节题为“致谢”、“题献”或“历史”,那么(第 4 节的)保留其标题(见第 1 节定义)的要求通常将需要改动其实际标题。

9. 终止#

除本许可证明确许可的情形外,你不得复制、修改、再许可或分发文档。任何以其他方式复制、修改、再许可或分发它的企图均属无效,并将自动终止你在本许可证下的权利。

但是,如果你停止一切对本许可证的违反行为,那么来自某一特定版权所有者的许可将 (a) 暂时恢复,除非且直到该版权所有者明确且最终终止你的许可;(b) 永久恢复,如果该版权所有者在停止违反之后的 60 天内没有以某种合理方式通知你该违反行为。

此外,如果该版权所有者以某种合理方式通知你该违反行为,这是你第一次从该版权所有者处收到(针对任何作品的)违反本许可证的通知,并且你在收到通知后的 30 天内纠正了该违反行为,那么来自该特定版权所有者的许可将永久恢复。

依据本节终止你的权利,不会终止那些已依据本许可证从你处获得副本或权利的当事方的许可。如果你的权利已被终止且未永久恢复,收到相同材料的部分或全部副本并不会赋予你使用它的任何权利。

10. 本许可证的未来修订版本#

自由软件基金会可能会不时发布 GNU 自由文档许可证的新的修订版本。此类新版本在精神上将与当前版本相似,但可能在细节上有所不同,以应对新的问题或关切。参见 http://www.gnu.org/copyleft/。

本许可证的每一版本都有一个可区分的版本号。如果文档指明本许可证的某个特定编号版本“或任何更晚的版本”适用于它,你可以选择遵循该指定版本的条款和条件,或者遵循自由软件基金会已发布的(非草案的)任何更晚版本的条款和条件。如果文档未指明本许可证的版本号,你可以选择自由软件基金会曾经发布的(非草案的)任何版本。如果文档指明可由某个代理决定可以使用本许可证的哪些未来版本,那么该代理对某一版本表示接受的公开声明将永久授权你为文档选择该版本。

11. 再许可#

“大规模多作者协作网站”(“Massive Multiauthor Collaboration Site”,或“MMC 网站”)指任何发布可受版权保护的作品、并提供显著设施供任何人编辑这些作品的万维网服务器。任何人都可以编辑的公共 wiki 就是此类服务器的一个例子。网站中包含的“大规模多作者协作”(“Massive Multiauthor Collaboration”,或“MMC”)指如此在该 MMC 网站上发布的一组可受版权保护的作品。

“CC-BY-SA”指知识共享组织(Creative Commons Corporation)发布的 Creative Commons Attribution-Share Alike 3.0 许可证;该组织是一家主要营业地点位于加利福尼亚州旧金山的非营利公司;也指该组织发布的该许可证的未来 copyleft 版本。

“纳入”(Incorporate)指把某一文档的全部或部分作为另一文档的一部分予以发布或重新发布。

如果 MMC 依据本许可证获得许可,并且所有最初在 MMC 之外的某处依据本许可证发布、随后被全部或部分纳入该 MMC 的作品:(1) 没有封面文字或不变章节,(2) 因而在 2008 年 11 月 1 日之前被如此纳入,则该 MMC “有资格再许可”。

MMC 网站的运营者可以在 2009 年 8 月 1 日之前的任何时间,在同一网站上依据 CC-BY-SA 重新发布该网站中包含的 MMC,只要该 MMC 有资格再许可。

附录(Addendum):如何为你的文档使用本许可证#

要在一份你已撰写的文档中使用本许可证,请在该文档中包含本许可证的一份副本,并把以下版权和许可通知紧接放在标题页之后:

版权所有 (C) 年份 你的姓名。
兹授予许可,允许依据自由软件基金会发布的 GNU 自由文档许可证 1.3 版或任何更晚版本的条款,复制、分发和/或修改本文档;没有不变章节,没有前封面文字,也没有后封面文字。本许可证的一份副本包含在题为“GNU Free Documentation License”的章节中。

如果你有不变章节、前封面文字和后封面文字,请把“with...Texts.”那一行替换为:

以不变章节为(列出其标题),以前封面文字为(列出),以后封面文字为(列出)。

如果你有不变章节但没有封面文字,或者这三者有其他组合,请把上述两种替代写法按情况合并。

如果你的文档包含非平凡的程序代码示例,我们建议你同时以你所选的自由软件许可证(例如 GNU 通用公共许可证)发布这些示例,以便允许它们在自由软件中使用。

附录 D 索引#

D.1 shell 内建命令索引#

A#

B#

C#

D#

E#

F#

G#

H#

J#

K#

L#

M#

P#

R#

S#

T#

U#

W#

D.2 shell 保留字索引#

C#

D#

E#

F#

I#

S#

T#

U#

W#

D.3 参数与变量索引#

A#

B#

C#

D#

E#

F#

G#

H#

I#

K#

L#

M#

O#

P#

R#

S#

T#

U#

V#

D.4 函数索引#

A#

B#

C#

D#

E#

F#

G#

H#

I#

K#

M#

N#

O#

P#

Q#

R#

S#

T#

U#

Y#

D.5 概念索引#

A#

B#

C#

D#

E#

F#

H#

I#

J#

K#

L#

M#

N#

O#

P#

Q#

R#

S#

T#

U#

V#

W#

Y#