3.2 Shell命令 ============= 一个简单命令如echo a b c由命令自身和后面的变元组成, 并以空格分隔. 复杂命令是由简单命令用以下方式组合而成: 管道线(使前面命令的输出变成后面 命令的输入), 循环或条件结构, 或者其他组合形式. 3.2.1 简单命令 -------------- 简单命令是最常见的命令. 一个简单命令就是一串以控制操作符结尾并用空白符 (*参见 2 定义::)分隔的单词. 通常第一个单词指定了要执行的命令, 剩余的单词 都是该命令的变元. 一个简单命令的返回状态就是POSIX 1003.1 waitpid函数提供的退出状态. 如果 命令被信号n终结, 则其返回状态是128+n. 3.2.2 管道线 ------------ 一个管道线就是由'|'分隔的一串简单命令. 管道线的形式是: [time [-p]] [!] command1 [| command2 ... ] 管道线中每个命令的输出通过管道连接到下一个命令的输入, 就是说, 每个命令读取 了前一个命令的输出. 使用保留字time会在管道线执行结束时打印出其计时数据. 目前计时数据包含该 管道线执行所消耗的总逝去时间, 用户态时间和系统态时间. 选项'-p'把时间输出 格式调整为POSIX所指定的格式. 可以设置TIMEFORMAT变量来指定如何显示时间信息. 关于有哪些可用的格式, *参见 5.2 Bash变量. 将time作为保留字使用使得对shell 内部命令, shell函数, 及管道线的时间测量成为可能. 这一点如果用外部time命令 则不容易做到. 如果管道线不是异步地执行(*参见 3.2.3 命令列表::), 则shell会等待管道线 中所有命令运行结束. 管道线中的每个命令都在各自的子shell中运行(*参见 3.7.3 命令执行环境). 如果pipefail选项被关闭(*参见 4.3 Set内部命令), 管道线的退出状态就是管道线 中最后一个结束命令的退出状态. 如果pipefail选项开启, 管道线的退出状态是最后 (最右)一个拥有非零退出状态的命令的退出状态, 或是0如果所有命令都成功退出. 若 管道线前面出现保留字'!', 则退出状态是上述所描述情况的逻辑反. Shell等到管道 线内所有命令结束才返回值. 3.2.3 命令列表 -------------- 列表是指一个或多个管道线组成的序列, 它们以';', '&', '&&' 或'||'分隔, 并可选地以';', '&', 或newline结束. 在这些列表操作符中, '&&'和'||'具有相同的优先级, ';'和'&'具有相同的优先 级, 且'&&'和'||'的优先级比';'和'&'要高. 在列表中, 也可用一个或多个newline组成的序列来分隔命令, 这点上和';'等价. 当一个命令以控制操作符'&'结尾时, shell将该命令放入一个子shell中异步地 执行. 这也被称为将命令放在后台执行. Shell不会等该命令结束, 而是立即以返回 状态0(真)返回. 在shell的任务控制功能没有启用(*参见 7 任务控制), 而且又没有 任何显式的重定向的时候, 此异步命令的输入将会从/dev/null重定向而得. 用';'分隔的命令顺序地执行; shell依次等待每个命令执行完毕. 最后的返回 状态由最后一个命令的退出状态决定. 控制操作符'&&'和'||'分别表示列表的'与'和'或'. 列表与的形式是: command1 && command2 当且仅当command1的退出状态为零时command2才被执行. 列表或的形式为: command1 || command2 当且仅当command1的退出状态非零时command2才被执行. 列表与和列表或的返回状态由列表中最后一个执行的命令的退出状态决定. 3.2.4 复合命令 -------------- 符号命令是shell编程特性的一个构造. 每个构造以一个保留字或控制操作符开始, 以一个对应的保留字或控制操作符结束. 在没有显式覆盖时, 任何针对复合命令的重 定向都对该复合命令内的所有命令起作用. Bash提供循环结构, 条件结构, 以及将命令组合起来作为一个基本单元的机制. 3.2.4.1 循环结构 ................ Bash支持以下循环结构. 注意在以下命令语法描述中, 任何';'出现的地方都可以用一个或多个newline替代. 'until' 'until'命令的语法为: until TEST-COMMANDS; do CONSEQUENT-COMMANDS; done 只要TEST-COMMANDS的退出状态非零, 就执行CONSEQUENT-COMMANDS. 最终返回状态是 CONSEQUENT-COMMANDS中最后一个执行的命令的退出状态; 如果CONSEQUENT-COMMANDS 未被执行, 则退出状态为零. 'while' 'while'命令的语法为: while TEST-COMMANDS; do CONSEQUENT-COMMANDS; done 只要TEST-COMMANDS的退出状态为零, 就执行CONSEQUENT-COMMANDS. 最终返回状态是 CONSEQUENT-COMMANDS中最后一个执行的命令的退出状态; 如果CONSEQUENT-COMMANDS 未被执行, 则退出状态为零. 'for' 'for'命令的语法为: for NAME [in WORDS ...]; do COMMANDS; done 首先扩展WORDS, 然后将NAME与WORDS扩展后的每个元素依次绑定, 且每绑定一次 就执行COMMANDS一次. 如果'in WORDS'部分没有出现, 'for'命令对应每个设置的 位置参数执行一次COMMANDS, 就像'in "$@"'(*参见 3.4.2 特殊参数::)出现在 'in WORDS'的地方一样. 最终的返回状态由最后一个执行的命令的退出状态指定. 如果WORDS展开之后为空, 则没有命令执行, 返回状态为0. 'for'命令的一种可选形式为: for (( EXPR1 ; EXPR2 ; EXPR3 )) ; do COMMANDS ; done 首先, 算术表达式EXPR1根据后面将要叙述到的规则(*参见 6.5 Shell算术运算::) 被求值. 然后算术表达式EXPR2被重复地求值直到其值为0. 每次EXPR2求值为非零 的时候, COMMANDS被执行且算术表达式EXPR3被求值. EXPR1, EXPR2, EXPR3中的 任何一个如果没出现则被算作1处理. 最终的返回状态是整个列表最后一个执行的 命令的退出状态, 如果所有表达式都不正确则返回假. 可以用内部命令'break'和'continue'(*参见 4.1 Bourne shell内部命令::)来 控制循环的执行. 3.2.4.2 条件结构 ................ 'if' 'if'命令的格式为: if TEST-COMMANDS; then CONSEQUENT-COMMANDS; [elif MORE-TEST-COMMANDS; then MORE-CONSEQUENT-COMMANDS;] [else ALTERNATIVE-CONSEQUENT-COMMANDS;] fi 首先TEST-COMMANDS列表被执行, 如果其返回状态为0则CONSEQUENT-COMMANDS被 执行并且命令结束. 如果TEST-COMMANDS返回非0状态, 则'elif'列表被分别执行, 如果其中某个'elif'的MORE-TEST-COMMANDS返回状态为0则其相应的 MORE-CONSEQUENT-COMMANDS被执行并且命令结束. 如果'else'句存在, 且最后的 'if'或'elif'的TEST-COMMANDS都返回非0, 则ALTERNATIVE-CONSEQUENT-COMMANDS 被执行. 最终的返回状态由最后一个执行的命令的退出状态决定, 如果所有的 条件都没通过则返回0. 'case' 'case'命令的语法是: case WORD in [ [(] PATTERN [| PATTERN]...) COMMAND-LIST ;;]... esac 'case'会有选择地执行第一个和WORD匹配的PATTERN之后的COMMAND-LIST. 如果 shell选项'nocasematch'(*参见 4.2 Bash内部命令之'shopt')启用, 匹配将不 考虑字符的大小写. '|'用于分隔多个模式, ')'用于结束模式列表. 一个模式 列表和其相应的COMMAND-LIST合称为一个'子句'. 每个子句必须以';;'结束. 在匹配进行之前, WORD要经过波浪号扩展, 参数扩展, 命令替换, 算术扩展, 和引用去除等操作; 每个PATTERN要经过波浪号扩展, 参数扩展, 命令替换, 和算术扩展等操作. 可以有任意多个'case'子句, 每个子句要以';;'结束. 第一个匹配的模式后面 的COMMAND-LIST将被执行. 这是一个使用'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." 如果所有PATTERN都不匹配则最终的返回状态为0, 否则最终的返回状态为所执行 的COMMAND-LIST的退出状态. 'select' 使用'select'结构可以很方便地生成菜单. 'select'结构与'for'结构的语法 基本相同: select NAME [in WORDS ...]; do COMMANDS; done 首先'in'后面的单词列表被扩展, 产生一个项目序列. 然后该项目序列被打印 至标准错误输出流, 每个项目前加入一个数字. 如果'in WORDS'没有出现, 则 用位置参数代替, 就好像用"$@"代替了'in WORDS'一样. 然后显示出'PS3'并且 等待从标准输入有一行输入. 如果输入是上面项目序列中的某个项目前面的 数字, 则NAME被设置成此项目. 如果输入是空行, 那么项目序列和提示符再次 被显示出来并等待输入. 如果输入'EOF', 则该'select'命令结束. 所有其他的 输入将导致NAME被设置为null. 每次输入后, 读入的行被保存在变量'REPLY'中. 每次输入后COMMANDS都被执行. 如果COMMANDS中含'break'命令, 则'select' 命令在此点结束. 这里有一个例子, 它让用户从当前目录中选择一个文件名, 然后显示出该文件 的名称及序号: select fname in *; do echo you picked $fname \($REPLY\) break; done '((...))' (( EXPRESSION )) 首先算术表达式EXPRESSION按照后面将要描述的规则被求值(*参见 6.5 Shell 算术::). 如果EXPRESSION的值为非0, 则返回状态为0; 否则返回状态为1. 该结构 等效于结构: let "EXPRESSION" 欲了解let内部命令的用法, *参见 4.2 Bash内部命令::. '``.``.``.``' ` EXPRESSION ` 条件表达式EXPRESSION的求值结果决定了最终的返回状态是0还是1. EXPRESSION 由下面将要描述的基本表达式组成(*参见 6.4 Bash条件表达式::). '[['和']]' 间不做单词分割和文件名扩展, 但是要做波浪号扩展, 参数和变量扩展, 算术 扩展, 命令替换, 进程替换, 以及引用去除. 条件操作符如'-f'不能加以引用, 以免被当作普通字符串. 当使用操作符'=='和'!='时, 操作符右边的字符串被当作一个模式, 并依据3.5.8.1 节介绍的'模式匹配'规则来进行匹配. 如果shell选项'nocasematch'被启用, 则 匹配不区分字符大小写. 如果字符串匹配('==')则返回0, 否则('!=')返回1. 模式的任何部分也可以通过加以引用, 以强制使其作为字符串进行匹配. 另外, 还有一个二元操作符'=~'. 它和'=='及'!='具有相同的优先级. '=~'右边 的字符串被作为增广正则表达式进行匹配(参见 regex3). 如果字符串匹配则 返回0, 否则返回1. 如果正则表达式语法错误, 则该条件表达式返回2. 如果 shell选项'nocasematch'被启用(*参见 4.2 Shell内部命令::), 则匹配不区分 字符的大小写. 正则表达式内的括号子表达式所匹配的字符串被保存在数组变量 'BASH_REMATCH'中. 'BASH_REMATCH'中序号为0的元素是跟整个正则表达式匹配 的字符串. 'BASH_REMATCH'中序号为N的元素是与第N个括号子表达式匹配的字符 串部分. 基本表达式可以用以下的操作符加以组合. 这些操作符优先级递减: '( EXPRESSION )' 返回EXPRESION的值. 可以用来改变操作符的优先级. '! EXPRESSION' 如果EXPRESSION为假则返回真. 'EXPRESSION1 && EXPRESSION2' 如果EXPRESSION1和EXPRESSION2都为真则返回真. 'EXPRESSION1 || EXPRESSION2' 只要EXPRESSION1或EXPRESSION2中有一个为真则返回真. 注意对于操作符'&&'和'||', 如果EXPRESSION1已经足够判断整个条件表达式的 返回值, 则EXPRESSION2不再被求值. 3.2.4.3 组合命令 ................ Bash提供两种方式将一串命令组合成一个单元执行. 当命令被组合后, 重定向 对整个命令列表起作用. 例如, 一串命令里的所有命令的输出都可以被重定向 到一个单一的流. '()' ( LIST ) 把一串命令放在一对小括号之间, 将生成一个子shell(*参见 3.7.3 命令执行 环境::), 并且让LIST中的每个命令在子shell中执行. 因为LIST被放在子shell 中运行, 所以其中的变量赋值在子shell结束后将失效. '{}' { LIST; } 把一串命令放在一对花括号之间, 将使LIST中的命令在当前shell环境中执行. 不会生成子shell. LIST后的分号(或newline)是必须的. 这两种构造除了在是否创建子shell上不同外, 由于历史原因还有一个细微的差别. 花括号是'保留字', 所以它们与LIST之间需用'空白符'加以间隔. 而小括号是 '操作符', 所以即使和LIST没有被空白间隔也能被shell识别为独立的token. 两个结构的退出状态都是LIST的退出状态.