树莓派以及其他的UNIX系操作系统以流的形式处理命令的输出。也就是说,你可以使用管道运算符链式处理流文本。
当一个命令在命令行执行的时候,有3个与它相关的流,分别是标准输入流、标准输出流以及标准异常流。这些流的本质是文本,树莓派会把它们当作文件处理。在UNIX类系统中,一切都是文件。这也是为什么作为UNIX家族一分子的树莓派系统能够通过简单的命令构建出复杂且可靠的系统。
标准输入流与你输入的文本有关,而标准输出流、标准异常流一般是一起出现的。之所以要把后两者在设计的时候分开,是因为你可能需要把它们之中的一个重定向到特定的位置。比如说,把报错信息重定向到标准输出之外。接下来我们先学习如何重定向标准输出到其他的命令与文件。
我们在这里使用的是管道操作,用|符号表示。举例来说:
ls -l /usr/bin | wc -l
前一个语句原本应该输出到标准输出,加上管道以后,会重定向到wc命令。wc -l命令会统计ls输出的信息有多少行。所以上面的这条复合指令能够统计某一文件夹下文件与文件夹的数量。
$ ls Desktop Documents Downloads MagPi Music Pictures Public Templates Videos $ ls -l /usr/bin | wc -l 1229
grep是我们要学习的非常重要的指令,请看下面的示例:
$ grep if /usr/share/python_games/catanimation.py
这条指令会打印出对应Python代码里包括了字符if的每一行。也就是说,以Python为例,包含elif或者gift的行也会被包括进去。想要更复杂的匹配结果,你可以使用正则表达式。
pi@raspberrypi: ~/visualizatoin$ grep if visual.py if __name__ == '__main__':
接下来,让我们结合一下以前学过的dpkg与现在的grep:
$ dpkg -l | grep -i game
-i选项表明对大小写不敏感,也就是说Game与game是相同的。
再结合之前的more命令,dpkg -l | more会一次打印一页的内容;sort指令可以排序输出的结果:
$ ls ~ /usr/share/python_games | sort -f
-f表明忽视大小写。
还可以结合之前的uniq命令来排除重复的项:
$ ls ~ /usr/share/python_games | sort -f | uniq
在介绍完这些指令的基本功能之后,我们在这里对正则表达式做一个入门的讲解。如果你对这一部分不是特别感兴趣,可以暂时跳过,这将不会对你的后续学习有任何影响。
为什么要使用正则表达式呢?正则表达式被我们用来匹配一定的文本模式。更具体地说,它可以被用来处理一类特定的文件。举个比较简单的例子,你想要处理文件夹下所有的txt文件,那么你可以使用*.txt这样的表达式来匹配所有的文本文件,使得同时处理大量的文件变成非常简单的一件事情。
接下来介绍一下基本的语法。鉴于篇幅的限制,不可能做原理上的详细讲解,但会尝试以一种简单易懂的方式介绍清楚基本的语法。
正则表达式中的匹配主要依赖于特殊字符,在Linux命令行下的这种正则表达式语法下,.*[]^${}\+?|()被定义为特殊字符。碰到了一个问题,如果想要匹配的文本里本身就包含这些特殊字符怎么办呢?
答案很简单,你只需要把这些特殊字符进行转义,而转义的工具本身也是特殊字符\。接下来在讲解示例的过程中会用到一个命令awk。awk是Linux系统上非常流行的行处理器,它的特点是一行一行地处理数据,然后进行处理化的输出。
请看一个简单的示例:
$ awk '{print $0}' /etc/passwd
这条指令的意思是原封不动地打印/etc/passwd的内容。$0代表整行内容。再看下面的示例:
$ awk '{print " "}' /etc/passwd
这条指令的意思是打印与/etc/passwd具有相同行数的空行。从这两条语句的输出行为可以看出awk按行处理文件的特性。在一行一行获取到文本内容后,awk提供了很多处理的相关功能,在这里我们就不进行介绍了,因为awk并不是这里讲解的重点。
接下来回到正则表达式的讲解部分。下面的指令展示了该如何匹配特殊字符:
$ echo "\ is a special character" | awk '/\\/{print $0}'
在这里我们用\\转义\符号。这里的//表示awk里的匹配代码块,后面这句awk的意思可以理解为输出匹配成功的所有整行。
因为//也有对应的特殊含义,所以也需要转义,示例如下:
$ echo "3 / 2" | awk '/\//{print $0}'
接下来我们讲解正则表达式里的其他一些问题。你可能会在实际使用中碰到这样的情况,一个单词在句子中多次出现,但你只想匹配那种单词在开头出现的模式,那么你需要用到^符号,它匹配的不是文本,而是位置(这在一开始可能难以理解,你需要想清楚这里位置的含义)。举例来说:
$ echo "welcome to likegeeks website" | awk '/^likegeeks/{print $0}'
没有对应的输出,而
$ echo "likegeeks website" | awk '/^likegeeks/{print $0}'
输出likegeeks website。
接下来是匹配的重头戏“.”符号。“.”符号可以匹配除了空字符串外的所有字符,比如:
$ cat myfile this is a test This is another test And this is one more start with this
$ awk '/.st/{print $0}' myfile
输出:
this is a test This is another test
如果你不想匹配所有的字符,而仅仅是几个特定的字符,可使用[]符号,例如:
$ awk '/[oi]th/{print $0}' myfile
输出如下:
this is a test This is another test
我们甚至可以反向选择,排除对应的字符,示例如下:
$ awk '/[^oi]th/{print $0}' myfile
这样会排除有o和i出现的情况。
使用-字符可以表示范围,示例如下:
$ awk '/[e-p]st/{print $0}' myfile
最后看一看“*”符号。在正则表达式里,它有不太一样的含义,这一点在使用的时候千万不要混淆。“*”在bash里有着与刚才“.”类似的含义,但可以匹配多个字符;而在正则表达式里,它并不匹配字符,而是表示一个模式可以重复多次或零次。
$ echo "st" | awk '/s[ae]*t/{print $0}'
输出st。
$ echo "awwwwwk" | awk '/aw*k/{print $0}'
输出awwwwwk。
符号“>”可以重定向输出流的位置。比如说,可以输出到一个新建的文件,也可以覆盖一个已经存在的文件。请看如下示例:
$ ls /usr/bin > ~/mylisting4.txt
现在文件mylisting4.txt里已经包含了命令输出的内容,如下所示:
文件重定向可以把命令输出直接保存到一个文本文件里。在上论坛询问网友前,先把你的报错信息保存起来,坛友经常会要求看到错误信息再帮你分析。
注意,输出的布局在文件与命令行里是有所不同的。在命令行里,为了节省空间,一般会把很多项挤在一行里,但重定向到文件后,一行只有一项内容。大部分命令都是以行作为最小单位来处理文本的,比如说我们刚刚学过的grep。另外,注意有些命令在使用管道时需要用“-”作为占位符,比如cat:
$ echo "zzzz is not a real program here" | cat mylisting.txt -
有时我们想向文件末尾追加内容,就应该使用“>>”符号:
$ echo "& one more for luck!" >> ~/mylisting4.txt
echo命令把所有在引号里的内容直接打印到标准输出;-e选项可以让你添加一些特殊字符,比如换行符;tail命令允许你查看文件的最后几行,例如tail ~/mylisting4.txt;“<”可以重定向文件的输入,比如重定向到sort的输入:
$ echo -e "aardvark\nplatypus\njellyfish\naardvark" > list1 $ sort < list1
我们可以同时使用“>”和“<”:
$ head -n 2 < list1 > list2
这样就可以实现从list1读,再把命令的输出重定向到list2。还可以跟管道相结合:
$ sort < list1 | uniq > list3
最后,看一看如何分离标准异常流。可以用2>来重定向异常信息:
$ cat list1 list2 list3 $ list42 2>errors.txt
这段命令的意思是把没有报错的部分直接打印到命令行里,再把异常信息打印到errors.txt文件里。注意,使用2>>的话就不会覆盖原本的文件了。