3.Shell的基本功能

3.Shell的基本功能
Bash是Bourne-Again Shell的缩写。
Bourne Shell的内部命令在Bash中同样适用。
3.1 Shell语法
3.1.1 Shell操作
shell读取和执行命令时执行下面的操作:
从文件脚本或启动"-c"选项的字符串参数中,或者用户的终端上读取输入。
按照引用中所述规则把输入分解为单词和运算符。这些符号用元字符分隔。该步骤还进行别名扩展。
把符号解析为简单命令和复杂命令。
进行各种shell扩展,并把扩展后的符号分解为文件名、命令和参数的列表。
进行必要的重定向,并把重定向运算符及其参数从参数列表中去掉。
执行得到的命令。
(可选的)等待命令结束并收集其退出状态。

3.1.1.1 引用
引用在shell中用以去除某些字符或单词的特殊含义,可以用来禁止对特殊字符的特殊处理,使得保留字不再被认为是保留字,或者禁止参数扩展。
shell中每个元字符在shell中都有特殊的含义,必须引用后才能代表其自身。
Bash中引用的三种机制:转义字符、单引用、双引用。

3.1.1.2 转移字符
在Bash中没有转义的反斜杠\是转义字符,它能保留其下一个字符的字面含义,除非这个字符是换行符。
如果\在行尾,且\没有被引用,则\是行连续符,即\和换行符会从输入流中被删除,并被完全忽略掉。

3.1.1.3 单引用
把字符串用单引号'引用能保留引号内各个字符的字面含义。在单引号中不允许再出现单引号,即使它已经由反斜杠转义。
3.1.1.4 双引用
特殊含义的字符:
$获取变量值
‘保持字符串的字面值
\\对\进行转移,即保留一个\
!获取历史扩展
*获取集合全部元素
@获取集合元素个数
在双引号中含有上述特殊字符时,该特殊字符的功能依然有效。
把字符串用双引号"引用能保留引号内各个字符的字面含义,除非这些字符是$、'、\\、!。
在双引号中$和'继续保留其特殊的功能;
在双引号中\后面的字符是$、'、"、\\或者换行符时,\的含义是将后面的字符原样输出(即去除后面字符的特殊含义,\自身会被删除);
在双引号中\后面的字符是没有特殊含义的普通字符时,\被作为普通字符原样输出;
在双引号中出现"时,需要给"加\(\")。
在双引号中出现!时,会导致历史扩展,如果想保留!的字面含义,则需要给!前增加\(即\!)。
特殊变量*和@在双引号中特殊含义继续有效。

3.1.2 ANSI标准C引用
$'string'的单词会被特殊处理,会扩展成一个字符串。
转义字符会按照ANSI C标准被替换,转义字符包括:
\a 警告响铃,
\b 退格删除,
\e 转义字符(不属于ANSI C),
\f 走纸换页,
\n 新行,
\r 换行,
\t 水平制表符,
\v 垂直制表符,
\\ 反斜杠,
\' 单引号,
\cx 一个控制字符CTRL-X,
\nn 由八进制数nnn代表的一个八位字符(例如:字符A,二进制01000001,十进制65,八进制101,十六进制41),
\xHH 由十六进制数HH代表的一个八位字符。
扩展的结果是一个单引用,就好像$符不存在一样。
在很多命令(tr\awk的IFS等变量)中都需要指定单个字符,应该使用ANSI标准C引用,而不能用双引号转义。
例如: tr $'\n' ' ' file 替换命令tr,将换行符替换为空格,即把文件file的所有行用空格连接在一起。

3.1.2.1 Local专用的翻译
双引用的字符串在$后面,将使得该字符串根据当前的local被翻译过来。
如果当前的local是C或者POSIX,则忽略$。
如果字符串被翻译或者替换,则替换后的字符串是双引用的。

3.1.3 注释
在shell中,如果打开了内部命令shopt的interactive_comments选项,以#开头的单词将使得该单词及本行所有其它单词都被忽略。
默认,交互式shell已经打开了interactive_comments选项;如果关闭interactive_comments选项则不允许注释。

3.2 Shell命令
Shell命令由简单命令、复杂命令、管道命令的组成。
3.2.1 简单命令
简单命令包括空白符分隔的多个单词,其结尾是一个shell控制运算符,
其中第一个单词指定要执行的命令,后续单词都是这个命令的参数。
简单命令的返回状态是POSIX中waitpid函数规定的推出状态,如果该命令由一个信号n终止,则其退出状态是128+n。

3.2.2 管道
管道是由控制字符|或|&分隔开的一系列简单命令组成。
语法:[time [-p]] [!] 命令一 [[|或|&] 命令二 ... ]
管道里面每个命令的输出都经由管道与下一个命令的输入相连接;即每个命令都去读上一个命令的输出作为该命令的输入。
time 指管道执行完毕后输出其执行时间的统计信息,包括执行该命令所花费的总时间(钟表时间)以及用户和系统时间。
-p选项 使得输出的形式和POSIX中的规定相同。
! 对返回状态逻辑取反。
| 指命令一的标准输出作为命令二的输入。
|& 指命令一的标准错误输出作为命令二的输入。
管道里每个命令都是在自己的子shell中执行,管道的退出状态是其中最后一个命令的退出状态;
如果打开了pipefail选项,则管道的退出状态是最后一个(最右侧)返回非零的那个命令的状态;如果所有命令都成功执行,则返回零。

3.2.3 命令队列
命令队列是由一个或者管道通过运算符;、&、&&、||连接而成,最后还可以(可选的)由;、&、或换行符结束。
与命令队列是由控制运算符&&分隔的一个或多个管道。
语法:命令一 && 命令二 [ && 命令三 ...] 按照左结合方式执行
功能:当命令一执行成功(返回值为零)时才会依次执行后续命令,当遇到非零时,返回状态值。
或命令队列是由控制运算符||分隔的一个或多个管道。
语法:命令一 || 命令二 [ ||命令三 ...] 按照左结合方式执行
功能:当命令一执行不成功(返回值非零)时才会依次执行后续命令,当遇到零时返回状态值。
与队列和或队列的返回值是最后一个被执行的命令的返回值。
由;分隔的命令将相继执行,shell依次等待每个命令的结束,整个返回状态是最后一个执行命令的返回状态。
换行符(\n)作为命令分隔符时与;作为命令分隔符等价。
&作为命令分隔符时,则命令在后台执行,shell不等待命令的结束,其返回状态为0(命令执行成功),异步执行命令的标准输入将被重定向到/dev/null。

3.2.4 复合命令
复合命令是shell的编程结构体。
每个结构体都是以保留字或者控制运算符开头,然后以与之对应的保留字或者控制运算符结束。
任何与复合命令相关的重定向都作用于该复合命令里边的所有命令,除非显示覆盖。
Bash提供了循环结构、条件结构,以及将命令分组并将分组整体执行的机制。

3.2.4.1 循环结构
";"与换行符等价,即将复合命令写成一行时必须加";",将复合命令写成多行时不必加";"。
三种循环结构:
A.until
语法: until 测试命令; do 命令块; done
until 测试命令
do
测试命令
done
功能:测试命令返回非零值(不成功)就执行命令块。其返回值是命令块中最后一个被执行的命令的返回值,如果命令块未被执行则返回零。

B.while
语法: while 测试命令; do 命令块; done
until 测试命令
do
测试命令
done
功能:测试命令返回零(成功)就执行命令块。其返回值是命令块中札后一个被执行的命令的返回值。如果命令块没有被执行则返回零。

C.for
语法1:for 变量 [ in 集合 ]; do 命令块; done
for 变量 [ in 集合 ]
do
命令块;
done
功能:将集合中的每个元素逐个赋值给变量,并执行一次命令块。其返回值是命令块中最后一个被执行的命令的返回值。如果对集合中没有得到任何元素,则不执行任何命令,并返回零。
语法2:for (( 表达式一;表达式二;表达式三 )); do 命令块; done
for (( 表达式一;表达式二;表达式三 ))
do
命令块;
done
功能:计算表达式一,判断表达式二是否为真(非0),不为真则退出for循环;为真则先执行命令块,再执行表达式三,最后再次判断表达式二是否为真...如此循环,直到表达式二不为真退出for循环。
如果省略某个表达式,则被省略的表达式为真,即省略表达式二,则for循环无法退出。死循环语法:for (( ; ; )); do 命令块; done
其返回值是命 令块中札后一个被执行的命令的返回值。如果表达式的值都是假的,则返回假。
continue命令:退出本次循环。
break命令:退出本层循环。

3.2.4.2 条件结构
A.if
语法:
if 测试命令一
then
命令块一;
[elif 测试命令二
then
命令块二; ]
...
[else
其他命令块; ]
fi
功能:首先判断测试命令一,如果是真,则执行命令块一;如果是假,则依次判断测试命令二。
测试命令二如果是真,则执行命令块二;如果是假,则执行其他命令块。
整个命 令的返回值是最后一个被执行的命令的返回值。如果没有一个条件为真,则返回零

B.case
语法:
case 变量 in
模式一 | 模式二 ) 命令块;;
模式三) 命令块;;
*) 命令块;;
esac
功能:case命令会选择性的执行变量所匹配的第一个模式对应的命令块。
"|"用来分隔多个模式,")"用来结束模式列表。
模式列表和其对应的命令块叫做一个分句,每个分句都必须由";;"、",&"、";;&"结束。
如果打开了shell的 nocasematch选项,则匹配时将忽略字母的大小写。
变量在匹配之前要经过波浪号扩展、参数扩展、命令替换、算术扩展以及引用去除, 而每个模式也要经过波浪号扩展、参数扩展、命令替换、算术扩展等步骤。
case 分句的数量量不限的,但是每个分句都以";;"、",&"、";;&"结束。朂先匹配的模式决定了哪个命令块被执行。
分句结束符";;"、",&"、";;&"的区别:
如果使用了";;"来结束分句,则匹配第一个模式以后就不会再匹配其它模式。
如果使用了",&"来结束分句,则执行命令块后,如果下面还有其它分句,就继续执行该分句。
如果使用了";;&"来结束分句,则执行命令块后,如果下面还有其它分句,就检查其模式是否匹配;如果其模式为真,继续执行其对应的命令块。
返回值:如果任何模式都不匹配,该命令的返回状态是零;否则,返回最后一个被执行的命令的返回值。

