本文为 Bash Reference Manual第6章:Bash Features 第11节:Bash and POSIX 的读书笔记。
完整的笔记目录参见Bash学习笔记总目录。
6.11 Bash and POSIX
6.11.1 What is POSIX?
POSIX 是一系列基于 Unix 的标准的名称。许多 Unix 服务、工具和功能都是该标准的一部分,涵盖从基本的系统调用和C 库函数到常用应用程序和工具,再到系统管理和维护的各个方面。
💡 POSIX 是 Portable Operating System Interface 的缩写。
POSIX Shell 和实用工具标准最初由 IEEE 工作组 1003.2(POSIX.2)制定。1003.2 标准的第一版于 1992 年发布。它后来与原 IEEE 1003.1 工作组合并,目前由奥斯汀集团(IEEE、The Open Group 和 ISO/IEC SC22/WG15 的联合工作组)维护。如今,Shell 和实用工具是组成 IEEE Std 1003.1-2024 文档集的一部分,因此原来的 POSIX.2(1992 年)现在成为当前统一 POSIX 标准的一部分。
《Shell 和实用程序》卷主要关注命令解释器界面以及通常从命令行或由其他程序执行的实用程序。该标准可在网上免费获取,网址为 The Open Group Base Specifications Issue 8。
Bash 关注的是由 POSIX 《Shell 和实用程序》卷定义的 shell 行为方面。shell 命令语言当然已经被标准化,包括基本的流程控制和程序执行构造、I/O 重定向和管道、参数处理、变量扩展以及引用。
特殊内建命令必须作为 shell 的一部分来实现以提供所需功能,这些命令被指定为 shell 的一部分;例如 eval 和 export。其他实用程序则出现在 POSIX 的非 shell 部分,这些实用程序通常(在某些情况下必须)作为内建命令来实现,如 read 和 test。POSIX 还规定了 shell 交互行为的某些方面,包括作业控制和命令行编辑。只有 vi 风格的行编辑命令被标准化;由于存在异议,emacs 编辑命令未被包含。
6.11.2 Bash POSIX Mode
尽管 Bash 是 POSIX shell 规范的一种实现,但在某些方面,Bash 的默认行为与规范有所不同。Bash 的 POSIX 模式会改变这些方面的行为,使其更严格地符合标准。
使用 --posix 命令行选项启动 Bash,或在 Bash 运行时执行 'set -o posix',将使 Bash 在默认行为与 POSIX 规范不同时,修改行为以更符合 POSIX 标准。
当以 sh 调用时,Bash 会在读取启动文件后进入 POSIX 模式。
以下列表列出了在启用 POSIX 模式时发生的更改内容:
1 . Bash 确保 POSIXLY_CORRECT 变量被设置。
2 . Bash 会读取并执行 POSIX 启动文件($ENV),而不是正常的 Bash 文件(参见 Bash 启动文件)。
3 . 即使在非交互式 shell 中,别名扩展始终是启用的。
4 . 在保留字被识别的上下文中出现的保留字不会进行别名扩展。
5 . 别名扩展在最初解析命令替换时进行。默认的(非 POSIX)模式通常会延迟它,当启用时,直到命令替换执行时才进行。这意味着在命令替换最初解析之后定义的别名不会被命令替换扩展(例如,作为函数定义的一部分)。
6 . time 保留字可以单独作为简单命令使用。以这种方式使用时,它会显示 shell 及其完成的子进程的时间统计信息。TIMEFORMAT 变量控制时间信息的格式。
7 . 如果下一个标记以 '-' 开头,解析器不会将 time 识别为保留字。
8 . 当解析并展开出现在双引号中的 KaTeX parse error: Expected 'EOF', got '#' at position 235: ...变间接函数可用,但不能应用于"#̲"和"?"这两个特殊参数。 1...*`不会被视为双引号。
-
当双引号字符(""'')出现在正在进行扩展的 here-文档正文中,以反引号命令替换形式出现时,特别受到重视。这意味着,例如,双引号字符前的反斜杠会逃逸,反斜杠也会被移除。
-
命令替换不会设置"?"特殊参数。一个没有命令词的简单命令的退出状态仍然是在评估该命令变量赋值和重定向时发生的最后一次命令替换的退出状态,但这要等到所有赋值和重定向都完成后才会发生。
-
在PATH变量元素中作为第一个字符出现的字面波浪不会如上文波浪展开中所述展开。
-
命令查找在shell函数之前找到POSIX特殊内置,包括类型和命令构建文件打印的输出。
-
即使在进入POSIX模式前定义了包含斜杠的shell函数,shell也不会执行包含一个或多个斜杠的函数。
-
当哈希表中的命令不存在时,Bash 会重新搜索 P A T H 以找到新的位置。这也可以通过" s h o p t − s c h e c k h a s h "获得。 21. B a s h 不会在没有执行位设置的情况下插入命令到命令哈希表,即使它通过 PATH以找到新的位置。这也可以通过"shopt -s checkhash"获得。 21. Bash 不会在没有执行位设置的情况下插入命令到命令哈希表,即使它通过 PATH以找到新的位置。这也可以通过"shopt−scheckhash"获得。21.Bash不会在没有执行位设置的情况下插入命令到命令哈希表,即使它通过PATH搜索返回了(最后的)结果。
-
当作业以非零状态退出时,作业控制代码和内置命令打印的消息是'Done(status)'。
-
当作业被停止时,作业控制代码和内置命令打印的消息是'Stopped(signame)',其中 signame 例如是 SIGTSTP。
-
如果 shell 是交互式的,Bash 在执行由 ';' 或换行分隔的命令列表之间不会进行作业通知。非交互式 shell 会在列表中的前台作业完成后打印状态消息。
-
如果 shell 是交互式的,Bash 会等待到下一个提示符才打印后台作业状态变化或由于信号终止的前台作业状态。非交互式 shell 在前台作业完成后立即打印状态消息。
-
Bash 在通过 wait 或 jobs 内置命令通知用户作业终止后,会永久地从作业表中移除该作业。它在通知用户作业终止后会从作业列表中移除该作业,但只要 wait 提供了 PID 参数,仍然可以通过 wait 获取该作业的状态。
-
vi 编辑模式在运行 'v' 命令时会直接调用 vi 编辑器,而不会检查 VISUAL 和 EDITOR。
-
提示符扩展使 POSIX PS1 和 PS2 中的 '!' 扩展为历史编号,'!!' 扩展为 '!',并且无论 promptvars 选项的设置如何,Bash 都会对 PS1 和 PS2 的值进行参数扩展。
-
默认历史文件是 ~/.sh_history(这是 shell 分配给 $HISTFILE 的默认值)。
-
即使启用了 histexpand 选项,当在双引号字符串内时,'!' 字符也不会引入历史扩展。
-
在打印 shell 函数定义(例如通过 type)时,Bash 不会打印函数保留字,除非必要。
-
如果算术扩展中的语法错误导致无效表达式,非交互式 shell 会退出。
-
如果出现参数扩展错误,非交互式 shell 会退出。
-
如果 POSIX 特殊内建命令返回错误状态,非交互式 shell 会退出。致命错误是 POSIX 标准中列出的那些,包括传递错误选项、重定向错误、在命令名之前的变量赋值错误等。
-
如果在赋值语句后没有命令名,非交互式 shell 在变量赋值错误时会以错误状态退出。例如,当试图给只读变量赋值时,会发生变量赋值错误。
-
如果在特殊内建命令之前的赋值语句中发生变量赋值错误,非交互式 shell 会以错误状态退出,但在其他简单命令中不会。对于任何其他简单命令,shell 会中止该命令的执行,并在顶层继续执行("shell 不应对发生错误的命令执行任何进一步处理")。
-
如果 for 语句中的迭代变量或 select 语句中的选择变量是只读变量或名称无效,非交互式 shell 会以错误状态退出。
-
如果 . filename 中的文件未找到,非交互式 shell 会退出。
-
如果通过 . 或 source 内建命令读取的脚本,或由 eval 内建命令处理的字符串存在语法错误,非交互式 shell 会退出。
-
如果 export、readonly 或 unset 内建命令得到的参数不是有效标识符,并且它们不作用于 shell 函数,这些错误会迫使 shell 退出,因为它们是特殊内建命令。
-
在 POSIX 特殊内建命令之前的赋值语句在内建命令完成后会保留在 shell 环境中。
-
内建命令 command 并不会阻止那些将赋值语句作为参数的内建命令将其展开为赋值语句;在非 POSIX 模式下,如果 declaration 命令前面跟有 command,则其赋值语句展开属性将失效。
-
启用 POSIX 模式会导致 inherit_errexit 选项被设置,因此用于执行命令替换的子 shell 将继承父 shell 的 -e 选项值。当 inherit_errexit 选项未启用时,Bash 会在这些子 shell 中清除 -e 选项。
-
启用 POSIX 模式会导致 shift_verbose 选项被设置,因此当 shift 的数字参数超过位置参数的数量时,会产生错误信息。
-
启用 POSIX 模式会导致 interactive_comments 选项被设置(参见 注释)。
-
如果 . 和 source 内建命令未能通过搜索 PATH 找到 filename 参数,则不会搜索当前目录。
-
当 alias 内建命令显示别名定义时,除非使用 -p 选项,否则不会在定义前加上 'alias '。
-
bg 内建命令使用所需的格式描述每个被放到后台的作业,该格式不包括作业是否为当前作业或上一个作业的标识。
-
当 cd 内建命令以逻辑模式调用时,如果由 $PWD 和作为参数提供的目录名构造的路径名未指向现有目录,则 cd 将失败,而不会退回到物理模式。
-
当 cd 内建命令无法切换目录时,如果由 $PWD 和作为参数提供的目录名构造的路径名在规范化后长度超过 PATH_MAX,cd 将尝试使用所提供的目录名。
-
当启用 xpg_echo 选项时,Bash 不会尝试将 echo 的任何参数解释为选项。echo 会在转换转义序列后显示每个参数。
-
export 和 readonly 内建命令以 POSIX 要求的格式显示其输出。
-
在列出历史记录时,fc 内建命令不会显示历史条目是否被修改。
-
fc 使用的默认编辑器是 ed。
-
fc 将多余的参数视为错误,而不是忽略它们。
-
如果向 fc -s 提供的参数过多,fc 会打印错误信息并返回失败。
-
'kill -l' 的输出将所有信号名称打印在一行上,用空格分隔,不带 'SIG' 前缀。
-
kill 内建命令不接受带有 'SIG' 前缀的信号名称。
-
如果任何 pid 或作业参数无效,或者向其中任何一个发送指定信号失败,kill 内建命令将返回失败状态。在默认模式下,如果信号成功发送到任意指定进程,kill 将返回成功。
-
printf 内建命令使用 double(通过 strtod)来转换与浮点格式说明符对应的参数,而不是使用 long double 即使它可用。'L' 长度修饰符会强制 printf 使用 long double(如果可用)。
-
pwd 内建命令会验证其打印的值是否与当前目录相同,即使没有使用 -P 选项检查文件系统。
-
read 内置命令可能会被已经设置 trap 的信号中断。如果 Bash 在执行 read 时接收到被捕获的信号,trap 处理程序会执行,且 read 返回的退出状态大于 128。
-
当 set 内置命令在没有选项的情况下调用时,它不会显示 shell 函数的名称和定义。
-
当 set 内置命令在没有选项的情况下调用时,它会显示变量值而不加引号,除非变量值包含 shell 元字符,即使结果包含不可打印字符。
-
test 内置命令在评估 '<' 和 '>' 二元操作符时会使用当前的本地化环境比较字符串。
-
test 内置命令的 -t 一元主操作符要求必须有参数。历史版本的 test 在某些情况下会让参数可选,而 Bash 会尽量兼容这些情况以保持向后兼容性。
-
trap 内置命令显示信号名称时不会带前缀 SIG。
-
trap 内置命令不会检查第一个参数是否可能是信号规格以便将信号处理恢复到原始状态,除非该参数完全由数字组成且是有效的信号编号。如果用户想将某个信号的处理器复位为原始状态,应将 '-' 作为第一个参数。
-
trap -p 无参数时会显示其处理方式被设置为 SIG_DFL 及启动 shell 时被忽略的信号,而不仅仅是被捕获的信号。
-
type 和 command 内置命令不会将非可执行文件报告为已找到,但如果在 $PATH 中找到了唯一同名文件,shell 会尝试执行此文件。
-
ulimit 内置命令对 -c 和 -f 选项使用 512 字节的块大小。
-
如果尝试取消设置只读或不可取消设置的变量,并且使用了 -v 选项,unset 内建命令会返回致命错误,这将导致非交互式 shell 退出。
-
当请求取消设置出现在命令前的赋值语句中的变量时,unset 内建命令还会尝试取消设置当前作用域或前一个作用域中同名的变量。这实现了所需的"如果赋值变量被工具进一步修改,则工具所做的修改应当保持"行为。
-
当 SIGCHLD 信号到来且为 SIGCHLD 设置了 trap 时,不会中断 wait 内建命令,也不会导致它立即返回。trap 命令会为每个退出的子进程执行一次。
-
bash 在 wait 内建命令返回已退出的后台进程状态后,会从该状态列表中移除该进程的状态。
示例:
bash
$ man -k posix
aio (7) - POSIX asynchronous I/O overview
attributes (7) - POSIX safety concepts
lockf (3) - apply, test or remove a POSIX lock on an open file
mq_overview (7) - overview of POSIX message queues
nptl (7) - Native POSIX Threads Library
POSIX (3pm) - Perl interface to IEEE Std 1003.1
posix_fadvise (2) - predeclare an access pattern for file data
posix_fallocate (3) - allocate file space
posix_madvise (3) - give advice about patterns of memory usage
...
$ shopt -o posix
posix off
$ set -o posix
$ shopt -o posix
posix on
$ echo $POSIXLY_CORRECT
y
$ time
user 0m0.11s
sys 0m0.09s
还有一些额外的 POSIX 行为,即使在 POSIX 模式下,Bash 默认也不实现。具体包括:
- POSIX 要求单词拆分是按字节进行的。也就是说,IFS 值中的每个字节都有可能拆分一个单词,即使该字节是 IFS 中多字节字符的一部分,或是单词中多字节字符的一部分。Bash 允许 IFS 值中的多字节字符,将一个合法的多字节字符视为单个分隔符,即使组成该字符的某一个字节出现在 IFS 中,也不会拆分这个合法的多字节字符。这是 POSIX 解释 1560,并在 issue 1924 中进一步修改。
- fc 内建命令在 FCEDIT 未设置时会检查 $EDITOR 作为编辑历史记录条目的程序,而不是直接默认使用 ed。如果 EDITOR 未设置,fc 执行 ed。
- 如上所述,Bash 需要启用 xpg_echo 选项,echo 内建命令才能完全符合标准。
Bash 可以配置为默认符合 POSIX,通过在构建时指定 --enable-strict-posix-default 选项来配置(参见可选功能)。