#read处理输入行的方式:处理单引号(' ')、双引号(" ")和反斜线;它依据环境变量IFS中的分隔符将行分成各个单词,并把单词赋值给shell变量。这也是shell处理命令行的邮给子集
#shell从标准输入或脚本读取的每行称为一个管道行,它包含一或多个由0个或多个管道字符(|)分隔的命令。对其读取的每个管道行,执行如下操作:
1)将命令分成由固定元字符集(meta)分隔的记号(token):SPACE、TAB、NEWLINE、;、(、)、<、>、|、和&。记号类型包括单词、关键字、I/O重定向符和分号。
2)检测每个命令的第一个记号,查看其是否为不带引号或反斜线的关键字。如果是一个开放的关键字,如if和其他控制结构起始字符串、funtion、{或 (,则命令实际上为一个复合命令。shell在内部对复合命令进行处理,读取下一命令,并重复这一过程。如果关键字不是复合命令起始字符串(例如,是一个 控制结构中间出现的关键字,如then、else、或do和类似于fi或done的结束关键字或逻辑操作符),则shell给出语法错误信号。
3)依据别名列表检查每个命令的第一个关键字。如果找到相应匹配,则替换其别名定义,并退回到第1步;否则进入第4步,该策略允许递归别名。它还允许定义关键字别名,例如alias aslongas=while或alias procedure=function
4)执行大括号扩展,例如,a{b,c}变成ab ac
5)如果~位于单词的开头,使用用户的主目录($HOME)替换~(用当前目录$PWD替换~+,用$OLDPWD替换~-。)
6)对任何以符号$开头的表达式执行参数(变量)替换
7)对形式$(string)的表达式进行命令替换
8)评估形式为$((string))的算术表达式
9)把行的参数、命令和算术替换部分再次分成单词,这次它使用$IFS中的字符做分隔符而不是步骤1中的元字符集
10)对出现的*、?、和[ ]对执行路径名扩展,也称为通配符扩展。
11)按照(别名-->关键字,例如function、if、for等-->函数-->内置命令-->根据PATH路径的脚本和可执行程序)的查找顺序寻找命令,使用第一个单词作为命令
12)完成I/O重定向和其他操作后执行该命令
#命令行的处理过程:
#例如如下命令:
alias ll="ls -l"
ll $(type -path cc) ~alice/.*$(($$00))
该行运行过程如下
1)ll $(type -path cc) ~alice/.*$(($$00))
将输入分隔成单词
2)ll不是关键字,因此步骤2执行为空
3)ls -l $(type -path cc) ~alice/.*$(($$00))
用ls -l替换其别名“ll”,然后shell重复步骤1至步骤3.步骤2将ls -l分成两个单词。
4)ls -l $(type -path cc) ~alice/.*$(($$00))
此步骤执行为空
5)ls -l $(type -path cc) /home/alice/.*$(($$00))
将~alice扩展为/home/alice
6)ls -l $(type -path cc) /home/alice/.*$((253700))
将$$替换为2537
7)ls -l /usr/bin/cc /home/alice/.*$((253700))
对“type -path cc”执行命令替换
8)ls -l /usr/bin/cc /home/alice/.*537
对算术表达式253700求值
9)ls -l /usr/bin/cc /home/alice/.*537
该步骤执行为空
10)ls -l /usr/bin/cc /home/alice/.hist537
用文件名替换通配符扩展.*537
11)在/usr/bin中找到命令ls
12)运行/usr/bin/ls,带有选项-l和两个参数
#有5种修改上述过程的方式:引用、command、builtin、enable、eval
#引用:
可以将引用看做shell忽略上面12个步骤中某些步骤的一种方式,例如:
1)单引号,绕过了前10个步骤(包括别名)。所有单引号内的字符均保持不动。不能把单引号放在单引号内(即使在前面加反斜线也不行)
2)双引号,绕过步骤1到步骤4,步骤9和步骤10。也就是,在双引号内忽略了管道字符、别名、~替换、通配符扩展、和通过分隔符分隔成单词。双引号内的 单引号没有作用。但双引号允许参数替换、命令替换和算术表达式求值。可以在双引号内包含双引号,方式是在前面加上反斜线字符。还必须使用反斜线转义$、顿 号`(旧式命令替换分隔符)和本身。
#
#如果你对一个特定shell编程环境下使用单引号还是双引号有疑问,最好使用单引号,除非你特别需要参数、命令或算术替换。
#command、builtin和enable
命令查找的默认次序是函数、内部命令、脚本和可执行代码。而command、builtin和enable这三个内置命令可以覆盖该次序。
#command删除别名和函数查找(command删除别名查找是一种副作用,因为command的第一个参数不再是bash解析的第一个单词,因此不 会遇到别名查找)。只有搜索路径中找到的内置命令和命令被执行。如果要在搜索路径下创建于内置命令或命令同名的函数,并需要从函数内调用最初的命令时,就 需要用到command命令。例如
cd () {
#some fancy things
command cd
}
#command选项
-p,使用PATH的默认值
-v,打印用来调用command的命令或路径名
-V,比使用-v选项描述的更详细信息
-,关闭进一步选项检查
[root@localhost shell]# command -V ls
ls is aliased to `ls --color=tty'
[root@localhost shell]# command -v ls
alias ls='ls --color=tty'
#builtin类似于command,但只查找内置命令,忽略函数和PATH中的命令。
#enable,使shell内置命令可用或屏蔽。
enable的选项
-a,显示所有命令以及是否可用
-d,删除使用-f载入的命令
-f filename,从共享对象filename中载入一个新的命令
-n,屏蔽一个命令或显示被屏蔽的命令列表
-p,显示所有命令列表
-s,限制输出为POSIX“特定”命令
-n,用于屏蔽一个命令,不带选项的enable使能一个命令。enable可带有多个命令参数。
可以使用enable本身或者enable -p找到当前可用和屏蔽的命令。
使用enable -n可以列出所有被屏蔽的命令。
要得到命令当前状态的完整列表,就使用enable -a
-s,选项限制输出为POSIX“特定”命令。它们是 :, ., source, break, continue, eval, exec, exit, export, readonly, return, set, shift, trap, and unset
#可以使用enable -n enable屏蔽enable本身。(小心操作)
#eval:eval可以再次执行命令行处理。例如可以编写脚本随意创建命令字符串,然后把它们传递给shell执行,这意味着你可以赋予脚本”智能”,在运行时修改其自身行为。
#eval语句通知shell接受eval参数,并再次通过命令行处理的所有步骤运行它们。例如:
[root@localhost ~]# listpage="ls | more"
[root@localhost ~]# $listpage
ls: |: No such file or directory
ls: more: No such file or directory
shell会把“|”和“more”看做ls的参数。原因是管道字符出现在shell对变量求值的步骤6,之后查找管道符,直到步骤9,变量扩展仍没有被 解析。结果是,shell把“|”和“more”看做ls的参数,试图在当前目录下查找名为“|”和“more”的文件。
#再看一个例子:
[root@localhost ~]# listpage="ls | more"
[root@localhost ~]# eval $listpage
shell到达最后一步时,运行带有参数ls,|,more的eval。这样shell就回到第一步,处理由这些参数组成的一行。在步骤2中它找到了“|”,将该行分隔成两个单词ls和more,每个命令都以正常方式处理,结果分页显示出当前目录下的文件列表
#前面有一个例子是:
"$@" > logfile 2>&1 &
我们说这个任务的限制是命令不能包含输出重定向或管道,虽然考虑前者没有意义,但要有管道的话,可以使用eval来解决这个问题:
eval "$@" > logfile 2>&1 &