C.select
select与for语法格式一致。
语法:select 变量 [ in 集合 ]; do 命令块; done
select 变量 [ in 集合 ]
do
命令块;
done
功能:集合列表将会打印到标准错误输出流中,并且每个元素前都会加上一个序数。默认集合为$@。
之后显示"# ?"提示符,并且从标准输入读取一行的输入,
如果输入的为空,则重新显示集合和提示符;
如果输入了 EOF字符,select 命令将结束;(测试不成功)
输入的值被存放在变量 REPLY 中;
如果输入的是数字,则和元素的序数匹配,匹配成功后将元素值赋值给变量,匹配不成功时将空值赋值给变量。
每次选择之后都会执行命令块,然后再次显示"# ?"提示符。如果在命令块中遇到break 命令,则退出select 命令。
例子:用户从当前目录中选择一个文件,并显示用户选择的文件名及其序号
select fname in *;
do
echo you picked $fname \($REPLY\);
# break;
done

D.((...))
语法:(( 算术表达式 ))
功能:对算术表达式求值,等价于 let "算术表达式 "
返回值:如果这个值不是零,则返回状态是零,否则返回1。

E.[[...]]
语法:[[ 条件表达式 ]]
功能: 对条件表达式求值,并根据其结果返回0或者1
条件表达式可以通过下面的运算符组合:
(表达式):使用()可以改变运算符的正常优先级。
!表达式:对表达式逻辑取反
表达式一 && 表达式二:对表达式一和表达式二进行逻辑与操作,即表达式一和表达式二均为真时结果为真,否则为假。
表达式一 || 表达式二:对表达式一和表达式二进行逻辑或操作,即表达式一和表达式二均为假时结果为假,否则为真。

3.2.4.3 命令组合
命令组合:将一系列命令放在一起作为整体执行。
A.()
把一系列命令放在括号中间就会创建一个子shell环境,并在这个子shell中执行该列表中的每个语句。
就是因为命令列表是在子shell中执行的,所以在子shell结束后,其中的变量赋值将不再有效。

B.{}
把一系列命令放在大括号中间,这一系列命令就会在当前shell中执行,而不是创建子shell。命令列表后面的;(或者换行符)是必须的。
多个命令在一行书写时用“;”分隔,“;”只有分隔作用,无其他任何含义。
任何命令的末尾都可以添加“;”,单独成行的命令也可以不用添加“;”,因为换行符也起到命令分隔的作用,所以二者是等价的。
为了代码的美观性、一致性、可读性,建议在命令末尾使用“;”。

命令组合符()和{}的区别:
()命令在子shell中执行,{}命令在当前shell中执行。
{}是保留字,{}与命令列表之间必须用空白符或其它shell的元字符分开;
而()是运算符,()与命令列表之间没有用空白符分开也会被shell当作独立的符号。
这两种结构的命令返回值都是其中命令列表的返回值。

3.2.5 协同进程
协同进程coprocess是指一个shell命令前面有coproc保留字,
协同进程在子shell中异步执行,
协同进程和其父shell进程之间有双向的管道。
语法:coproc [NAME] 命令 [重定向]
功能:创建一个名为NAME的协同进程,如果不指定[NAME]时,默认名称为COPROC。
如果命令是一个简单命令时,则不能指定 NAME,否则它会被当作命令的第一个单词。
当 coproc 执行时,shell会在父进程中创建一个名为 NAME 的数组变量。
命令的标准输出通过管道和父进程的一个文件描述符相连;该文件描述符被赋给 NAME[0]。
命令的标准输入通过管道和父进程的一个文件描述符相连;该文件描述符被赋给 NAME[1]。
这个管道是在命令当中指定的任何重定向之前就建立了。
这些文件描述符可以在shell命令和重定向中通过标准的单词扩展而当作参数使用。
用来执行协同进程的子shell的进程号保留在数组变量 NAME[PID] 中。
可以使用内部命令wait来等待协同命令的结束。
协同进程的返回状态是其中命令的返回状态。

