Compound Commands 复合命令
复合命令是 shell 编程语言的结构。每个构造都以保留字或控制运算符开始,并以相应的保留字或运算符终止。与复合命令关联的任何重定向(请参阅 Redirections )都适用于该复合命令中的所有命令,除非显式覆盖。
在大多数情况下,复合命令描述中的命令列表可以通过一个或多个换行符与命令的其余部分分隔开,并且后面可以用换行符代替分号。
Bash 提供循环结构、条件命令以及对命令进行分组并将其作为一个单元执行的机制。
Looping Constructs 循环
Bash 支持以下循环结构。
请注意,无论何处 ‘;’(分号) 出现在命令语法的描述中,它可以被一个或多个换行符(newline)替换。
until
until 命令的语法为:
until test-commands; do consequent-commands; done
只要测试命令(test-commands)的退出状态不为零,就执行后续命令(consequent-commands)。返回状态是后续命令(consequent-commands)中执行的最后一个命令的退出状态,如果后续命令(consequent-commands)没有执行(none),则返回零。
while
while 命令的语法为:
while test-commands; do consequent-commands; done
只要测试命令(test-commands)的退出状态为零,就执行后续命令(consequent-commands)。返回状态是后续命令(consequent-commands)中执行的最后一个命令的退出状态,如果后续命令(consequent-commands)没有执行(none),则返回零。
for
for 命令的语法为:
for name [ [in [words …] ] ; ] do commands; done
将words扩展(请参阅 Shell Expansions )为一个列表,将列表中的元素依次赋值给变量(name)并执行命令体(commands)。如果 ‘in words’ 被忽略, for 命令针对每个设置的位置参数执行一次命令体(commands),就好像已被指定为(参见 Special Parameters )。‘in “$@”’ 一样。
返回状态是最后执行的命令的退出状态。如果 words 的扩展中没有项目,则不执行任何命令,并且返回状态为零。
for 命令的替代形式(C语言模式):
for (( expr1 ; expr2 ; expr3 )) ; do commands ; done
此种for循环根据下述规则运行:首先,对算术表达式 expr1 求值(参见 Shell Arithmetic ),然后重复计算算术表达式 expr2 ,直到其计算结果为零。每次 expr2 计算结果为非零值时,都会执行命令块(commands)并计算算术表达式 expr3 。如果省略任何表达式,则其行为就好像其计算结果为 1。返回值是命令体(commands)中执行的最后一个命令的退出状态,如果任何表达式无效,则返回值是 false 。
break 和 continue 内置函数(请参阅 Bourne Shell Builtins )可用于控制循环执行。
Conditional Constructs 条件结构
if
if 命令的语法为:
if test-commands; then
consequent-commands;
[elif more-test-commands; then
more-consequents;]
[else alternate-consequents;]
fi
执行测试命令(test-commands)列表,如果其返回状态为零,则执行后续命令(consequent-commands)列表。如果测试命令(test-commands)返回非零状态,则依次执行每个 elif 列表,如果其退出状态为零,则执行相应的更多的后续命令(more-consequents )并完成命令。如果 'else alternate-consequents’语句存在,并且 if 或 elif 子句中的测试命令返回非零状态,则执行其他命(alternate-consequents)。返回状态是最后执行的命令的退出状态,如果没有条件测试为真,则返回零。
case
case 命令的语法为:
case word in
[ [(] pattern [| pattern]…) command-list ;;]…
esac
case 将选择性地执行与单词(word)匹配的第一个模式(pattern)对应的命令块(command-list )。按照模式匹配(Pattern Matching)中描述的规则进行匹配。如果启用了 nocasematch shell 选项(请参阅 The Shopt Builtin 中对 shopt 的说明),则执行匹配时忽略字母字符的大小写。这个 ‘|’ (管道符)用于分隔多个模式,并且 ‘)’(后圆括号)运算符代表模式列表结束。模式列表和关联的命令列表称为子句(clause)。
每个子句必须以 ‘;;’, ‘;&’,或者 ';;&'结尾。在尝试匹配之前,单词(word)会经历波形符扩展、参数扩展、命令替换、算术扩展和引号删除(请参阅 Shell Parameter Expansion )。每个模式(pattern)也会经历波形符扩展、参数扩展、命令替换、算术扩展、进程替换和引号删除。
可以有任意数量的 case 子句,每个子句以’;;’, ‘;&',或者 ';;&‘结尾。第一个匹配的模式决定了要执行的命令列表。这是一个常见的习语,使用“*’ 作为定义默认情况的最终模式,因为该模式将始终匹配。
以下是在脚本中使用 case 的示例,可用于描述动物的一个有趣特征:
echo -n "Enter the name of an animal: "
read ANIMAL
echo -n “The $ANIMAL has "
case $ANIMAL in
horse | dog | cat) echo -n “four”;;
man | kangaroo ) echo -n “two”;;
*) echo -n “an unknown number of”;;
esac
echo " legs.”
如果使用 ‘;;’ 运算符,在第一个模式匹配之后不会尝试后续匹配。使用 ‘;&‘代替’;;’ 导致执行继续与下一个子句关联的命令块(command-list(如果有))。使用 ‘;;&‘代替’;;’ 使 shell 测试下一个子句中的模式(如果有),并在成功匹配时执行任何关联的 command-list ,继续执行 case 语句,就好像模式列表不匹配一样。
下面是关于不同结束符的演示:
以正常使用的结束符 ’;;’ 的脚本运行结果作为参照,case语句符合预期,第一次匹配之后结束匹配。
以结束符 ’;&’ 结束的脚本会“一泻千里”,将匹配子句后的剩余子句全部执行,无论匹配与否。(这个“一泻千里”用在这里不是我的首创,好像哪本书上就是这么用的,因为别致,所以印象深刻)
以结束符 ’;;&’ 结束的脚本将匹配子句后的剩余子句继续寻找匹配,如果匹配则执行相应的命令块。
混用的情况下:使用 ‘;;’ 作为结束符的子句的符号 ‘;;‘,在 ’;&’ 和 ‘;;&‘ 子句向下执行(或者寻找匹配中)起到阻断作用,一旦 ’;;’ 前的命令块被执行,’;;‘ 就开始发挥其阻断作用,停止向下的执行(或者匹配)。
如果模式(pattern)匹配失败,则返回状态为零。否则,返回状态是命令块(command-list)执行的退出状态。
select
select 构造可以轻松生成菜单。它的语法与 for 命令几乎相同:
select name [in words …]; do commands; done
in 之后的单词列表被扩展,生成一个项目列表,并且扩展的单词集被打印在标准错误输出流上,每个单词前面都有一个数字。如果 ‘in words’ 被省略,位置参数被打印,就像使用了 ‘in “$@”’ 一样。然后, select 显示 PS3 提示符并从标准输入读取一行。如果该行包含与所显示的字之一相对应的数字,则 name 的值将设置为该字。如果该行为空,则再次显示文字和提示。如果读取 EOF ,则 select 命令完成并返回 1。读取的任何其他值都会导致 name 设置为空。读取的行序列号保存在变量 REPLY 中。
每次选择后都会执行 commands ,直到执行 break 命令,此时 select 命令完成。
这是一个示例,允许用户从当前目录中选择文件名,并显示所选文件的名称和索引。
select fname in *;
do
echo you picked KaTeX parse error: Can't use function '\(' in math mode at position 7: fname \̲(̲REPLY)
break;
done
((…))
(( expression ))
算术运算符中的表达式 (expression) 根据下述规则运行(参见 Shell Arithmetic )。表达式(expression) 扩展是与双引号内的扩展相同,但表达式(expression)中的双引号字符没有经过特殊处理,而是被删除。如果表达式的值非零,则返回状态为0;否则返回状态为1。
[[…]]
[[ expression ]]
根据条件表达式 expression 的计算结果返回状态 0 或 1。表达式由bash条件表达式( Bash Conditional Expressions )中描述的元素组成。 [[ 和 ]] 之间的字(word)不会进行单词拆分和文件名扩展。shell 对这些单词执行波形符扩展、参数和变量扩展、算术扩展、命令替换、进程替换以及引号删除(如果单词用双引号括起来,则会发生扩展)。条件运算符不能放置在引号中,例如 ‘-f’ 必须不加引号才能被正确识别。
当与 [[ 一起使用时,‘<’ 和 ‘>’ 运算符使用当前区域设置按字典顺序排序。
当使用 ‘==’ 和 ‘!=’ 运算符的时候,运算符右侧的字符串被视为模式并根据 模式匹配(Pattern Matching) 中描述的规则进行匹配,就像启用了 extglob shell 选项一样。运算符 ‘=’ 与 ’ ’ 相同。如果启用了 nocasematch shell 选项(请参阅 The Shopt Builtin 中对 shopt 的说明),则执行匹配时忽略字母字符的大小写。如果字符串匹配 ('‘) 或不匹配 (’!=') 模式下,条件成立则为真(ture 、0)否则为假(false 、1)。
如果使用 shell 的任何引用机制引用模式的任何部分,则引用的部分将按字面匹配。这意味着引用部分中的每个字符都与其自身匹配,而不具有任何特殊的模式匹配含义。
二元运算符’=~‘, 在[[ expression ]]中是可用的,优先级与 ’ ==’ 和 '!='相同,代表的是使用正则表达式进行匹配。当你使用 ‘=~’ ,运算符右侧的字符串被视为 POSIX 扩展正则表达式模式并进行相应匹配(参见man regex(3))。如果字符串与模式匹配,则返回值为 0;如果不匹配,则返回值为 1。如果正则表达式语法不正确,则条件表达式返回 2。如果启用了 nocasematch shell 选项(请参阅 The Shopt Builtin 中对 shopt 的说明),则执行匹配时忽略字母字符的大小写。
您可以引用模式的任何部分,以强制引用的部分按字面匹配,而不是作为正则表达式(参见上文)。如果模式存储在 shell 变量中,则引用变量扩展会强制整个模式按字面匹配。
如果该模式与字符串的任何部分匹配,则该模式将匹配。如果您想强制模式匹配整个字符串,请使用正则表达式运算符的锚定模式 ’ ^’ 和 ‘$’。
举例说明, shell 变量 line 中的值的实例:如果值中任意位置存在由任意数量(包括零)的空白符,后紧跟零或一个字符 ’a’,再接字符 ‘b’ 组成的字符串,在下面匹配中将匹配此字符串所在的行。
[[ $line =~ [[:space:]]*(a)?b ]]
这意味着 line 的值类似于 ‘aab’, ‘aaaaaab’, ‘xaby’, 和 ‘ab’ 将全部匹配,及包含 ’ b’ 其值的任何一行。
这一段话有点晦涩难懂,下面是一个演示:
从egrep匹配和条件表达式的结果来看,[[ $line =~ [[:space:]]*(a)?b ]]的结果是匹配所有包含字符 ‘b’ 的字符串。
如果要匹配正则表达式语法的元字符 (’ ^ $ | \ . * + ? '),必须引用它以消除其特殊含义。这意味着在模式 ‘xxx.txt’, 这个点 ‘.’ 匹配字符串中的任何一个字符(其通常的正则表达式含义),但在模式 ‘“xxx.txt”’(双引号引用),它只能匹配文字含义的点 '.’ ,不再具有特殊含义。
同样,如果您想在模式中包含对正则表达式语法具有特殊含义的字符,则必须确保它不被引用。例如,如果您想在模式中锚定字符串的开头或结尾,则不能对锚定符 ‘^’ 或者 ‘$’ 使用任何形式的 shell 引用。
例如:如果你想匹配’initial string’ 在一行的开头:
正确示例:
[[ $line =~ ^“initial string” ]]
错误示例:
[[ $line =~ “^initial string” ]]
这是因为在第二个例子中引用了’‘,使其失去了通常的特殊含义,仅一个字面含义的’'。
有时很难在不使用引号的情况下正确指定正则表达式,或者在关注 shell 引用和 shell 的引号删除的同时跟踪正则表达式使用的引用。将正则表达式存储在 shell 变量中通常是避免引用 shell 特有字符时出现问题的有用方法。
例如,以下内容与上面使用的模式等效:
pattern=‘[[:space:]]*(a)?b’
[[ $line =~ $pattern ]]
shell 程序员应特别注意反斜杠,因为 shell 和正则表达式都使用反斜杠来转义后面字符的特殊含义。这意味着,在 shell 的单词扩展完成后(请参阅 Shell Expansions ),模式中最初未引用的部分中保留的任何反斜杠都可以转义模式字符的特殊含义。如果模式的任何部分被引用,则 shell 会尽力确保正则表达式将那些剩余的反斜杠视为文字(如果它们出现在引用部分中)。
以下两组命令并不等效:
pattern=‘.’
[[ . =~ $pattern ]]
[[ . =~ . ]]
[[ . =~ “$pattern” ]]
[[ . =~ ‘.’ ]]
前两个匹配会成功,但后两个不会成功,因为在后两个中,反斜杠将成为要匹配的模式的一部分。在前两个示例中,传递给正则表达式解析器的模式是 ‘.’。反斜杠删除了 ‘.’ 的特殊含义,所以是字面意思点 ‘.’ 。在后两个示例中,传递给正则表达式解析器的模式包含反斜杠(例如,‘\.’),它将与字符串不匹配,因为它不包含反斜杠。如果第一个示例中的字符串不是点 ‘.’, 而是字符 ‘a’,模式不匹配,因为引用了点 '. ’ 失去了匹配任何单个字符的特殊含义。
正则表达式中的括号表达式也可能是错误的来源,因为通常在正则表达式中特殊的字符会在括号之间失去其特殊含义。但是,您可以使用正则表达式中字符组 ’[ ]’ (方括号表达式)来匹配特殊模式字符而不用引用它们,这种方法也是避免错误的有效方法。
尽管这看起来是一种奇怪的书写方式,但以下模式将匹配 ‘.’ 在字符串中:
[[ . =~ [.] ]]
shell 在将模式传递给正则表达式函数之前执行任何单词扩展,因此您可以假设 shell 的引用优先。如上所述,正则表达式解析器将根据其自己的规则解释 shell 扩展后模式中剩余的任何未加引号的反斜杠。目的是避免让 shell 程序员不得不引用事物两次,因此,如果不是必须使用shell 引用来转义特殊模式字符的情形,就尽量避免使用shell引用。
数组变量 BASH_REMATCH 记录字符串的哪些部分与模式匹配。 BASH_REMATCH 中索引为 0 的元素包含与整个正则表达式匹配的字符串部分。正则表达式中带括号的子表达式匹配的子字符串保存在其余的 BASH_REMATCH 索引中。 BASH_REMATCH 中索引为 n 的元素是与第 n 带括号的子表达式匹配的字符串部分。
Bash 在全局范围内设置 BASH_REMATCH ;将其声明为局部变量将出现非预期的结果。
可以使用以下运算符组合表达式,按优先级降序列出:
( expression ) 括号运算符
返回 expression 的值。这可用于覆盖运算符的正常优先级。
! expression 取反运算符
如果 expression 为假,则为 True 。
expression1 && expression2 逻辑与(AND)
如果 expression1 和 expression2 都为真,则为 True 。
expression1 || expression2 逻辑或 (OR)
如果 expression1 或 expression2 为真,则为 True 。
如果 expression1 的值足以确定整个条件表达式的返回值,则 && 和 || 运算符不会计算 expression2 。