我们已经看到如何编写简单的命令,也就是命令后面跟着参数。 现在我们来看看如何通过组合简单命令来构造更复杂的命令。
开始之前,先简单介绍一下输入/输出。
进程拥有“标准 I/O 通道”。
tr命令就是一个非常纯粹的例子。
它所做的只是从输入读取文本,进行字符转换,再把结果文本打印到输出。
我们会在后面的教学大纲中进一步了解如何操作“stdio”通道。
这是 shell 编程的“杀手级特性”之一。 管道让你能够对文本流进行复杂的变换。
要生成一个排好序的用户列表:
cat /etc/passwd | cut -d : -f 1 | sort
管道符号(|)把一个命令的输出连接到另一个命令的输入。
cut读取cat的输出,sort读取cut的输出。
默认情况下,管道中的每个命令都在单独的子 shell 中运行。 (子 shell 是一个子进程,是当前正在运行的 shell 的一份副本。)
运行管道是有性能开销的。
如果你发现自己写着写着就有了一长串相似命令组成的管道,不妨考虑把它们合并成一条命令。
例如,用到多个grep、cut、sed、awk和tr实例的管道,通常都可以为了效率合并成一条awk命令。
管道的退出状态就是管道中最后一条命令的退出状态。 也就是说,如果前面某条命令失败了,但最后一条命令成功了,整个管道的退出状态就是零。
有一个 shell 设置可以改变这一点;
如果启用了“pipefail”设置(用set -o pipefail),那么管道的退出状态就会是:
命令列表是由;(或换行)、&&或||分隔的一串管道。
A; B是一条命令列表,其中B在A完成后才执行。A && B,其中只有当A成功(以状态零退出)时,B才执行。A || B,其中只有当A失败(以非零状态退出)时,B才执行。命令列表的退出状态就是最后执行的那条命令的退出状态。
&&和||运算符可以串联起来,让下一条命令根据前面命令的状态有条件地执行。
例如
A && B && C && D || E
这些逻辑运算符要少用。 它们很快就会导致代码难以阅读,或者产生难以理解的逻辑。
例如,你可能会以为下面两段代码是一样的:
if A; then B; else C; fi
A && B || C
区别在于:C 什么时候才会执行?
if语句)中,只有 A 失败时 C 才会执行。下面举两个例子,说明命令列表如何简化代码。
假设你有一个包含三角形数据的数据文件, 每个三角形由三行分别表示它的三条边。
$ cat triangle.dat
3
4
5
9
12
14
你可以用一个while循环,把条件写成三个独立的read命令:
while read a && read b && read c; do
if is_pythagorean "$a" "$b" "$c"; then
echo "$a:$b:$c is pythagorean"
else
echo "$a:$b:$c is not pythagorean"
fi
done < triangle.dat
假设is_pythagorean是一条判断三条边是否满足勾股方程的命令,那么输出会是:
3:4:5 is pythagorean
9:12:14 is not pythagorean
很多编程语言都有一种断言机制,当某个条件不满足时会抛出异常
assert(user == "Administrator", "wrong user");
我们可以用 Bash 中的 OR 运算符来模拟这个功能:
die () { # a little convenience function
echo "$*" >&2
exit 1
}
[[ $user == "Administrator" ]] || die "wrong user"
[[ $password == "secret!" ]] || die "incorrect password"
命令列表一长就很容易变得难以阅读。 多使用换行会有很大帮助。
看下面这个例子:如果满足两个条件,就把一个单词加入数组。
[[ "$word" != "$topic" ]] && [[ "$key" == "$(sorted "$topic")" ]] && anagrams+=("$candidate")
Bash 允许你在管道符或逻辑运算符之后换行。
[[ "$word" != "$topic" ]] &&
[[ "$key" == "$(sorted "$topic")" ]] &&
anagrams+=("$candidate")
不过,运算符放在行尾时很容易被忽略。 使用_续行_就能把运算符放在开头,这样就能更清楚地看出命令列表还没结束:
[[ "$word" != "$topic" ]] \
&& [[ "$key" == "$(sorted "$topic")" ]] \
&& anagrams+=("$candidate")
_续行_就是“反斜杠”和“换行”这两个字符组成的序列(\ + \n)。
当 Bash 看到这个序列时,会直接把它从代码中删掉,从而用下一行_继续_当前这一行。
注意不要在反斜杠和换行之间留任何空格。
再看一个例子
printf "%s\n" "${numbers[@]}" | bc --mathlib | sort --general-numeric-sort
或者
printf "%s\n" "${numbers[@]}" \
| bc --mathlib \
| sort --general-numeric-sort