3.3 Shell函数
shell函数把一组命令与单一的名称相关联,以便以后执行。
在执行时,它们就和常规命令一样。
如果shell函数的名称被当作一个简单命令使用,与它相关联的命令就会被执行。
shell函数是在当前的 shell环境中执行的,而不是创建新的进程来执行。
函数语法: [ function ] func_name() 复合命令块 [ 重定向 ]
说明:
func_name是定义的函数名;
保留字 funnction是可选的,如果有 function 这个保留字,则可以省略括号()。
复合命令块是函数体,函数体是包含在{和}之间的命令列表,也可以是上面列出的任何复合命令。
每当函数名被指定为一个命令名时,复合命令块就会被执行。
当函数被执行时,与之相关的重定向也会同时被执行。
可以使用内部命令 unset 的"-f"选项来取消函数的定 义。
除非发生语法错误,或者一个同名并且为只读的函数已经存在,函数定义的返回值是零。
执行时,函数的返回值是函数体内最后一个被执行命令的返回值。
注意,由于历史的原因,通常情况下,函数体外的大括号与函数体之间必须用空白符或者换行符分开。
因为大括号是保留字,但是只有它们与其中间的命令列表空格或其它shell元字符分隔时才能被识别为保留字。
此外,使用大括号时,其中间的命令列表必须用逗号、&或者换行符结束。
函数执行时,传递给它的参数成为它执行期间的位置参数。
能扩展为位置参数个数的特殊参数#将随之更新。特殊参数 0不变。
在函数执行时,变量FUNCNAME 的第一个元素被设为函数的名称。
除了DEBUG和RETURN这两个陷阱没有被继承以外,函数和其调用者之间在shell执行环境所有其它方面都完全乨样。
如果用内部命令declare设置了函数的trace属性,或者设置了内部命令set 的functrace选项,则函数也会继承调用者的DEBUG和RETURN陷阱。
如果在函数里面执行了内部命令return,则函数的执行将结束,并且返回到调用函数那里的下一个命令。
任何与RETURN陷阱相关联的命令都将在执行恢复前被执行。
函数结束时,位置参数以及特殊参数#的值恢复到函数被执行以前的状态。
如果return带有一个数值型参数,则这个参数就是函数的返回值;否则,函数的返回状态是其返回前最后一个被执行命令的返回状态。
函数本地的变量可以用内部命令 local 来声明。local变量只对函数及它使用的命令是可见的。
函数名称及其定义可以用内部命令typeset或者declare加上"-f"选项来列出;而typeset 或者 declare 加上"-f"选项只列出函数名;如果打开了 shell的extdebug选项,则还会列出源文件和行号。
通过内部命令 export 的"-f"选项可以把函数导出,使得它们在子shell中自动得以定义。
注意,如果shell函数和变量同名,则可能导致传给shell子进程的环境中有多个完全一样的名字。如果这样会引发问题,就需要避免同名。
函数可以是递归的。对递归调用的次数没有限制。

3.4 Shell参数
参数是能存储值的实体;它可以是一个名称、一个数字、或者下面列出的特殊字符之一。
变量是名称所代表的参数。
每个变量都有值以及零个或多个属性。
属性通过内部命令declare来设置。
参数通过赋值来设置。
空字符串也是一个有效的值。
参数一旦设置以后,只能通过内部命令unset才能取消设置。
参数赋值语法: 名称=[值]
如果没有给定值,则变量被赋于空字符串。
所有的值都会进行大括号扩展、参数和变量扩展、命令替换、算术扩展、以及引用去除;不会进行单词扩展、文件名扩展。
特殊变量"$@"会进行单词扩展。
如果启用了变量的 integer 属性,则把该变量当作算术表达式求值, 即使没有使用 $((...) 扩展 。
赋值语句还可以作为内部命令 alias、declare、typeset、export、readonly 和local 的参数。
赋值语句给shell变量或数组元素赋值时,可以使用"+="运算符附加或增加到变量原来的值中。
如果变量启用了integer属性并且使用"+=",则按照算术表达式对值进行求值,并把它加入到变量原来的值中后再求值。
如果对数组变量进行复合赋值时使用了"+=",则变量原来的值不会被覆盖,新的值被附加到数组中下标最大的那个元素的后面 ,或者在键值数组中新增一个键值对。
如果对字符串变量使用它,则把值进行扩展并附加在变量的值后面。

3.4.1 位置参数
位置参数是由除了单个0以外的一个或多个数字表示的参数;它是在 shell启动时由其参数赋值的,并且可以用内部命令 set 来重新赋值。
第N个位置参数可以表示为 ${N};如果 N只含有一个数字,也可以 表示为$N。含有多于一个数字的位置参数在扩展时必须放在大括号中。
位置参数不可以通过赋值语句来赋值;而应该用内部命令set 或者shift来设置或删除。
在执行shell函数时,位置参数会暂时被更换。

3.4.2 特殊参数
shell会对一些参数特殊处理,这些参数只能使用而不能对它们赋值。
* 扩展为从1开始的所有位置参数。
如果它出现在双引号中,则扩展为一个包含每个参数的单词,参数之间用特殊变量IFS的第一个字符分隔。
即"$*" 和 "$1c$2c..." 是等价的;其中,c是特殊变量IFS的第一个字符。
如果IFS没有设置,则参数之间用空格分隔。如果IFS为空,则参数直接相连,中间没有分隔。
@ 扩展为从1开始的所有位置参数。
如果它出现在双引号中,则每个参数都扩展为一个单词;即"$@" 和 "$1c" "$2c"...是等价的,其中,c 是特殊变量IFS的第一个字符。
如果IFS没有设置,则参数之间用空格分隔。如果 IFS为空,则参数直接相连,中间没有分隔。
如果这样的双引号扩展发生在单词里面,则第一个参数扩展后与原单词的开始部分连在一起,而最后一个参数扩展后与原单词的最后一个部分连在一起。
如果没有位置参数,则 "$@" 和 $@ 扩展后为空,也即它们会被删除。
# 扩展为位置参数的个数,用十进制表示。
? 扩展为最近在前台执行的命令的退出状态。
- (连字符)扩展为当前的所有选项;这些选项是启动时给定的,或者通过内部命令set打开的,或者由shell本身打当的(例如 -i 选项)。
$ 扩展为当前shell的进程号。在子shell() 中,扩展为启动shell的进程号,而不是子shell的进程号。
! 扩展为最近在后台(异步)执行的命令的退出状态。
0 扩展为shell或者shell脚本的名称。它是在shell初始化时设置的。
如果Bash启动时带有包含命令的文件名参数,$0就被设为该文件名。
如果Bash启动时带有"-c"选项,则 $0 被设为待执行字符串后面的第一个参数(如果这个参数存在)。
否则,它就是用来启动Bash的文件名,即(命令行的)第一个参数。
_ (下划线)在shell启动时,设为启动shell的绝对路径,或者在执行环境或参数列表中所传递的待执行的shell脚本的绝对路径。
随后,扩展为前一条命令的最后一个参数扩展后的值。
还可设为每个已执行命令的绝对路径,这些路径是启动时指定的并且导入到命令的执行环境中。
检查邮件时,这个变量保存邮箱文件的文件名。

3.5 Shell扩展
命令行被拆分成符号以后要进行扩展;
扩展的方式有七种:大括号扩展、波浪号扩展、参数和变量扩展、命令替换、算术扩展、单词拆分、文件名扩展。
扩展的顺序是:大括号扩展,波浪号扩展,参数、变量和算术扩展以及命令替换(按从左到右的顺序),单词拆分,以及文件名扩展。
如果系统支持,则还有另外一种扩展,即进程替换;它和参数、变量和算术扩展以及命令替换是同时进行的。
只有大括号扩展,单词扩展,以及文件名扩展在扩展时能够改变单词的数目。其它的扩展都是单个单词扩展成单个单词,唯一例外的是对"$@"和"${name[@]}"的扩展。
所有扩展完成后再进行引用去除。

3.5.1 大括号扩展
大括号扩展是一种能够生成任意字符串的机制;它和文件名扩展是相似的,但是生成的文件名不一定存在。
进行大括号扩展的模式在形式上有一个可选的前缀;其后是一组用逗号分隔的字符串,或者是一个序列表达式,它们都在一对大括号之间;最后是一个可选的后缀。
前缀部分将放在大括号中每个字符串的前面,而后缀将放在每个结果的后面,它们都是从左到右进行扩展的。
例如:
bash$ echo a{d,c,b}e
ade ace abe
序列表达式的形式是 {x .. y [ 增量 ] }
其中,x 和 y 是整数或者单个字符;而可选的增量是一个整数。
如果使用了整数,则扩展成 x 与 y 之间的 每个整数,包括 x 和 y。
所使用的整数可以用"0"开头,以使每个量都有同样的宽度。
当 x 或者 y有前导 的零时,shell会试图强制让每个生成的量都含有同样多的数位,如果不同就在前面补零。
如果使用了字符, 那么表达式就扩展成在字母表中 x 和 y 中间的每个字母,包括 x 和 y。
注意,x 和 y 必须有同样的类型。
如果指定了增量,它就是每个量之间的差值。默认的增量是 1 或者 -1,根据情况而定,如果 x 比 y 小,则增量是 1;如果 x 比 y 大,则增量是 -1。
大括号扩展在其它所有扩展之前进行;在其它扩展中特殊的字符都被保留下来。
它完全是字面上的扩展。
Bash不会对扩展的上下文字或大括号之间的文本进行任何语义解释。
为了避免与参数扩展冲突,大括 号扩展不会识别字符串中的"${"。
格式正确的大括号扩展必须包含没有被引用的起始和结束大括号,还有至少一个未被引用的逗号或者序列表达式。
格式不正确的大括号扩展不会被处理。
为了防止被认为是大括号扩展的一部分,"{"或者","可以用反斜杠转义。
当要生成的字符串具有的公共前缀比上面的例子更长时,大括号扩展通常用来简写:
mkdir /usr/local/src/bash/{old,new,dist,bugs}
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how ex}}

