什么是 GNU:Bash - GNU Shell

作者:Chet Ramey

Bash 是 shell,或称命令行解释器,它将出现在 GNU 操作系统中。“Bash”这个名字是 “Bourne-Again SHell” 的首字母缩写,是对 Steve Bourne 的双关语,他是当前 Unix shell /bin/sh 的直接祖先的作者,该 shell 出现在 Unix 第七版贝尔实验室研究版本中。

Bash 是与 sh 兼容的 shell,它融合了 Korn shell (ksh) 和 C shell (csh) 的实用功能,本文稍后将对此进行描述。它的最终目标是忠实地实现 IEEE POSIX Shell 和工具规范(IEEE 工作组 1003.2)。它为交互式和编程使用提供了比 sh 更多的功能改进。

虽然 GNU 操作系统很可能包含 Berkeley shell csh 的一个版本,但 Bash 将是默认 shell。像其他 GNU 软件一样,Bash 具有很强的可移植性。它目前几乎可以在每个版本的 Unix 和其他一些操作系统上运行——OS/2 有一个独立支持的端口,并且有传言称将移植到 DOS 和 Windows NT。移植到类 Unix 系统(如 QNX 和 Minix)是发行版的一部分。

什么是 POSIX?

POSIX 最初是 Richard Stallman 为基于 Unix 的一系列开放系统标准创造的名称。Unix 的许多方面都在考虑标准化,从系统调用和 C 库级别的基本系统服务到应用程序和工具,再到系统管理。每个标准化领域都分配给 1003 系列中的一个工作组。

POSIX Shell 和工具标准是由 IEEE 工作组 1003.2 (POSIX.2) 开发的。它专注于命令行解释器接口和通常从命令行或由其他程序执行的实用程序。该标准的初始版本已获得 IEEE 批准并发布,目前正在进行更新工作。1003.2 标准中有四个主要工作领域

  • shell 语法和命令语言的各个方面。许多特殊的内置命令(如 cd 和 exec)被指定为 shell 的一部分,因为它们的功能通常无法通过单独的可执行文件来实现;

  • 一组供 shell 脚本和应用程序调用的实用程序。例如 sed、tr 和 awk 等程序。本节描述了通常作为 shell 内置命令实现的实用程序,例如 test 和 kill。本节范围的扩展,称为用户可移植性扩展或 UPE,已将 vi 和 mailx 等交互式程序标准化;

  • 一组 shell 提供的服务的功能接口,例如传统的 system() C 库函数。有函数可以执行 shell 单词扩展、执行文件名扩展 (globbing)、获取 POSIX.2 系统配置变量的值、检索环境变量的值 (getenv()) 和其他服务;

  • 一套“开发”实用程序,例如 c89(POSIX.2 版本的 cc)和 yacc。

Bash 关注 POSIX.2 定义的 shell 行为的各个方面。shell 命令语言当然已经标准化,包括基本流程控制和程序执行结构、I/O 重定向和管道、参数处理、变量扩展和引用。必须作为 shell 的一部分实现的特殊内置命令(以提供所需的功能)被指定为 shell 的一部分;这些命令的示例包括 eval 和 export。其他实用程序出现在 POSIX.2 的非 shell 部分,这些实用程序通常(在某些情况下必须)作为内置命令实现,例如 read 和 test。POSIX.2 还将 shell 交互行为的各个方面指定为 UPE 的一部分,包括作业控制和命令行编辑。有趣的是,只有 vi 风格的行编辑命令被标准化了;由于反对意见,emacs 编辑命令被排除在外。

虽然 POSIX.2 包括 shell 传统上提供的许多内容,但一些重要内容因“超出其范围”而被省略。例如,没有提及登录 shell 和任何其他交互式 shell 之间的区别(因为 POSIX.2 没有指定登录程序)。也没有定义固定的启动文件——标准没有提及 .profile。

Bash 的基本功能

