6.Bash的功能
本章介绍 Bash 的特色功能。
6.1 Bash的启动
bash [长选项] [-ir] [-abefhkmnptuvxdBCDHP] [-o 选项] [-O shopt 选项] [参数 ...]
bash [长选项] [-abefhkmnptuvxdBCDHP] [-o 选 项] [-O shopt 选 项] -c string [参 数 ...]
bash [长选项] -s [-abefhkmnptuvxdBCDHP] [-o 选项] [-O shopt 选项] [参数 ...]
除了单字符的命令行选项(参见[内部命令 set])外,还可以使用一些多字符选项。
要想正确解析命令行,多字符选项必须出现在单字符选项的前面。
--debugger 在shell启动前准备调试器分析。打开扩展的调试模式(参见[内部命令 shopt])和shell函数的跟踪(参见[内部命令 set])。
--dump-po-strings 在标准输出中打印"$"后面的双引用字符串。除了输出的格式,其它和"-D"选项是等价的。
--dump-strings 和"-D"选项等价。
--help 在标准输出中打印使用帮助后成功邟出。
--init-file 文件名
--rcfile 文件名 在交互式的 shell 中执行文件名 (而不是 ~/.bashrc) 中的命令。
--login 和"-l"选项等价。
--noediting 当 shell 交互式运行时,不使用GNU Readline库来读取命令行。
--noprofile 当 Bash 作为登录 shell 启动时,不加载系统或个人的初始化文件 /etc/profile、~/.bash_profile、 ~/.bash_login、~/.profile。
--norc 在交互式的 shell 中,不读取初始化文件 ~/.bashrc。如果用 sh 来启动 shell,这个选项默认就打开了。
--posix 如果 Bash 的默认行为与 POSIX 不同,就遵循 POSIX 规范。这个选项是用来让 Bash 成为该规范的一个超集。
--restricted 打开受限制模式。
--verbose 和“-v”选项等价,回显读取的输入行。
--version 在标准输出中显示当前 Bash 的版本信息后成功退出。
启动时还可以指定一些单字符选项;这些选项是内部命令 set 所没有提供的。
-c 字符串 处理完选项以后从字符串中读取命令并执行,然后退出。剩余的参数赋值给从 $0 开始的位置参数。助记词: Command, 命令字符串
-i 强制 shell 交互式的运行。交互式的 shell 在[交互式的 shell]中介绍。助记词: Interactive, 交互的
-l 使当前 shell 表现得像登录后直接启动的那样。在交互式的 shell 中,这和用 exec -l bash 命令启动的登录 shell 是等价的。
如果不是交互式的 shell,则执行登录 shell 的初始化文件。exec bash -l 或 exec bash --login 将把当前的 shell 替换成一个登录 shell。关于登录 shell 的特殊行为,请参见[Bash 的启动脚本]。
-r 把当前 shell 变为受限制的 shell (参见[受限制的 shell])。助记词: Login, 登录的
-s 如果给定了这个选项,或者处理选项以后没有剩余的参数,则从标准输入读取命令。这个选项可以用来在启动交互式的 shell 时指定位置参数。助记词: Startupfile, 初始化文件
-D 在标准输出中打印所有"$"后面的双引用字符串。如果当前的语言区域不是C或POSIX,则要对这些字符串进行翻译。这个选项隐含了“-n“选项,并且不执行命令。助记词: Debug, 语言翻译调试
[-+]O [shopt 选项] shopt 选项是内部命令shopt所接受的选项。如果指定了shopt 选项,则”+O“就会设置这个选项,而”-O“重置它。
如果没有指定shopt选项,则在标准输出中显示shopt所接受的选项名称和值。如果这时选项是“+O”,则以一种可以重新作为输入的格式来显示。助记词: Option, 选项
-- 单个 -- 表示选项的结束并停止继续处理选项。它后面的任何参数都被当成文件名或参数。
“登录 shell”是指其第零个参数的第一个字符是“-”,或者通过“--login”选项启动的shell。
“交互式”的shell是指启动时没有非选项的参数(除非指定了“-s”选项),没有指定"-c"选项,并且其输入和输出都与终端相连(通过 isatty(3) 查看),或者用"-i"选项启动的 shell。
更多细节请参[交互式的 shell]。
如果选项处理完毕还剩余参数,这时又没有指定"-c"或"-s"选项,则第一个参数就当作是包含shell命令的文件名。
如果通过这种方式启动Bash,则 $0 就设为该文件名,而位置参数就设为其余的参数。Bash会从这个文件中读取命令并执行,然后退出。
Bash的退出状态是脚本中最后一个被执行命令的退出状态。如果没有执行任何命令,则退出状态为 0。
6.2 Bash的启动脚本
本节介绍Bash如何执行其初始化文件。
如果这些文件存在但是不可读,Bash 就会报错。
文件名中的 波浪号会被扩展。
6.2.1 作为交互式的登录shell启动,或带有"--login"选项
当 Bash 作为交互式的 shell 启动时,或作为非交互式的 shell 但带有“--login”选项,它将先读取/etc/profile 里面的命令并执行 ,
然后依次搜索 ~/.bash_profile、 ~/.bash_login、 ~/.profile,然后读取并执行第一个存在并且可读的文件。
可以在 shell 启动时指定"--noprofile"选项来禁止这种行为。
当一个登录 shell 退出时,Bash会读取并执行 ~/.bash_logout 里面的命令。
6.2.2 作为交互式的非登录shell启动
当启动一个交互式的非登录 shell 时,Bash 会读取并执行 ~/.bashrc 文件里面的命令。
这个行为可以用"--norc"选项来禁止。而"--rcfile 文件名"选项强制 Bash 从文件名中读取命令并执行,而不是从 ~/.bashrc 中。
所以,~/.bash_profile 文件通常在进行登录相关的初始化之前 (或之后) 包含下面这行:
if [ -f ~/.bashrc ]; then . ~/.bashrc; fi
6.2.3 非交互式的启动
如果 Bash 非交互式的启动,例如为了运行一个脚本,它就会在环境中寻找 BASH_ENV 变量,如果找到就把这个变量扩展后的值当作一个文件名,并且从中读取命令并执行。
这就好像执行了下面的命令:
if [ -n "$BASH ENV" ]; then . "$BASH ENV"; fi
只不过这时并没有使用 PATH 来搜索这个文件。
如上所说,如果非交互式的 shell 启动时指定了"--login"选项,Bash 就会试图从初始化文件中读取命令并执行。
6.2.4 作为sh启动
如果把 Bash 作为 sh 来启动,它就会尽量去模仿历史上的 sh 的启动行为,同时还保证遵循 POSIX 标准。
如果作为交互式的登录 shell 启动,或者作为非交互式的 shell 启动但却指定了"--login"选项,Bash会依次试图去读取 /etc/profile 和 ~/.profile 并执行其中的命令。
可以使用"--noprofile"选项来制止这种行为。
如果有交互式的 shell 中用名称 sh 来启动时,Bash 将会寻找 ENV 变量,如果找到就扩展它的值并把这个值当成文件名来读取和执行。
因为作为 sh 启动的 shell 不会读取和执行任何其它的初始化文件, 所以"--rcfile"选项不起作用。
如果用名称 sh 启动一个非交互的 shell,它就不会读取任何初始化文件。
作为 sh 启动时,Bash 会在读取初始化文件以后进入 POSIX 模式。
6.2.5 启动POSIX模式
如果通过"--posix"选项以 POSIX 模式启动 Bash,它就会按照 POSIX 规范去使用初始化文件。
在这种模式下,交互式的 shell 会扩展 ENV 变量并在扩展结果所指示的文件在读取命令并执行。它不会读取其它初始化文件。
6.2.6 由远程的shell守护进程启动
Bash 会试图把它的标准输出和一个网络连接相关联,就好像它由远程的 shell 守护进程 (通常是 rshd或 sshd)启动的一样。
如果 Bash 以这种方式启动,它就会读取并执行 ~/.bashrc 里面的命令。而如果以 sh 来启动就不这么做。
可以使用"--norc"选项来制止这种行为,或者用"--rcfile"选项来强制读取另外一个文件,但 rshd 在启动 shell 时通常都不带这些选项,或者不允许指定它们。
6.2.7 启动时实际用户(组)号和有效用户(组)号不同
如果 Bash 启动时实际用户 (组)号和有效用户 (组天)不同,并且没有指定"-p"选项,它就不会读取初始化文件,也不从环境中继承 shell 函数;
如果环境中有 SHELLOPTS 变量,也会被忽略。这时,把有效用户号设为实际用户。
如果启动时指定了"-p"选项,则启动行为仍然这样,但不设置有效用户号。
6.3 交互式的Shell
6.3.1 什么是交互式的shell
交互式的 shell 是指它启动时没有非选项的参数 (除非指定了"-s"选项),也没有指定"-c"选项,并且标准输出和标准错误输出都和终端关联 (可以用 isatty(3) 查看),或者通过"-i"选项启动。
通常,交互式的shell 都读取和写入用户的终端。
"-s"选项可以在启动交互式 shell 时设置位置参数。
6.3.2 当前的shell是交互式的吗?
如果想在初始化脚本中检测 Bash 是否以交互方式运行,可以检测特殊变量 - 的值。如果它的值包 含"i",那么它就是交互的。
另外一种方法是,检查变量 PS1 的值;这个变量在交互式的 shell 中设置,而非交互的 shell 重置了它。
代码清单4:Readline 启动脚本的例子
# 第一种方法
case "$-" in
*i*)
echo 这个是交互式的 shell。
;;
*)
echo 这个不是交互式的 shell。
;;
esac
# 第二种方法
if [ -z "$PS1" ]; then
echo 这个不是交互式的 shell。
else
echo 这个是交互式的 shell。
fi
6.3.3 交互式shell的行为
Shell在交互式运行时,会改变几个方面的行为:
(1) 读取并执行初始化文件。
(2) 默认启用作业控制。已经启用作业控制时,Bash 就会忽略来自键盘的作业控制信号 SIGTTIN、SIGTTOU、SIGTSTP。
(3) Bash 会在读取第一行命令之前先扩展并显示 PS1,在读取多行命令的第二行和其余行之前扩展并显示PS2。
(4) Bash 会在打印主提示符 PS1 之前把变量 PROMPT_COMMAND 的值当成一个命令去执行。
(5) 用 Readline从用户的终端读取命令。
(6) 在读取命令时,Bash 会检查 set -o 命令的 ignoreeof 选项的值,而不是接收到标准输入中的 EOF 后就立即退出。
(7) 默认启用命令历史和历史补全。在退出时,Bash 会把命令历史写入到 $HISTFILE 指定的文件中。
(8) 默认进行别名扩展。
(9) 如果没有定义任何陷阱,Bash 就忽略 SIGTERM 信号。
(10) 如果没有定义任何陷阱,Bash 就捕获并处理 SIGINT 信号。SIGINT 信号可能会中断某些内部命令。
(11) 如果打当了 huponexit 选项,交互式的登录 shell 在退出时会向所有的作业发送 SIGHUP 信号。
(12) 忽略启动选项"-n"。"set -n"也不会起作用。
(13) Bash 会根据变量 MAIL、MAILPATH、MAILCHECK 的值定期检查邮件。
(14) 设置 set -u 后,扩展一个末定义的变量发生的错误不会导致 shell 退出。
(15) Shell不会因为在扩展 ${var:?word} 时发生变量 var 未定义错误而退出。
(16) Shell内部命令的重定向错误不会导致退出。
(17) 如果在 POSIX 模式下运行,特殊命令返回错误状态不会导致 shell 退出。
(18) 执行 exec 失败不会导致 shell 退出。
(19) 解析器遇到语法错误不会导致 shell 退出。
(20) 默认打当了内部命令 cd 对目录参数的简单拼写更正功能。
(21) Shell会检查 TMOUNT 变量的值,如果打印提示符 $PS1 后,在其指定的秒数内没有读取到命令就会退出。
6.4 Bash条件表达式
条件表达式由复合命令 [[ 和内部命令 test 与 [ 使用。这些表达式可以是单目或者双目的。
单目表达式常常用来检测文件的状态。此外还有字符串运算符和数值比较运算符。
如果某个基本表达式的文件参数格式为 /dev/fd/N,则测试的是文件描述符 N。
如果某个基本表达式的文件参数是 /dev/stdin、/dev/stdout、/dev/stderr 之一,则测试的分别是文件描述符 0、1、2。
下面除非特别说明,下列操作文件的表达式将跟随符号链接去操作其指向的目标,而不是操作符号链接本身。
-a 文件 如果文件存在则为真。
-b 文件 如果文件存在并且是个块设备文件则为真。
-c 文件 如果文件存在并且是个字符设备文件则为真。
-d 文件 如果文件存在并且是个目录则为真。
-e 文件 如果文件存在则为真。
-f 文件 如果文件存在并且是个常规文件则为真。
-g 文件 如果文件存在并且设置了有效组号则为真。
-h 文件 如果文件存在并且是个符号链接则为真。
-k 文件 如果文件存在并且设置了"滞留位"则为真。
-p 文件 如果文件存在并且是个命名管道 (FIFO) 则为真。
-r 文件 如果文件存在并且可读则为真。
-s 文件 如果文件存在并且其大小不为零则为真。
-t 文件描述符 如果文件描述符已打开并且指向终端则为真。
-u 文件 如果文件存在并且设置了有效用户号则为真。
-w 文件 如果文件存在并且可写则为真。
-x 文件 如果文件存在并且可执行则为真。
-O 文件 如果文件存在并且被其有效用户号所拥有则为真。
-G 文件 如果文件存在并且被其有效组号所拥有则为真。
-L 文件 如果文件存在并且是个符号链接则为真。
-S 文件 如果文件存在并且是个套接字文件则为真。
-N 文件 如果文件存在并且上次读取过后被修改过则为真。
文件一 -nt 文件二 如果文件一比文件二新 (根据修改时间)或者文件一存在而文件二不存在则为真。
文件一 -ot 文件二 如果文件一比文件二旧 (根据修改时间)或者文件二存在而文件一不存在则为真。
文件一 -ef 文件二 如果文件一和文件二指向同样的设备或文件节点则为真。
-o 选项名称 如果 shell 的选项名称已设置则为真。可以用内部命令 set 的"-o"选项列出所有选项。
-z 字符串 如果字符串的长度是零则为真。
字符串 如果字符串的长度不是零则为真。
-n 字符串 如果字符串的长度不是零则为真。
字符串一 == 字符串二 如果字符串一与字符串二相等则为真。可以把 == 换成 = 以保证与 POSIX 一致。
字符串一 != 字符串二 如果字符串一与字符串二不相等则为真。
字符串一 < 字符串二 在当前语言区域中排序时,如果字符串一排在字符串二前面则为真。
字符串一 > 字符串二 在当前语言区域中排序时,如果字符串一排在字符串二后面则为真。
数值一 运算符 数值二 运算符是"-eq"、"-ne"、"-lt"、"-le"、"-gt"、"-ge"之一。在这些算术双目运算中,如果数值一分别为等于、不等于、小于、小于或等于、大于、大于或等于数值二则为真。数值一和数值二可以是正整数或负整数。
6.5 Shell的算术运算
Shell可以对算术表达式求值,它可以是 shell 扩展的结果,也可以由内部命令 let,或者 declare的"-i"选项来实现。
求值时使用固定宽度的整数,并且不检查溢出,虽然它可以捕获到除以零的情况并报错。
运算符的优先级、结合性、以及值都和C语言相同。
下列运算符按优先级分组,并按优先级从高到低的顺序列出。
i++ i-- 后增和后减
++i --i 先增和先减
- + 单目负号和正号
! ~ 逻辑取反,按位取反
** 指数
* / % 乘,除,求余
+ - 加,减
<< >> 按位左移,按位右移
<= >= < > 比较
== != 相等,不等
& 按位与
^ 按位异或
| 按位或
&& 逻辑与
|| 逻辑或
cond ? expr1 : expr2 条件运算符
= *= /= %= += -= <<= >>= &= ^= |= 赋值
expr1,expr2 逗号运算
可以使用 shell 变量作为运算数。在进行求值之前会进行参数扩展。
在表达式中,还可以通过名称直接使用变量而无需参数扩展的语法形式。
如果是通过名称而不是参数扩展来使用变量,则对于未设置或设为空值的变量在求值时为 0。
在引用到一个变量时,或者对一个用 declare -i 设置了其 integer 属性的变量进行赋值时,就把这个变量当成算术表达式进行求值。
空值运算结果为 0。
要在表达式中使用一个 shell变量,不需要设置它的 interger 属性。
以 0 开头的常量当作八进制数解释,而以"0x"或"0X"开头表示十六进制数。
此外,数值的格式是 [base#]n;其中的base是 2 到 64 之间的一个十进制数,它表示算术进制基数;而 n 是这个进制中的一个数。
如果省略了 base# 部分,则表示 10 进制。
大于 9 的数字依次用小写字母、大写字母、"@"、"_"来表示。
如果 base 小于或等于 36,则可以混合使用大小写字母来表示 10 到 35 之间的数。
求值时按运算符的优先顺序进行。括号()可以用来改变运算顺序,括号中的子表达式先求值。
6.6 别名
对于简单命令中的第一个单词,别名可以把它替换成一个字符串。
Shell维护一个别名列表,可以用内部命令 alias 或 unalias 进行设置或删除。
对于每条简单命令的第一个单词,如果没有引用,shell 都会去检查它是否有别名。如果有,这个单词就用别名中的文本替换。
"/"、"$"、"'"、"=" 以及前面列出的任一 shell 元字符都不能在别名的名称中出现;而要替换的文本中可以含有任何有效的 shell 输入,包括 shell 元字符。
检查别名时只测试替换文件中的第一个单词,但是与别名扩展后完全一样的单词不会再次被扩展。
例如,这就意味着可以把 ls 作为 ls -F 的别名,但是 Bash 不会递归的去扩展要替换的文本。
如果别名文本中的最后一个字符是空格或制表符, 则还要对命令中别名之后单词进行别名扩展。
别名可以用 alias 命令来创建或列出,并用 unalias 命令删除。
在替换的文本中没有办法像 csh 那样使用参数。如果需要参数,则应该使用 shell 函数。
在非交互运行的 shell 中不会进行别名扩展,除非打开了 shell 的 expand_aliases 选项 。
定义和使用别名的规则有点含糊。
Bash 在执行任何命令之前总是至少读取一整行。
别名是在读取命令时扩展的,而不是在执行时。
所以,同一行中另外一个命令定义的别名直到读取下一行输入时才能生效,而本行中该别名之后的命令不受它的影响。
执行函数时也有同样的问题。别名是在读取函数定义时扩展的,而不是在函数执行时,因为函数定义本身就是一条复合命令。
这样的结果就是,在函数内部定义的别名直到函数执行以后才能使用。
所以,为了安全,永远都在单独的行中定义别名,并且不要在复合命令中使用别名。
不管出于什么目的,都应该优先使用 shell 函数而不是别名。
6.7 数组
Bash 中支持一维的下标数组变量和键值数组变量。
任何变量都可以作为下标数组来使用;内部命令declear 可以显式的声明一个数组。
数组的元素个数不受限制,赋值时不限制数组下标是否连续。
下标数组使用整数或算术表达式来访问元素,下标从零开始,而键值数组使用任意字符串来访问元素。
如果用下面的语法形式给任意变量赋值就自动创建了一个下标数组:
数组名[下标]=值
其中下标被当成算术表达式,它的求值结果必须是一个大于或等于零的数。
显式声明一个数组的语法:
declare -a 数组名
declare -a 数组名[下标]
显式声明一个键值数组的语法:
declare -A 数组名
可以用内部命令 declare 或 readonly 来设置数组变量的属性。每个属性都会作用于数组中的每个元素。
可以使用下面的复合赋值语句给数组赋值
数组名=([下标=]值 ... [下标=]值 )
如果给下标数组连续下标赋值时,不需要指定下标。
如果给下标数组赋值时指定了下标,就赋值给指定下标的元素;否则,要赋值元素的下标就是该语句所赋值的最后一个下标加上一。
下标是从零开始的。
给键值数组赋值时,必须指定下标。
这种语法形式对内部命令 declear 同样有用。
对单个元素赋值可以查上面介绍的形式: 数组名[下标]=值
数组的任何元素都可以用: ${数组名[下标]} 的形式来引用。
这里必须使用大括号以免与 shell 的文件名扩展运算符向冲突。
如果下标是"@"或"*",则这个单词就扩展为数组中的所有元素。
只有在这个单词位于双引号之间时这两个下标才会有区别。
如果这个单词位于双引号中间,${变量名[*]} 就扩展为一个单独的单词,它是用 IFS 变量的第一个字符把数组名所有的元素连接在一起;而 ${数组名[*]} 把数组名中的每个元素都扩展成一个独立的单词。
如果数组中没有元素,则 ${数组名[*]} 的扩展结果为空。
如果双引用的扩展在单词中进行,则第一个参数扩展后就和原来单词的开头部分相连,而最后一个参数扩展后就和原来单词的结尾部分相连。
这和特殊变量"@"及"*"的扩展方法是类似的。
${#数组名[下标]} 会扩展成 ${数组名[下标]} 的长度。
如果下标是"@"或"*",则它就扩展为数组的长度。
使用数组时如果没有指定下标,就相当于指定了 0 作为下标。
可以用内部命令 unset 来删除数组。
如果删除时指定下标,则只删除该下标处的元素。
如果删除时只指定了数组名,则删除整个数组。
删除时指定下标为"@"或"*"也会删除整个数组。
内部命令 declare、local、readonly 都接受"-a"选项来指定下标数组,或"-A"选项来指定键值数组。
可以用内部命令 read 的"-a"选项把从标准输入中读取的一组单词赋给数组,也可以用它从标准输入中读取值后赋给指定的数组元素。
内部命令 set 和 declare 可以重新设置输入格式,以便于显示数组的值。
6.8 目录栈
目录栈是一组最近访问过的目录。
内部命令 pushd 可以在更改当前目录时把目录压入到栈中;
内部命令 popd 可以把指定的目录从栈中移除并把当前目录设为被移除的那个目录;
而内部命令 dirs 可以显示目录栈的内容。
目录栈的内容还可以从 shell 变量 DIRSTACK 中获得。
6.8.1 用于目录栈的内部命令
A.dirs
语法:dirs [+N | -N] [-clpv]
功能:列出当前目录栈中的目录。可以用 pushd 命令向目录栈添加目录,用 popd 命令从目录栈删除目录。
选项说明:
+N 显示从零开始的第 N个目录 (在不带参数执行 dirs 所列出的内容中从左开始数)。
-N 显示从零开始的第 N个目录 (在不带参数执行 dirs 所列出的内容中从右开始数)。
-c 删除目录栈中所有目录。助记词: Clean, 清除
-l 显示长列表;默认的列表会用波浪号来表示主目录。助记词: LongList, 长列表
-p 列出目录时每个目录占一行。助记词: wraP, 分行
-v 列出目录时每个目录占一行,每行前面都显示这个目录在栈中的位置。助记词: wraP, 分行
B.popd
语法:popd [+N | -N] [-n]
功能:(如果没有参数)删除目录栈中的顶端目录,并用 cd 命令进入到新的栈顶目录中。
dirs 命令列出的目录 第一个序号为 0。所以,popd 就相当于 popd +0。
选项说明:
+N 删除从零开始的第 N个目录 (在不带参数执行 dirs 所列出的内容中从左开始数)。
-N 删除从零开始的第 N个目录 (在不带参数执行 dirs 所列出的内容中从右开始数)。
-n 在目录栈中删除目录时,禁止改变目录,即只操纵目录栈
C.pushd
语法:pushd [-n] [+N | -N | 目录]
功能:在目录栈的顶端保存当前目录并进入目录中。如果没有参数,则交换栈顶的两个目录。
选项说明:
-n 在目录栈中添加目录时不按常规改变当前工作目录,而只对目录栈进行操作。
+N 轮转目录栈,从而把第 N个目录移到栈顶 (在 dirs 所列出的内容中从左开始数,第一个为零)。
+N 轮转目录栈,从而把第 N个目录移到栈顶 (在 dirs 所列出的内容中从右开始数,第一个为零)。
目录 把当前工作目录加入到栈顶,然后进入目录
6.9 提示符的控制
Bash 在每次打印主提示符之前都会检查 PROMPT_COMMAND 变量的值。
如果设置了这个变量并且其值不为空,就执行这个值中的命令,就好像这些命令是从命令行输入的一样。
此外,下表列出了可以用于这个变量的特殊字符:
\a 响铃字符。
\d 当前日期,格式为"周 月 日",例如"Tue May 26"。
\D{格式} 把格式传给 strftime3 并把其结果插入到提示符中;空的格式将会用当前语言区域的格式显示时间。大括号是必须的。
\e 转义字符。
\h 主机名中第一个"."之前的部分。
\H 主机名。
\j 当前 shell 管理的作业数目。
\l Shell所在终端设备的文件基名。
\n 换行符。
\r 回车符。
\s Shell的名称,$0 的基名 (即完整文件名中最后一个斜杠后面的部分)。
\t 24 小时制的当前时间,格式为"HH:MM:SS"。
\T 12 小时制的当前时间,格式为"HH:MM:SS"。
\@ 12 小时制的当前时间,区分上午和下午。
\A 24 小时制的当前时间,格式为"HH:MM"。
\u 当前用户的用户名。
\v Bash 的版本号,例如 2.00。
\V Bash 的发行号,即版本号加上补丁级别,例如 2.00.0。
\w 当前工作目录,其中的 $HOME 部分省略成一个波浪号 (使用 $PROMPT_DIRTRIM 变量)。
\W $PWD 的基名,其中的 $HOME 部分省略成一个波浪号。
\! 当前命令的历史编号。
\# 当前命令的命令编号。
\$ 如果有效用户号为 0 就是 # 字符,否则就是 $ 字符。
\nnn ASCII 代码为八进制数 nnn 的字符。
\\ 一个反斜杠。
\[ 开始一个不可打印字符的转义序列;可以在提示符中插入终端控制字符序列。
\] 结束一个不可打印字符的转义序列。
命令编号和历史编号通常是不一样的:一个命令的历史编号是它在历史中的位置,历史中可能包含了从历史文件中读取的命令;而命令编号是指在当前 shell 会话中已经执行的所有命令中的序号。
这个字符串在解析以后还要根据 shell 的 promptvars 选项进行参数 扩展、命令替换、算术扩展、以及引用去除 ()。
代码清单5:控制提示符实例
CL="\[\e[0m\]"
GREEN="$CL\[\e[0;32m\]"
BGREEN="$CL\[\e[0;32;1m\]"
XORG="$CL\[\e[0;36m\]"
XRED="$CL\[\e[0;35m\]"
BRED="$CL\[\e[0;35;1m\]"
ORG="$CL\[\e[0;33m\]"
DARK GRAY="$CL\[\e[1;30m\]"
CYAN="$CL\[\e[1;36m\]"
BLUE="$CL\[\e[1;34m\]"
# 为了显示方便,下面一行用命令替换进行赋值。实际使用时应该用单引号。
PROMPT COMMAND=$(
echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"
NTTY=$(tty | cut -d"/" -f3-4)
LS=$(ls | wc -l)
LSA=$(ls -a | wc -l)
L1a="$BLUE[$BGREEN\u$GREEN@\h:$NTTY\s$BLUE]$BLUE"
L1b="$ORG\t$BLUE"
L1c="$BLUE<$BRED\w$BLUE>$BLUE"
L1d="($XRED$LS/$LSA$BLUE)$BLUE"
L2="$CYAN\\\$$CL"
export PS1="$L1a-$L1b-$L1c-$L1d-\n$L2 "
#export PS1="[\u@\h:$NTTY\s]-\t-<\w:$LS/$LSA>-\n\\$ "
history -a
)
# 实际使用时,上面一行也应该改成单引号
6.10 受限制的shell
如果通过 rbash 来启动 Bash,或者启动时指定了"--restricted"或"-r"选项,则 shell 就进入受限模式。
受限制的 shell 可以用来设置一个控制更严格的环境;它在行为上和 bash 完全一样,除了下面不允许的操作:
用内部命令 cd 改变目录。
设置或重置变量 SHELL、PATH、ENV、BASH_ENV 的值。
使用包含斜杠的命令名。
在内部命令 . 的参数中指定带有斜杠的文件名。
在内部命令 hash 的"-p"选项参数中指定带有斜杠的文件名。
启动时从 shell 环境中导入函数定义。
启动时解析 shell 环境中的 SHELLOPTS。
使用重定向运算符">"、">|"、"<>"、">&"、"&>"进行重定向。
使用内部命令 exec 把当前的 shell 换成另外一个命令。
使用内部命令 enable 的"-f"或"-d"选项增加或删除内部命令。
使用内部命令 enable 来启用已经禁用的 shell 内部命令。
指定内部命令 command 的"-p"选项。
使用 set +r 或者 set +o restricted 来取消受限模式。
这些限制是在读取启动文件以后生效的。
如果要执行的命令是个 shell 脚本,rbash 就会创建一个不带任何限制的 shell 来执行这个脚本。
6.11 Bash的POSIX模式
在 Bash 启动时指定"--posix"命令行选项,或者在 Bash 运行时执行 set -o posix 命令,都会使它改变那些与 POSIX 规范不一致的行为,从而更接近于 POSIX 规范。
如果作为 sh 来启动,Bash 就会在读取启动文件之后进入 POSIX 模式。
下面是这种模式中改变的行为:
(1) 如果散表列中的命令不存在了,Bash 会在 $PATH 中重新搜索基路径。这个行为也可以用 shopt -s checkhash 得到。
(2) 如果作业退出时返回状态不为零,作业控制代码及其内部命令打印的信息是"已完成 (状态号)"。
(3) 作业退出时,作业控制代码及其内部命令打印的信息是"已停止 (信号名)",其中信号名是诸如SIGTSTP 等名称。
(4) 内部命令 bg 使用指定的格式显示后台的每个作业,但不指明哪个是当前作业,哪个是前一个作业。
(5) 在能够识别保留字的上下文中出现的保留字不会进行别名扩展。
(6) 启用了 POSIX 方式来扩展 PS1 和 PS2,使得 ! 扩展成历史编号,而 !! 扩展成 !。并且,不管promptvars 选项如何设置,都会对 PS1 和 PS2 的值进行参数扩展。
(7) 执行 POSIX 的启动文件 ($ENV)而不是正常的 Bash 文件。
(8) 只对命令名之前的赋值进行波浪号扩展,而不是对本行中所有赋值都进行。
(9) 默认的历史文件是 \/.sh_history (这是 $HISTFILE 的默认值)。
(10) kill -l 的输出是在单行中打印所有信号名,其间用空格分隔,并且不带 SIG 前缀。
(11) 内部命令 kill 不接受带有 SIG 前缀的信号名。
(12) 在执行". 文件名"时如果文件名不存在,则非交互的 shell 将会退出。
(13) 非交互的 shell 在进行算术扩展时如果遇到无效的表达式而产生语法错误就会退出。
(14) 重定向运算符不会对重定向中的单词进行文件名扩展,除非在交互式的 shell 中。
(15) 重定向运算符不会对重定向中的单词进行单词拆分。
(16) 函数名必须是有效的 shell 标志符;即它们不能包含字母、数字、下划线以外的字符,也不能以数字开头。在非交互的 shell 中如果定义的函数名无效则会导致一个严重错误。
(17) 在搜索命令时,POSIX 的特殊命令会在函数名之前找到。
(18) 如果 POSIX 的特殊命令返回错误的状态,则非交互的 shell 就会退出。严重错误是指 POSIX 规范中列出的那些,包括指定了不正确的选项,重定向错误,命令名之前的变量赋值错误,等等。
(19) 如果设置了 CDPATH,内部命令 cd 就不会在其后隐式附加当前目录。这意味着如果在 $CDPATH 的任一条目中都找不到一个有效的目录,cd 就会失败,即使其参数指定的目录在当前目录中存在。
(20) 如果变量赋值时发生错误并且其后没有跟着命令名,非交互的 shell 就会退出并返回错误状态。变量赋值会发生错误的例子包括试图给只读变量赋值。
(21) 如果 for 语句中的循环变量或者 select 语句中的选择变量是只读变量,则非交互的 shell 就会退出并返回错误状态。
(22) 不可以使用远程替换。
(23) 在 POSIX 特殊内部命令之前的赋值语句,在命令执行完毕以后还将持续保留在 shell 环境中。
(24) 在 shell 函数调用之前的赋值语句,在函数返回以后还将持续保留在 shell 环境中,好像未执行POSIX 特殊内部命令。
(25) 内部命令 export 和 readonly 会按照 POSIX 要求的格式输出内容。
(26) 内部命令 trap 显示的信号名称不带有 SIG 前缀。
(27) 内部命令 trap 不会检查其第一个参数是否是一个信号指示,并且在是信号指示时恢复该信号的处理, 除非这个参数只含有数字并且指定了一个有效的信号。
如果用户希望把某个信号的处理重置为原来的程序,则应该用"-"作为第一个参数。
(28) 如果 PATH 中不包括当前路径,则内部命令 . 和 source 就不会在当前目录中搜索文件名参数。
(29) 为执行命令替换而创建的子 shell 会继承父 shell 的"-e"选项。如果不是在 POSIX 模式中,Bash 会在这种子 shell 中重置"-e"选项。
(30) 总是启用别名扩展,即使是在非交互的 shell 中。
(31) 当用内部命令 alias 显示别名定义时,不会在每条输出前加上前导的"alias ",除非指定了"-p"选项。
(32) 如果启动内部命令 set 时没有指定选项,它不会显示 shell 函数的名称和定义。
(33) 如果启动内部命令 set 时没有指定选项,它在显示变量值时就不用引用,即使其中含有不可打印字符,除非这个值中含有 shell 元字符。
(34) 如果使用了内部命令 cd 的逻辑路径模式,并且由 $PWD 和其参数指定的目录名构成的路径不存在,cd将会失败,而不是继续尝试物理路径模式。
(35) 如果内部命令 pwd 指定了"-P"选项,它就会把 $PWD 转换成不带符号链接的路径后输出。
(36) 内部命令 pwd 会检查其打印的路径是否和当前目录相同,即使没有用"-P"选项让它去检查文件系统。
(37) 在列出历史条目时,内部命令 fc 不会指示每个条目是否已经被修改过。
(38) fc 默认使用的编辑器是 ed。
(39) 内部命令 type 和 command 不会报告不可执行的文件,尽管在 $PATH 中只有这个文件的名称匹配时 shell 会试图去执行它。
(40) 在 vi 编辑模式中,"v"命令会直接启动 vi 编辑器,而不是去检查 $VISUAL 和 EDITOR。
(41) 如果打开了 xpg_echo 选项,Bash 不会把 echo 命令的任何参数当成选项;而是对每个参数进行转义处理以后直接显示。
(42) 内部命令 ulimit 的"-c"和"-f"选项使用 512 字节的块。
还有一些 POSIX 行为,即使是 Bash 的 posix 模式在默认情况下也没有实现的。特别的:
(1) 当一个程序要编辑历史条目并且这时没有设置 FCEDIT 变量时,内部命令 fc 会再去检查 $EDITOR,而不是直接使用默认的 ed。只有在没有设置 EDITOR 时 fc 才会使用 ed。
(2) 如上所说,Bash 需要打开 xpg_echo 选项才能让内部命令 echo 完全遵循规范。
在编译时,可以指定 --enable-strict-posix-default 把 Bash 配置成默认就支持 POSIX。