3.5.2 波浪号扩展
如果一个单词以未被引用的波浪号"~"开头,则其后的所有字符,直到第一个未被引用的斜杠(如果有未被引用的斜杠),都被看作波浪号前缀。
如果波浪号前缀里面的字符都没有被引用,则其中波浪号后面的所有字符就被当成一个可能存在的登录用户名。
如果这个登录名是个空字符串,波浪号就被替换成shell变量中 HOME 的值。
如果没有设置 HOME,则替换成执行该脚本那个用户的主目录。
否则,波浪号前缀就被替换成其中指定的那个登录名的主目录。
如果波浪号前缀是"~+",它就会被shell变量PWD的值取代。
如果波浪号前缀是"~-",它就会被shell变量OLDPWD的值取代(如果这个值已经设置)。
在波浪号前缀中,如果波浪号后面的字符包含数字N,前面可能还有"+"或"-",那么这个波浪号前缀就会被目录栈中相应的元素取代,这个元素是内部命令dirs以波浪号前缀中波浪号后面各字符作为参数所要显示的内容。
如果波浪号前缀中除去波浪号的部分仅仅包含数字N,而没有前导的"+"或"-",则认为指定了"+"。
如果登录名是无效的,或者波浪号扩展失败,则该单词不会被处理。
每个变量在赋值时都会检查紧接着":"或者"="是否含有波浪号前缀。如果找到,就进行波浪号扩展。
所以,在给 PATH、MAILPATH 以及 CDPATH 赋值时,可以使用带波浪号的文件名;这时,shell会用扩展后的量进行赋值。
下面的例子显示了Bash是如何处理未被引用的波浪号前缀:
~ $HOME 的值
~/foo "$HOME/foo"
~fred/foo 用户 fred 主目录中的子目录 foo,即"$PWD/foo"
~+/foo "$PWD/foo"
~-/foo "${OLDPWD-'~-'}/foo"
~N 命令"dirs +N"所显示的字符串
~+N 命令"dirs +N"所显示的字符串
~-N 命令"dirs -N"所显示的字符串