由于 Bourne shell 为 Bash 提供了大部分哲学基础,因此 Bash 从 sh 继承了其大部分特性和功能。Bash 实现了所有传统的 sh 流程控制结构(for、if、while 等)。所有 Bourne shell 内置命令,包括 POSIX.2 标准中未指定的那些,都出现在 Bash 中。Shell 函数是在 Bourne shell 的 SVR2 版本中引入的,类似于 shell 脚本,但使用特殊语法定义,并在与调用 shell 相同的进程中执行。Bash 具有与 sh 函数向上兼容的方式运行的 shell 函数。Bash 以与 sh 相同的方式解释某些 shell 变量,例如 PS1、IFS 和 PATH。Bash 基本上实现了与 Bourne shell 相同的语法、参数和变量扩展语义、重定向和引用。当 POSIX.2 标准和传统 sh 行为之间出现差异时,Bash 遵循 POSIX。

Korn Shell (ksh) 是 Bourne shell 的后代,由 David Korn 在 AT&T 贝尔实验室编写。它提供了许多 POSIX 和 Bash 已采用的实用功能。POSIX.2 中的许多交互式工具都源于 ksh。例如,POSIX 和 ksh 作业控制工具几乎相同。Bash 包含了 Korn Shell 的功能,用于交互式使用和 shell 编程。

对于编程,Bash 提供了诸如 RANDOM 和 REPLY 等变量、typeset 内置命令、基于模式从变量中删除子字符串的能力以及 shell 算术。

  • RANDOM 每次被引用时都会扩展为一个随机数。为 RANDOM 赋值会播种随机数生成器。

  • REPLY 是 read 内置命令在未提供变量名作为参数时使用的默认变量。

  • typeset 内置命令用于定义变量并赋予它们诸如只读之类的属性。

Bash 算术允许评估表达式并替换结果。Shell 变量可以用作操作数,表达式的结果可以分配给变量。C 语言中几乎所有的运算符都可用,并且具有相同的优先级规则

   $ echo $((3 + 5 * 32)) 163