3.5.3 Shell参数扩展
字符"$"引导参数扩展,命令替换和算术扩展。
将要扩展的变量名或符号可以放在大括号中。
大括号虽然是可选的,但却可以保护待扩展的变量,使得紧跟在大括号后面的部分名称不会被扩展。
如果使用了大括号,则与这匹配的结束半边是第一个没有用反斜杠转义或不属于引用字符串的"}","}"不能嵌入在算术扩展、命令替换、或者参数扩展之中。
参数扩展语法: ${参数}
参数扩展功能: 结果用参数的值替换。
如果参数是包含多个数位的位置参数,或者参数后面的字符不应该当成是整个名称的不部分,则大括号是必须的。
如果参数的第一个字符是个感叹号,就表示某个级别的间接变量。感叹号必须紧跟在大括号后面才表示间接变量。
Bash使用后续变量的值作为新变量的名称,然后扩展这个新的变量,并用其值进行替换,而不是后续变量的值。这叫做间接扩展。
下面将介绍的${!前缀*}和 ${!名称[@]}属于例外情况。
正确使用间接变量:
在很多其它语中,可以用 $$A 来表示以 $A为名称的间接变量,而Bash中不可以,即使 ${$A}也不可以;
Bash只识别感叹号形式的间接变量。不过,这个功能在其它的shell中可能没有,
所以为了增强可移植性,可以这样写:eval echo \$$B
在下面介绍的每种情况中,名称都要进行波浪号扩展、参数扩展、命令替换和算术扩展。
如果不是进行字符串扩展,使用下面的形式时,Bash会检查参数是否已经设置或者为空;只有当参数尚未设置时才会在测试时忽略冒号后面的结果。
换句话说,如果包含了冒号,则这个运算符会测试参数是否存在,同时还会检 查它是否为空;如果忽略了冒号,就只检查变量参数是否存在。
${参数:?单词} 如果参数没有设置或者为空,则替换为单词;否则替换为参数的值。
${参数:=单词} 如果参数没有设置或者为空,则把扩展后的单词赋给参数,然后替换为参数的值。对位置参数和特殊参数,不可以这样进行赋值。
${参数:?单词} 如果参数没有设置或者为空,就把扩展后的单词(如果没有给出单词,则代之以一条大意相同的相信)写到标准错误输出中。如果当前的shell是交互式的,退出shell。否则,替换为参数的值。
${参数:+单词} 如果参数没有设置或者为空,不进行任何替换;否则,替换为扩展后的单词。
${参数:偏移量}
${参数:偏移量:长度} 扩展为参数中从偏移量开始的不超过长度个字符。
如果没有指定长度,扩展为参数中从偏移量开始的子字符串。
长度和偏移量都是算术表达式。这又叫做"子字符串扩展"。
长度的值必须是个大于或等于零的数字。如果长度的值是个小于零的数,它就会被当成参数所表示的字符串中从结尾开始的偏移量。
如果参数是"@",结果就是从偏移量开始的第长度个位置参数。
如果参数是带有“@”或“*”下标的下标数组名,则结果是该数组中从 ${参数[偏移量]} 开始的长度个元素。
负的偏移量是从数组中比最大的下标大一的数字开始的。
对键值数组进行子字符串扩展的结果没有定 义。
注意,负数的偏移量与冒号之间至少得有一个空格,这样可以避免与“:-”扩展相混淆。
查找子字符串的下标是从 0 开始的;但是如果使用了位置参数,则默认从 1 开始。
如果使用位置参数时偏移量是 0,则把 $@添加到结果前面。
${!前缀*}
${!前缀@} 扩展为名称中含有前缀的变量,以特殊变量 IFS 的第一个字符分隔。如果使用了"@",并且在双引号内扩展,则每个变量都扩展成单独的单词。
${!名称@}
${!名称*} 如果名称是个数组变量,扩展成名称内数组下标或者键名列表。
如果名称不是数组变量,则如果名称已经设置就扩展为 0,否则扩展为空值。
如果使用了"@",并且在双引号内扩展,则每个键或下标都扩展成单独的单词。
${#参数} 替换为参数扩展后所含的字符数目。如果参数是"*"或"@",则替换为位置参数的个数。如果参数是带有下标"*"或"@"的数组名,则在做的为该数组中元素的个数。
${参数#单词}
${参数##单词} 单词被扩展成一个模式,就像文件名扩展一样。
如果这个模式与参数扩展后的值开始部分匹配,则替换的结果是该模式与参数扩展后的值朼短的匹配部分(指“#”)或者最长的匹配部分(指“##”)删除以后的字符串。
如果参数是“*”或“@”,则模式删除操作就对位置参数依次进行,扩展的结果就是所得到的位置参数列表。
如果参数是带有下标“*”或“@”的数组名,则模式删除操作就对数组元素依次进行,扩展的结果就是所得到的数组元素列表。
${参数%单词}
${参数%%单词} 单词被扩展成一个模式,就像文件名扩展一样。
如果这个模式与参数扩展后的值开始部分匹配,则替换的结果是该模式与参数扩展后的值最短的匹配部分(指“%”)或者最长的匹配部分(指“%%”)删除以后的字符串。
如果参数是“*”或“@”,则模式删除操作就对位置参数依次进行,扩展的结果就是所得到的位置参数列表。
如果参数是带有下标“*”或“@”的数组名,则模式删除操作就对数组元素依次进行,扩展的结果就是所得到的数组元素列表。
${参数/模式/字符串} 模式被扩展成一个模式,就像文件名扩展一样。参数被扩展后,与模式匹配的最长部分用字符串来取代。
如果模式以“/”开头,则所有与之匹配的部分都用字符串来取代;而通常情况下,只取代第一个匹配的部分。
如果模式以“#”开头,它就只能和参数扩展后的开始部分匹配;如果模式以“%”开头,它就只能和参数扩展后的结尾部分匹配。
如果字符串为空,则与模式匹配的部分将被删除;这时,模式后面的 / 也可以省略。
如果参数是带有下标“*”或“@”的数组名,则模式删除操作就对数组元素依次进行,扩展的结果就是所得到的数组元素列表。
${参数^模式}
${参数^^模式}
${参数,模式}
${参数,,模式} 这种扩展会改变参数中字母符号的大小写。模式被扩展成一个模式,就像文件名扩展一样。
运算符“^”把匹配部分的小写字母转换为大写;运算符“,”把匹配部分的大写字母转换为小写。
“^^”和“,,”扩展转换参数扩展后的每个匹配的字符,而“^”和“,”扩展只转换参数扩展后第一个匹配的字符。
如果省略了模式,则将它当成“?”,这就与每个字符都匹配。
如果参数是“*”或“@”,则大小写转换操作就对位置参数依次进行,扩展的结果就是所得到的位置参数列表。
如果参数是带有下标“*”或“@”的数组名,则模式删除操作就对数组元素依次进行,扩展的结果就是所得到的数组元素列表。

3.5.4 命令替换
命令替换是用命令的输出取代命令本身。
语法:$( 命令 ) 或者 ` 命令 `
功能:Bash先执行命令,并把该命令的标准输出中最后面的换行符删除,用结果取代命令替换。
这个时候中间的换行符不删除,但是它们可能在单词拆分时被删除。
命令替换形式 $(cat 文件名) 可以用与其等价但速度更快的 $(< 文件名) 来取代。
如果使用了旧式的反引号,反斜杠就保留其字面含义,除非它后面是“$”、“`”夢或者“\”。第一个不是出 现在反斜杠后面的反引号结束命令替换。
如果使用了 $( 命令 ) 的形式,则括号中间的所有字符组成命令,没有任何一个会被特殊处理。
命令替换可以嵌套。
使用反引号形式进行嵌套时,里面的反引号需要用反斜杠转义。
如果命令替换出现在双引号之间,则其结果不会进行单词拆分和文件名扩展。

3.5.5 算术扩展
算术扩展可以对算术表达式求值并替换成的求值的结果。
语法:$(( 表达式 ))
功能:处理表达式时,就好像它在双引号之间,但圆括号中间的双引号不会被特殊处理。
表达式中的所有符号都会进行参数扩展,命令替换和引用去除。
算术表达式也可以嵌套。
求值时按照Shell的算术运算规则进行。
如果表达式是无效的,Bash就会在标准错误输出中打印一条错误信息,并且不会进行任何替换。

3.5.6 进程替换
如果系统支持命名管道(fifo)或能够以"/dev/fd"方式来命名打开的文件,则也就支持进程替换。
语法:<( 命令列表 ) 或者 >( 命令列表 )
功能:将"/dev/fd"下的某个文件的名称作为一个参数转递给当前的命令。
在运行时,进程的命令列表的输入和输出与一个命名管道或者“/dev/fd”目录里面的某个文件相关联。
如果使用了 >( 命令列表 ) 这种形式,对该文件的写入就为命令列表提供了输入。
如果使用了 <( 命令列表 ) 这种形式,在需要得到命令列表的输出时,应该去读取作为参数转递的文件。
注意,< 或 > 与左边括号之间不能有任何空格,否则这种结构就会被解释成重定向。
如果系统支持,进程替换就和参数及变量扩展、命令替换、还有算术扩展同时进行。

3.5.7 单词拆分
在单词拆分时,shell会扫描参数扩展、命令替换和算术运算的结果,如果它们不是在双引号之间进行的。
shell会把$IFS中的每个字符都当成分隔符,并按照这些字符把其它扩展的结果拆分成单词。
如果$IFS没有设置,或者它的值和默认的 <space><tab><newline> 完全一样,
那么在前述扩展的结果中开头或结尾出现的由 <space>、<tab>、或者 <newline> 构成的序列就会被忽略;
而由$IFS中任意字符组成的序列如果不是出现在头部或尾部就成为单词的分隔符。
如果$IFS的值不是默认的,并且含有由空格和制表符这两个空白符(称为$IFS分隔符)构成的空白符,则如果在单词的头部或尾部出现就会被删除。
$IFS中除了空白符以外的任何字符,包括与其毗邻的空白符,就成为字段的分隔符;
由$IFS空白符组成的序列也是分隔符。如果$IFS的值为空,则不拆分单词。
明确表示的空参数 ("" 或 '')会被保留下来。
由没有设置值的参数扩展后得到的未被引用的隐含空参数会被删除。
如果没有设置值的参数在双引号之间扩展,则结果的空值会被保留。
注意,如果没有进行扩展,则也不会进行单词拆分。

3.5.8 文件名扩展
单词拆分以后,BASH会在每个单词中搜索字符"*"、"?"、和"[",除非打当了"-f"选项。
如果找到其中一个,则把这个单词当作一个模式,并把与之匹配的文件名按字母顺序排列来取代它。
如果没有找到匹配的文件名,并且禁止了SHELL的 nullglob 选项,则不处理该单词;
如果打开了nullglob选项,并且没有找到匹配的文件名,这个单词就会被删除。
如果打开了failglob 选项,并且没有找到匹配的文件名,则打印一条错误信息,并且不执行当前命令。
如果打当了nocaseglob 选项,则匹配时不区分字母字符的大小写。
如果一个模式用于生成文件名,则对于文件名开头或紧跟在斜杠后面的“.”必须明确匹配,除非打当了 shell的dotglob选项。
匹配文件名时,斜杠也必须明确匹配。
在其它情况下,".“不会特殊处理。
关于 nocaseglob、nullglob、failglob、以及 dotglob 选项,请参见shopt命令。
shell变量GLOBIGNORE可以用来限制匹配的文件名。
如果设置了 GLOBIGNORE,并且匹配的文件名中又与GLOBIGNORE中的模式匹配的,将会从列表中删除。
如果设置了 GLOBIGNORE,并且它的值不为空,则文件名"."和".."总是被忽略。
但同时,给GLOBIGNORE设置一个非空的值会让shell的dotglob选项也生效,使得所有以"."开头的文件名也会被匹配。
为了像以往一样忽略以“.”开头的文件名,可以让“.*”成为GLOBIGNORE 的模式之一。
如果取消GLOBIGNORE设置,则 dotglob 也会被取消。

3.5.8.1 模式匹配
除了下面介绍的特殊模式字符,模式中出现的任何其它字符都与其自身相匹配。
模式中不能使用nul字符。反斜杠可以用来转义其后面的字符;用于转义的字符在匹配时会被忽略。
特殊模式字符必须转义以后才能按照字面含义匹配。
特殊模式字符有如下含义:
* 匹配任何字符串,包括空字符串。
如果打开了shell的 globstar 选项,并且在文件名扩展的行文中使用了“*”,连续使用两个“*”字符的模式会匹配所有文件以及零个或多个文件夹和子文件夹。
如果连续两个“*”并且后面有个“/”,那么它只匹配文件夹和子文件夹。
? 匹配任意单个字符。
[...] 匹配方括号中的任一字符。由连字符分隔的一对字符是一个“范围表达式”;
在当前语言区域(locale)和字符集中,按顺序在这两个字符之间的任一字符,包括这两个字符本身,都会被匹配。
如果"["之后的第一个字符是"!"或者"^",则匹配没有出现在方括号中的任一字符。
如果要匹配"-",可以把它放在方括号中第一个或最后一个位置。
如果要匹配“]”,可以把它放在方括号中第一个位置。
范围表达式中字符地排列顺序是由当前的语言和shell变量LC_COLLATE决定的。
例如,在默认的 C 语言区域中,“[a-dx-z]”和"[abcdxyz]"是等价的。
在许多语言区域中,字符都是按词典顺序排列的;在这些语言区域中,“[a-dx-z]“和“[abcdxyz]“通常是不等价的,例如,它可能与“[aBbCcDdxXyYz]”等价。
为了方括号表达式中使用在传统意义上的范围,可以把环境变量LC_COLLATE 或 LC_ALL 设为"C"以强制使用 C 语言区域。
在“[”和“]”中间,可以用 [:类别:] 这样的语法形式来指定“字符类别”;其中的类别是POSIX标准中定义的下列类别之一:
alnum 匹配所有字母和数字
alpha 匹配所有字母
ascii 匹配所有(ASCII)字符
blank 匹配所有空白符
cntrl 匹配所有控制字符 (即ASCII中的二十个字符)
digit 匹配所有的数字(0-9)
graph 匹配所有可显示字符(可打印字符中,空格和退格符不可显示)
lower 匹配所有小写字母
print 匹配可打印字符(非控制字符都可打印)
punct 匹配所有标点符号
space 匹配空格
upper 匹配所有大写字母
word 匹配单词里面的字符(大小写字母)
xdigit 匹配所有十六进制数字(0-9 和 A-F)
字符类别和该类别中的任一字符匹配。字符类别 word 匹配字母,数字和下划线。
在“[”和“]”中间, 还可以用 [=c=] 这样的语法形式来指定一个等价的字符类,它匹配当前语言中区域为c的所有字符。
在“[”和“]”中间,[.名称.]这样的语法形式和名称为名称的语 区域相匹配。
如果用内部命令 shopt 打开了shell的 extglob 选项,Bash就可以识别几个扩展的模式匹配运算符。
在下面的叙述中,模式列表是由“|”分隔的一个或多个模式。可以使用下面的一个或多个子模式构成复合模式。
?(模式列表) 与模式列表匹配零次或一次。
*(模式列表) 与模式列表匹配零次或多次。
+(模式列表) 与模式列表匹配一次或多次。
@(模式列表) 与模式列表中的模式之一匹配。
!(模式列表) 与模式列表中的任一模式之外的字符匹配。

3.5.9 引用去除
经过上述扩展以后,对于所有没有被引用的字符"\"、"'"、以及",如果它们不是由上述任何一种扩展产生的,就会被删除。

3.6 重定向
在执行命令之前,shell可能会用特殊的方式把它的输入和输出重新定向。
重定向还可以用来在当前的shell执行环境中打当和关闭文件。
下面讲的重定向运算符可以出现在一个简单命令的前面、中间的任何地方,也可以出现在命令的后面。
重定向按照出现的顺序从左到右处理。
在下文中,如果省略了文件描述符,并且重定向运算符的第一个字符是“<”,则指的是重定向标准输入(文件描述符为 0);
重定向运算符的第一个字符是“>”,则指的是重定向标准输出(文件描述符为 1)。
在下文中,重定向运算符后的单词,如果没有特别说明,要进行大括号扩展、波浪号扩展、参数扩展、 命令替换、算术扩展、引用去除、文件名扩展、以及单词拆分。
如果扩展后产生多个单词,Bash就会报错。
注意,重定向的顺序是很重要的。
例如,ls > 目录列表 2>&1 会把标准输出(文件描述符为1)和标准错误输出(文件描述符为2)重定向到文件目录列表中,
而命令 ls 2>&1 > 目录列表 仅把标准输出重定向到文件目录列表中,因为在标准输出被重定向到文件目录列表中之前,标准错误输出已经被复制到标准输出中了。
在进行重定向时,Bash会对下表列出的几个文件特殊处理:
/dev/fd/fd 如果fd是个有效的整数,则复制文件描述符fd。
/dev/stdin 复制文件描述符 0。
/dev/stdout 复制文件描述符 1。
/dev/stderr 复制文件描述符 2。
/dev/tcp/主机名/端口号 如果主机名是个有效的主机名称或因特网地址,并且端口号是整数型的端口号或服务名称,Bash会试图打开一个到相应套接字端口的TCP连接。
/dev/udp/主机名/端口号 如果主机名是个有效的主机名称或因特网地址,并且端口号是整数型的端口号或服务名称,Bash会试图打开一个到相应套接字端口的UDP连接。
如果不能打开或者创建文件,重定向就会失败。要谨慎使用比9大的文件描述符进行重定向,因为它们可能会和shell内部使用的文件描述符相冲突。

3.6.1 输入重定向
输入重定向会打开单词扩展后所形成的文件名以备读取,并将其作为文件描述符 n;
如果没有指定n则将其作为标准输入(文件描述符为 0)。
输入重定向的一般格式是: [n]<单词
3.6.2 输出重定向
输出重定向会打开单词扩展后所形成的文件名以备写入,并将其作为文件描述符n;
如果没有指定n则将其作为标准输出(文件描述符为 1)。
如果文件不存在,就首先创建它;如果已存在,就将它清空使得长度为零。
输出重定向的一般格式是: [n]>[|]单词
如果重定向运算符是">",并且已经打当了内部命令 set 的 noclobber 选项,则当单词扩展后所形成的文件名且是一个普通文件时,重定向将失败。
如果重定向运算符是">|",或者重定向运算符是">"且没有打当noclobber 选项,则即使单词扩展后所形成的文件名存在,重定向也会进行。

3.6.3 输出重定向的追加
这种类型的输出重定向会打开单词扩展后所形成的文件名以备追加,并将其作为文件描述符n;
如果没有指定n则将其作为标准输出(文件描述符为 1)。
如果文件不存在,就首先创建它。
追加输出重定向的一般格式是:[n]>>单词

3.6.4 输出和错误输出重定向
这种结构把标准输出(文件描述符为 1)和标准错误输出(文件描述符为 2)同时重定向到单词扩展后所形成的文件中。
标准输出和标准错误输出重定向的形式有两种:&>单词 和 >&单词
这两种形式中,优先使用第一种。
它们和下面的形式在语法上是等价的: >单词 2>&1

3.6.5 输出和错误输出重定向的追加
这种结构把标准输出(文件描述符为 1)和标准错误输出(文件描述符为 2)同时追加重定向到单词扩展后所形成的文件中。
标准输出和标准错误输出重定向的追加用下面的形式: &>>单词 和 >>单词 2>&1

3.6.6 即插即用文本
这种形式的重定向指示 shell从当前文本源中读取输入,直到遇到其中一行只包含单词(结尾不含空白符)。
这时,已经读取的所有行都被当作命令的标准输入。
即插即用文本的格式是:<<[-]单词 即插即用文本 结束符
单词不会进行参数扩展,命令替换,算术扩展,或文件名扩展。
如果单词中任一字符被引用,则结束符是单词进行引用去除后的结果,这时不会对即插即用文本进行扩展。
如果单词没有被引用,则即插即用文本中的所有行都会进行参数扩展、命令替换、和算术扩展;
这时,字符序列\newline就会被忽略,并且只能用"\"来引用字符"\"、"$"和"'"。
如果重定向运算符是"<<-",则输入行和结束符所在行中所有的在行开头的制表符都会被删除。
这样,在shell脚本中的即插即用文本就能够自然的缩进。

3.6.7 即插即用字符串
即插即用字符串是即插即用文本的一种变体,
其形式是:<<< 单词
功能:扩展单词并作为标准输入提供给命令。

3.6.8 文件描述符的复制
运算符 [n]<&单词 用来复制输入文件描述符。
如果单词扩展后是一个或多个数字,则文件描述符n就是与这个数字对应的文件描述符的拷贝。
如果单词中的数字没有指定要打当并读取的文件描述符,就会发生重定向错误。
如果单词扩展后的值是"-",就会关闭文件描述符n。
如果没有指定n,则使用标准输入(文件描述符为 0)。
运算符 [n]>&单词 用来复制输出文件描述符。
如果没有指定n,则使用标准输出(文件描述符为 1)。
如果单词中的数字没有指定要打开并写入的文件描述符,就会发生重定向错误。
作为特殊情况,如果省略n,并且单词扩展后不是一个或多个数字,则标准输出和标准错误输出就会按前面说的规则重定向。

3.6.9 文件描述符的移动
重定向运算符 [n]<&数字- 把文件描述符数字转移到文件描述符n上;
如果没有指定n,则转移到标准输入(文件描述符为 0)上。
转移到n后,(文件描述符)数字就会被关闭。
类似的,重定向运算符 [n]>&数字- 把文件描述符数字转移到文件描述符n上;
如果没有指定n,则转移到标准输出(文件描述符为 1)上。

3.6.10 打开文件描述符已备读出和写入
重定向运算符 [n]<>单词
可以打开单词扩展后的文件名以同时准备读取和写入,其文件描述符为n;
如果没有指定n,则使用文件描述符0。如果文件不存在,则首先创建它。

3.7 命令的执行
3.7.1 简单命令的扩展
执行简单命令时,shell会从左到右地进行下列扩展、赋值、重定向:
被分析器当作(在命令名称之前的)变量赋值和重定向的单词,将被保存下来以备后续处理。
不是变量赋值和重定向的单词会被扩展。如果扩展以后还有单词,则其中的第一个会被当作命令的名称,剩余的当作该命令的参数。
进行前面所说的重定向.
每个变量赋值语句中"="后面的文本在赋给变量之前会进行大括号扩展、参数扩展、命令替换、算术扩 展和引用去除。
如果结果没有产生命令名称,则变量赋值会影响到当前的shell环境;
否则,变量被加到命令的执行环境中, 而不会影响当前的shell环境。
如果某个赋值语句试图给只读变量赋值,就会发生错误,命令就会退出并返回一个非零的状态。
如果结果没有产生命令名称,也会进行重定向,但是不会影响到当前的shell环境。
如果重定向发生错误,命令就会退出并返回一个非零的状态。
如果扩展以后产生了命令名称,则按如前所说的步骤执行;否则,命令结束。
如果某个扩展含有命令替换,则命令的退出状态是替换中所执行的最后一个命令的退出状态。
如果没有命令替换,则结束命令并返回状态零。

3.7.2 命令的搜索和执行
命令拆分成单词以后,结果就得到一个简单命令及其可选的参数列表。这时,执行下面的操作。
如果命令名中不含斜杠,shell试图找到它。如果有一个同名的shell函数,则执行该函数。
如果这个名称不是函数,shell会在内部命令列表中搜索它。如果找到,则执行该内部命令。
如果这个名称既不是函数名,也不是内部命令,并且不含有斜杠,Bash会搜索 $PATH 中每个目录里面同名的可执行文件。
Bash使用一个散列表来保存可执行文件的绝对路径,以避免重复搜索 $PATH 路径。
只有当命令不在散列表中时才去搜索 $PATH 中的所有目录。
如果搜索失败,shell就会去找一个叫 command_not_found_handle 的shell函数定义。
如果这个函数存在,就调用这个函数,并把原来的命令及其参数当作参数传递给它;这个函数的就成为shell的退出状态。
如果这个函数也没有定义,shell会打印一条错误信息并返回状态127。
如果搜索成功,或者命令名中含有一个或多个斜杠,shell就会在独立的执行环境中执行这个命令。参数0设为指定的命令名,如果有剩余的参数,将传作为参数传递给命令。
如果因为文件格式和可执行文件不同导致执行失败,并且该文件不是一个目录名称,它就会被当成一个shell脚本并按照shell脚本的方法执行。
如果命令不是异步执行的,shell会等待它的结束并收集其返回状态

3.7.3 命令执行的环境
shell的执行环境包括下列内容:
shell启动时,打当它所继承的文件;这些文件可以用内部命令 exec 来重定向
当前工作目录;它是由 cd,pushd 或 popd 设置的,或者 女奨奥奬奬 启动时就继承的
创建文件所用的掩码;它是由 umask 设置,或者继承自shell的父进程
当前的陷阱,由 trap 设置
shell参数;它是通过变量赋值或者set设置的,或者从shell的父进程环境中继承来的
shell函数;它是在执行期间定义的,或者从shell的父进程环境中继承来的
启动时(默认的或通过命令行参数)或通过set打当的选项
用 shopt打开的选项
用 alias定义的shell别名
各个进程号,包括后台作业(参见命令队列),以及 $$ 和 $PPID 的值。
要执行一个不是内部命令或者shell函数的简单命令时,它会在包含下列内容的单独环境中执行。
除非特别 说明,下面的值都从shell中继承。
shell打开的文件,包括命令重定向时所作的修改和增加
当前工作目录
创建文件所用的掩码
标志为可以导出的shell变量和函数,以及命令从环境中导入的变量。
shell捕获的陷阱被重置为从shell父进程中继承来的值;shell忽略的陷阱也会被忽略
在这种单独的环境里启动的命令不会影响shell的执行环境。
命令替换,组合在括号中的命令,以及异步命令都在子shell环境中;
子shell复制了shell环境,不同的是shell捕获的陷阱被重置为从shell父进程中继承来的值。
属于管道中一部分的内部命令也在子shell环境中执行。
对子shell环境的改变不会影响到shell的执行环境。
用来执行命令替换的子shell会继承父shell的"-e"选项。
如果不是在POSIX模式下,Bash会在这些子shell中去掉"-e"选项。
如果启用了作业控制,并且命令后面有个"&",则命令的默认标准输入是空文件"/dev/null";否则,命令将会继承shell的重定向后的文件描述符。

3.7.4 环境
在一个程序启动时,它会得到一个字符串数组,这就叫做环境。
这个数组有三列成对的名称和值, 即"名称=值"。
Bash提供了几种操纵环境的方法。
启动时,shell会检查自己的环境,每找到一个名称就创建一个变量,并自动标志这个变量,使得它可以导入到子进程中。
执行的命令都继承这个环境。export 命令 和 "declare -x"可以在环境中增加和删除变量或函数。
在环境中,如果一个参数的值被修改,这个新的值取代旧的值就会成为环境的一部分。
执行的命令所继承的环境包括shell的初始环境,这些值可能在shell中被修改了,其中不包括用unset或"export -n"删除了的,但包含 export 和"declare -x"命令所增加的部分。
简单变量或函数的环境都可以通过加上参数赋值的前缀来暂时修改。
这些赋值语句只影响该命令所能访问到的环境。
如果打开了"-k"选项,则所有赋值的变量都被加入到命令的环境中,而不仅仅是命令名前面的那些变量。
当Bash启动外部命令时,$_就会被设置为这个命令的绝对路径,并传递到变量的环境中。

3.7.5 退出状态
命令执行以后,它的退出状态就是系统调用waitpid或与其等价的函数所返回的值。
返回状态总是介于0和255之间,具体将在下面解释;125以上的值使用比较特殊。
shell的内部命令和复合命令的退出状态也是位于这个范围。
在某些情况下,shell将使用特殊的状态来表示具体的错误状态。
为了便于shell处理,对成功执行的命令,它的退出状态是零;而非零的退出状态表示失败了。
使用这种看起来不直观的方法是因为,这样就可以很好的区分成功和多种不同的失败状态。
如果命令接收到一个值为N的关键信号而退出,Bash就会把128+N作为它的退出状态。
如果命令没有找到,用来执行它的子进程就会返回状态127。
如果命令跃然找到但却不是可执行的,就返回状态126。
如果命令因为扩展或重定向时发生错误而失败,则它的返回状态比零大。
Bash的条件命令以及部分命令队列使用了退出状态。
Bash的所有内部命令都会在成功时返回状态零,失败时返回非零,所以它们可以用于条件命令和命令队列中。
如果使用不正确,所有的内部命令都会返回状态2。

3.7.6 信号
如果Bash是交互运行的,并且没有任何陷阱,它就会忽略SIGTERM(所以“kill 0”不会杀死一个交互式的shell),但会捕获并处理SIGINT(所以内部命令wait是可以中断的)。
当Bash接收到SIGINT时,会退出任何正在进行的循环。
在任何情况下,Bash都会忽略SIGQUIT。
如果启用了作业控制(参见作业控制),Bash会忽略SIGTTIN、SIGTTOU、SIGTSTP。
Bash启动的非内部命令会使用shell从其父进程继承的信号处理程序。如果没有启用作业控制,异步执行的命令除了有这些信号处理程序,还会忽略SIGINT和SIGQUIT。
因为命令替换而运行的命令会忽略键盘产生的作业控制信号SIGTTIN,SIGTTOU和SIGTSTP。
默认情况下,shell接收到SIGHUP后会退出。在退出之间,交互运行的shell会向所有的作业,不管是正在运行还是已经停止,重新发送SIGHUP。
对已经停止的作业,shell还会发送SIGCONT以确保它能够接收到SIGHUP。
为了阻止shell向某个特定的作业发送SIGHUP信号,可以用内部命令disown(参见作业控制内部命令)将它从作业表中删除,或者用disown -h让它不接收SIGHUP。
如果使用 shopt打开了shell的huponexit选项,当一个交互运行的登录 shell退出时,会向所有的作业发送 SIGHUP。
如果Bash在等待命令结束时接收到了一个信号,并且已经给这个信号设置了陷阱,则该陷阱直到命令结束时才会执行。
如果Bash通过内部命令wait在等待一个异步命令时接收到了一个信号,并且已经给这个信号设置了陷阱,则内部命令wait在执行完该陷阱后将立即返回一个大于128的状态。

3.8 Shell脚本
shell脚本是包含shell命令的文本文件。
如果Bash启动时把这个文件用作为第一个不是选项的参数, 并且没有使用"-c"或"-s"选项,Bash会从该文件中读取命令并执行,然后退出。
这种方式会产生一个非交互运行的shell;这个shell会首先在当前目录中搜索该文件,如果没有找到就会继续搜索$PATH中的目录。
Bash运行shell脚本时,会把特殊参数0设为该脚本的名称,而不是shell的名称。
如果还有其它参数,就把其余的参数当作位置参数;如果没有其它参数,就不设置位置参数。
可以用chown命令打开shell脚本的执行位使得它成为可执行文件。如果Bash在$PATH中搜索命令时找到了这个文件,就会产生一个子shell来执行它。
换句话说,如果文件名是一个可执行的shell脚本,则执行:文件名 参数
就相当于执行:Bash 文件名 参数
这个子shell会重新初始化,效果就等同于启动了一个新的shell来执行该脚本;
所不同的是,父进程存储的各命令的路径(参见内部命令hash)将会在子进程中沿用。
大多数Unix版本在执行命令时都包含这样的机制:如果脚本的第一行由“#!”这两个字符开头,则这一行其余的内容就指定该脚本的解释器。
因此,可以指定Bash、awk、Perl、或者其它解释器,并在脚本的这行后面用这些语言来写。
在脚本文件的第一行,解释器名称后面可以包含一个单一的选项,以作为该解释器的参数;后面是脚本的名称,再后面是其余参数。
在不能处理这些参数的操作系统里面,Bash会处理它们。
注意,有些比较老的Unix版本规定,解释器名称和其参数总长度不得超过32个字符。
Bash脚本的开头通常是 #!/bin/bash(假定 Bash安装在"/bin"下面),因为这样能保证该脚本用Bash来解释,即使它在其它shell下执行。

猜你喜欢

转载自www.cnblogs.com/BradMiller/p/11833476.html
今日推荐