对于交互式使用,Bash 实现了 ksh 风格的别名和内置命令,例如 fc(如下所述)和 jobs。Bash 别名允许用字符串替换命令名称。它们可以用于为 Unix 命令名称创建助记符(例如,alias del=rm),将单个单词扩展为复杂命令(例如,alias news='xterm -g 80x45 -title trn -e trn -e -S1 -N &),或确保使用基本选项集调用命令(例如,alias ls="/bin/ls -F")。

C shell (csh) 最初由 Bill Joy 在加州大学伯克利分校编写。它因其交互式工具而被广泛使用和非常受欢迎。Bash 包括与 csh 兼容的历史记录扩展机制(“! history”)、大括号扩展、通过 pushd、popd 和 dirs 内置命令访问目录堆栈以及波浪号扩展,以生成用户的主目录。波浪号扩展也被 Korn Shell 和 POSIX.2 采用。

在某些领域,POSIX.2 认为标准化是必要的,但没有现有的实现提供正确的行为。工作组发明并标准化了这些领域的功能,Bash 实现了这些功能。发明了 command 内置命令,以便可以编写 shell 函数来替换内置命令;它使内置命令的功能可用于函数。保留字“!”被添加到否定命令或管道的返回值;使用 sh 语言几乎不可能干净地表达“if not x”。

test 内置命令存在多种不兼容的实现,该命令测试文件类型和其他属性,并执行算术和字符串比较。POSIX 认为这些都不正确,因此标准行为是根据命令的参数数量指定的。POSIX.2 规定了当向 test 提供四个或更少参数时会发生什么,并保留

当提供更多参数时,行为未定义。Bash 使用由 David Korn 构思的 POSIX.2 算法。

Bourne Shell 中没有的功能

Bash 和大多数其他 Unix 版本中存在的 sh 版本之间存在许多细微的差异。其中大部分是由于 POSIX 标准造成的,但有些是 Bash 采用其他 shell 功能的结果。例如,Bash 包括新的“!”保留字、command 内置命令、read 内置命令正确返回以反斜杠结尾的行的能力、umask 内置命令的符号参数、变量子字符串删除、获取变量长度的方法以及 POSIX.2 标准的新 test 内置命令算法,这些都没有出现在 sh 中。

Bash 还实现了“$(...)”命令替换语法,该语法替换了 sh `...` 结构。“$(...)”结构扩展为括号内命令的输出,并删除尾随换行符。为了向后兼容性,接受 sh 语法,但首选“$(...)”形式,因为其引用规则更简单,并且更容易嵌套。

Bourne shell 没有诸如大括号扩展、具有相同名称的变量和函数的能力、shell 函数中的局部变量、启用和禁用单个内置命令或编写函数来替换内置命令的能力,或者将 shell 函数导出到子进程的方法等功能。

Bash 通过不使用 $IFS 变量来拆分 shell 读取的每个单词,而是仅拆分扩展结果,从而弥补了长期存在的 shell 安全漏洞(ksh 和 4.4 BSD sh 也修复了此问题)。在 Bourne shell 中也不存在诸如中止使用“.”命令读取的脚本的执行或自动将 shell 环境中的变量导出到子进程的有用行为。Bash 为交互式使用和编程提供了更强大的环境。

Bash 特有功能

本节详细介绍了一些使 Bash 独一无二的功能。它们中的大多数都提供了改进的交互式使用,但也存在一些编程改进。这些功能的完整描述可以在 Bash 文档中找到。

启动文件

Bash 执行启动文件的方式与其他 shell 不同。Bash 行为是 csh 启动文件原则(为每个 shell 执行具有固定名称的启动文件)和 sh “极简主义”行为之间的折衷。作为登录 shell 启动的 Bash 交互式实例会读取并执行 ~/.bash_profile(用户主目录中的 .bash_profile 文件)(如果存在)。交互式非登录 shell 会读取并执行 ~/.bashrc。非交互式 shell(例如,为执行 shell 脚本而启动的 shell)不读取固定的启动文件,但使用变量 $ENV 的值(如果已设置)作为启动文件的名称。ksh 为每个 shell 读取 $ENV 的做法,以及为交互式和非交互式 shell 定义正确的变量和函数或仅为交互式 shell 读取文件带来的困难,被认为过于复杂。易用性在这里胜出。

新的内置命令

有一些内置命令是 Bash 中新增或扩展的。

  • enable 内置命令允许任意打开和关闭内置命令。要使用用户搜索路径中找到的 echo 版本而不是 Bash 内置命令,“enable -n echo”就足够了。

  • help 内置命令提供了 shell 功能的快速概要,而无需访问手册页。

  • Builtin 与 command 类似,因为它绕过 shell 函数并直接执行内置命令。通过 pushd、popd 和 dirs 内置命令提供对 csh 风格的目录堆栈的访问。

  • Pushd 和 popd 分别在堆栈中插入和删除目录,dirs 列出堆栈内容。

  • 在允许细粒度资源控制的系统上,可以使用 ulimit 内置命令来调整这些设置。Ulimit 允许用户控制(除其他外)是否生成核心转储、shell 或子进程允许分配多少内存以及子进程创建的文件可以增长到多大。

  • 当作业控制处于活动状态时,suspend 命令将停止 shell 进程;大多数其他 shell 不允许像这样停止自身。

  • Type 是 Bash 对 which 和 whence 的回答,它显示了当一个单词作为命令键入时会发生什么

$ type export export is a shell builtin $ type -t export builtin $
type bash bash is /bin/bash $ type cd cd is a function cd () {
    builtin cd ${1+"$@"} && xtitle $HOST: $PWD
}

各种模式指示命令词是什么(保留字、别名、函数、内置命令或文件),或者将根据用户的搜索路径执行哪个版本的命令。其中一些功能已被 POSIX.2 采纳并整合到 command 实用程序中。

编辑和补全

Bash 的一个亮点是命令行编辑。Bash 使用 readline 库在交互式时读取和编辑行。Readline 是一个强大而灵活的输入工具,用户可以根据自己的喜好进行配置。它允许使用 emacs 或 vi 命令编辑行,其中这些命令是合适的。emacs 的全部功能不存在——例如,无法使用 M-x 执行命名命令——但现有命令已绰绰有余。vi 模式符合 POSIX.2 标准化的命令行编辑。

Readline 是完全可定制的。除了基本命令和键绑定之外,该库还允许用户使用启动文件定义其他键绑定。inputrc 文件(默认为 ~/.inputrc 文件)在每次 readline 初始化时读取,允许用户在多个程序中保持一致的界面。Readline 包括一个可扩展的接口,因此每个使用该库的程序都可以添加自己的可绑定命令和特定于程序的键绑定。Bash 使用此工具添加执行历史记录扩展或 shell 单词扩展的绑定。

Readline 解释了许多变量,这些变量进一步调整其行为。变量存在以控制是否将 8 位字符直接读取为输入或转换为元前缀键序列(元前缀键序列由第八位归零的字符组成,前面是元前缀字符,通常是转义符,它选择备用键映射),以决定是否直接输出第八位设置的字符或作为元前缀键序列,在编辑的行长于屏幕宽度时是否换行到新的屏幕行,后续键绑定应应用到的键映射,甚至当 readline 想要响铃终端的铃声时会发生什么。所有这些变量都可以在 inputrc 文件中设置。

启动文件理解一组类似 C 预处理器的条件结构,这些结构允许根据使用 readline 的应用程序、当前使用的终端或编辑模式分配变量或键绑定。用户可以添加特定于程序的绑定,以使他们的生活更轻松:我有允许我编辑 $PATH 值并为当前或上一个单词添加双引号的绑定

# Macros that are convenient for shell interaction $if Bash # edit the
path "\C-xp": "PATH=${PATH}\\C-e\C-a\f\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" # Quote the current or previous word
"\C-xq": "\b\"\f\"" $endif

有一个 readline 命令可以重新读取文件,因此用户可以编辑文件、更改某些绑定并几乎立即开始使用它们。

Bash 实现了 bind 内置命令,以便比启动文件允许的更动态地控制 readline。Bind 以多种方式使用。在列表模式下,它可以显示当前键绑定、列出所有可用于绑定的 readline 编辑指令、列出哪些键调用了给定指令,或以可以直接合并到 inputrc 文件中的格式输出当前键绑定集。在批处理模式下,它直接从文件读取一系列键绑定并将它们传递给 readline。在其最常见的用法中,bind 接受单个字符串并将其直接传递给 readline,readline 将该行解释为好像它是刚从 inputrc 文件中读取的一样。键绑定和变量赋值都可以出现在提供给 bind 的字符串中。

readline 库还为单词补全提供了接口。当键入补全字符(通常是 TAB 键)时,readline 会查看当前输入的单词,并计算当前单词是有效前缀的文件名集。如果只有一个可能的补全,则直接插入其余字符,否则将文件名集的公共前缀添加到当前单词。在非唯一补全之后立即输入的第二个 TAB 字符会导致 readline 列出可能的补全;有一个选项可以立即显示列表。Readline 提供了钩子,以便应用程序可以在尝试默认文件名补全之前提供特定类型的补全。这非常灵活,尽管它不是完全用户可编程的。例如,Bash 可以补全文件名、命令名称(包括别名、内置命令、shell 保留字、shell 函数和文件系统中找到的可执行文件)、shell 变量、用户名和主机名。它使用一组启发式方法,虽然不完美,但通常非常擅长确定要尝试哪种类型的补全。

本文将在下个月继续。

加载 Disqus 评论