(十一)命令执行顺序控制与管道

一、实验介绍

1.1 实验内容

顺序执行、选择执行、管道、cut 命令、grep 命令、wc 命令、sort 命令、uniq命令、head命令等,高效率使用 Linux 的技巧。

1.2 实验知识点

cut,grep,wc,sort命令的使用,管道的理解

二、命令执行顺序的控制

2.1 顺序执行多条命令

通常情况下,我们每次只能在终端输入一条命令,按下回车执行,执行完成后,我们再输入第二条命令,然后再按回车执行…… 你可能会遇到如下使用场景:我需要使用apt-get
安装一个软件,然后安装完成后立即运行安装的软件(或命令工具),又恰巧你的主机才更换的软件源还没有更新软件列表(比如之前我们的环境中,每次重新开始实验就得sudo apt-get update,现在已经没有这个问题了),那么你可能会有如下一系列操作:

$ sudo apt-get update   
# 等待——————————然后输入下面的命令
$ sudo apt-get install some-tool   
# 等待——————————然后输入下面的命令
$ some-tool

这时你可能就会想要是我可以一次性输入完,让它自己去依次执行各命令就好了,这就是我们这一小节要解决的问题。简单的顺序执行你可以使用;来完成,比如上述操作你可以

$ sudo apt-get update;sudo apt-get install some-tool;some-tool
# 让它自己运行

2.2 有选择的执行命令

关于上面的操作,不知你有没有思考过一个问题,如果我们在让它自动顺序执行命令时,前面的命令执行不成功,而后面的命令又依赖与上一条命令的结果,那么就会造成花了时间,最终却得到一个错误的结果,而且有时候直观的看你还无法判断结果是否正确。那么我们需要能够有选择性的来执行命令,比如上一条命令执行成功才继续下一条,或者不成功又该做出其它什么处理,比如我们使用which来查找是否安装某个命令,如果找到就执行该命令,否则什么也不做(虽然这个操作没有什么实际意义,但可帮你更好的理解一些概念):

$ which cowsay>/dev/null && cowsay -f head-in ohch~

你如果没有安装cowsay,你可以先执行一次上述命令,你会发现什么也没发生,你再安装好之后你再执行一次上述命令,你也会发现一些惊喜。上面的&&就是用来实现选择性执行的,它表示如果前面的命令执行结果(不是表示终端输出的内容,而是表示命令执行状态的结果)返回0则执行后面的,否则不执行,你可以从$?环境变量获取上一次命令的返回结果:

(十一)命令执行顺序控制与管道_第1张图片
image.png

学习过 C 语言的用户应该知道在 C 语言里面&&表示逻辑与,而且还有一个||表示逻辑或,同样 Shell 也有一个||,它们的区别就在于,shell中的这两个符号除了也可用于表示逻辑与和或之外,就是可以实现这里的命令执行顺序的简单控制。

$ which cowsay>/dev/null || echo "cowsay has not been install, please run 'sudo apt-get install cowsay' to install"

除了上述基本的使用之外,我们还可以结合这&&和||来实现一些操作,比如:

$ which cowsay>/dev/null && echo "exist" || echo "not exist"
(十一)命令执行顺序控制与管道_第2张图片

我画个流程图来解释一下上面的流程:


(十一)命令执行顺序控制与管道_第3张图片

思考

上面我们讲到将&&和||结合起来使用,那么是否以任意顺序都行?比如上面我们是&&在前||在后,反过来可以么?会不会有问题? 。。。。肯定不行啊

如果第一个命令执行成功,与操作符 (&&)才会执行第二个命令
如果第一个命令执行失败,或操作符 (||)才会执行第二个命令
短路现象

可以分析一下下面的命令的输出:
echo "1" && echo "2" || echo "3" && echo "4" || echo "5" || echo "6" && echo "7" && echo "8" || echo "9" 

下面是输出:
1
2
4
7
8

分析:
echo "1"  执行成功,所以执行echo "2"
echo "2" 执行成功,不执行 echo "3"
既然没有执行echo "3",可以看做不存在 “ " || echo "3" ”,echo "2"后紧接“ && echo "4" ”
echo "4" 执行成功,不执行echo "5"
既然没有执行echo "5",可以看做不存在 “ " || echo "5" ”,echo "4"后紧接“ || echo "6" ”
echo "4" 执行成功,不执行echo "6"
既然没有执行echo "6",可以看做不存在 “ " || echo "6" ”,echo "4"后紧接“ && echo "7" ”
echo "7" 执行成功,执行echo "8"
echo "8" 执行成功,不执行echo "9"
所以最终变成了:

echo "1" && echo "2" && echo "4" && echo "7" && echo "8"
shell中&&和||的使用方法&&运算符:
command1  && command2
&&左边的命令(命令1)返回真(即返回0,成功被执行)后,&&右边的命令(命令2)才能够被执行;换句话说,“如果这个命令执行成功&&那么执行这个命令”。 
语法格式如下:
command1 && command2 [&& command3 ...]
1 命令之间使用 && 连接,实现逻辑与的功能。
2 只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。
3 只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。

示例 1
malihou@ubuntu:~$ cp ~/Desktop/1.txt ~/1.txt && rm ~/Desktop/1.txt && echo "success"
示例 1 中的命令首先从 ~/Desktop 目录复制 1.txt 文件到 ~ 目录;执行成功后,使用 rm 删除源文件;如果删除成功则输出提示信息。

||运算符:
command1 || command2
||则与&&相反。如果||左边的命令(命令1)未执行成功,那么就执行||右边的命令(命令2);或者换句话说,“如果这个命令执行失败了||那么就执行这个命令。
1 命令之间使用 || 连接,实现逻辑或的功能。
2 只有在 || 左边的命令返回假(命令返回值 $? == 1),|| 右边的命令才会被执行。这和 c 语言中的逻辑或语法功能相同,即实现短路逻辑或操作。
3 只要有一个命令返回真(命令返回值 $? == 0),后面的命令就不会被执行。

示例 2
malihou@ubuntu:~$ rm ~/Desktop/1.txt || echo "fail"
在示例 2 中,如果 ~/Desktop 目录下不存在文件 1.txt,将输出提示信息。 

示例 3
malihou@ubuntu:~$ rm ~/Desktop/1.txt && echo "success" || echo "fail"
在示例 3 中,如果 ~/Desktop 目录下存在文件 1.txt,将输出 success 提示信息;否则输出 fail 提示信息。
如果希望把几个命令合在一起执行, s h e l l提供了两种方法。既可以在当前s h e l l也可以在子s h e l l中执行一组命令。

()运算符:
为了在当前shell中执行一组命令,可以用命令分隔符(即",")隔开每一个命令,并把所有的命令用圆括号()括起来。 
它的一般形式为:  ( 命令;命令;命令… )
1 一条命令需要独占一个物理行,如果需要将多条命令放在同一行,命令之间使用命令分隔符(;)分隔。执行的效果等同于多个独立的命令单独执行的效果。
2 () 表示在当前 shell 中将多个命令作为一个整体执行。需要注意的是,使用 () 括起来的命令在执行前面都不会切换当前工作目录,也就是说命令组合都是在当前工作目录下被执行的,尽管命令中有切换目录的命令。
3 命令组合常和命令执行控制结合起来使用。
 
示例 4
malihou@ubuntu:~$ rm ~/Desktop/1.txt || (cd ~/Desktop/;ls -a;echo "fail")
 
在示例 4 中,如果目录 ~/Desktop 下不存在文件 1.txt,则执行命令组合。

三、管道

管道是什么?管道是一种通信机制,通常用于进程间的通信(也可通过socket进行网络通信),它表现出来的形式就是将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)

管道又分为匿名管道和具名管道。我们在使用一些过滤程序时经常会用到的就是匿名管道,在命令行中由|分隔符表示,|在前面的内容中我们已经多次使用到了。具名管道简单的说就是有名字的管道,通常只会在源程序中用到具名管道。下面我们就将通过一些常用的可以使用管道的"过滤程序"来帮助你熟练管道的使用。

3.1 试用管道

查看/etc目录下有哪些文件和目录,使用ls命令来查看:

$ ls -al /etc

有太多内容,屏幕不能完全显示,这时候可以使用滚动条或快捷键滚动窗口来查看。不过这时候可以使用管道:

$ ls -al /etc | less 
# less命令是对文件或其它输出进行分页显示的工具

通过管道将前一个命令(ls)的输出作为下一个命令(less)的输入,然后就可以一行一行地看。

3.2 cut 选取命令,就是将一段数据经过分析,取出我们想要的。一般来说,选取信息通常是针对“行”来进行分析的,并不是整篇信息分析的。

cut是以每一行为一个处理对象的,这种机制和sed是一样的。
cut一般以什么为依据呢? 也就是说,我怎么告诉cut我想定位到的剪切内容呢?
cut命令主要是接受三个定位方法:
第一,字节(bytes),用选项-b
第二,字符(characters),用选项-c
第三,域(fields),用选项-f
打印/etc/passwd文件中以:为分隔符的第1个字段和第6个字段分别表示用户名和其家目录:

$ cut /etc/passwd -d ':' -f 1,6
(十一)命令执行顺序控制与管道_第4张图片
(十一)命令执行顺序控制与管道_第5张图片
正常情况下读取样式

打印/etc/passwd文件中每一行的前N个字符:

# 前五个(包含第五个)
$ cut /etc/passwd -c -5
# 前五个之后的(包含第五个)
$ cut /etc/passwd -c 5-
# 第五个
$ cut /etc/passwd -c 5
# 2到5之间的(包含第五个)
$ cut /etc/passwd -c 2-5

3.3 grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

grep命令是很强大的,也是相当常用的一个命令,它结合正则表达式可以实现很复杂却很高效的匹配和查找,不过在学习正则表达式之前,这里介绍它简单的使用,而关于正则表达式后面将会有单独一小节介绍到时会再继续学习grep命令和其他一些命令。

grep命令的一般形式为:grep [命令选项]... 用于匹配的表达式 [文件]...

-a 不要忽略二进制数据。 
                                 -A<显示列数> 除了显示符合范本样式的那一行之外,并显示该行之后的内容。 
-b 在显示符合范本样式的那一行之外,并显示该行之前的内容。 
-c 计算符合范本样式的列数。 
-C<显示列数>或-<显示列数> 除了显示符合范本样式的那一列之外,并显示该列之前后的内容。 
-d<进行动作> 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。 -e<范本样式> 指定字符串作为查找文件内容的范本样式。 
-E 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式。 
-f<范本文件> 指定范本文件,其内容有一个或多个范本样式,让grep查找符合范本条件的文件内容,格式为每一列的范本样式。 
-F 将范本样式视为固定字符串的列表。 
-G 将范本样式视为普通的表示法来使用。 
-h 在显示符合范本样式的那一列之前,不标示该列所属的文件名称。 
-H 在显示符合范本样式的那一列之前,标示该列的文件名称。 
-i 胡列字符大小写的差别。 -l 列出文件内容符合指定的范本样式的文件名称。 
-L 列出文件内容不符合指定的范本样式的文件名称。
                                     -n 在显示符合范本样式的那一列之前,标示出该列的编号。 
                                     -q 不显示任何信息。 
-R/-r 此参数的效果和指定“-d recurse”参数相同。  (我个人认为这个就属于递归搜索子目录中的文件)
                        grep递归搜索文件 在多级目录中对文本进行递归搜索: 
                        grep "text" . -r -n 
                        # .表示当前目录。
-s 不显示错误信息。 
-v 反转查找。 
-w 只显示全字符合的列。 
-x 只显示全列符合的列。 
-y 此参数效果跟“-i”相同。 
                                   -o 只输出文件中匹配到的部分。
$ export | grep "declare"
(十一)命令执行顺序控制与管道_第6张图片

3.4 wc 命令,简单小巧的计数工具

wc 命令用于统计并输出一个文件中行、单词和字节的数目,比如输出/etc/passwd文件的统计信息:

$ wc /etc/passwd

分别只输出行数、单词数、字节数、字符数和输入文本中最长一行的字节数:

# 行数       $ wc -l /etc/passwd
# 单词数     $ wc -w /etc/passwd
# 字节数     $ wc -c /etc/passwd
# 字符数     $ wc -m /etc/passwd
# 最长行字节数 $ wc -L /etc/passwd

注意:对于西文字符来说,一个字符就是一个字节,但对于中文字符一个汉字是大于2个字节的,具体数目是由字符编码决定的

(十一)命令执行顺序控制与管道_第7张图片

再来结合管道来操作一下,下面统计 /etc 下面所有目录数:

$ ls -dl /etc/*/ | wc -l
ls命令目录基本操作 
ls命令用来显示目标列表,在Linux中是使用率较高的命令。
ls命令的输出信息可以进行彩色加亮显示,以分区不同类型的文件。
 语法 ls(选项)(参数) 选项 
-a:显示所有档案及目录(ls内定将档案名或目录名称为“.”的视为隐藏,不会列出); 
                                -A:显示除影藏文件“.”和“..”以外的所有文件列表; 
-C:多列显示输出结果。这是默认选项;
-l:与“-C”选项功能相反,所有输出信息用单列格式输出,不输出为多列; 
-c:与“-lt”选项连用时,按照文件状态时间排序输出目录内容,排序的依据是文件的索引节点中的ctime字段。与“-l”选项连用时,则排序的一句是文件的状态改变时间; 
                                  -d:仅显示目录名,而不显示目录下的内容列表。显示符号链接文件本身,而不显示其所指向的目录列表; 
-f:此参数的效果和同时指定“aU”参数相同,并关闭“lst”参数的效果; 
-i:显示文件索引节点号(inode)。一个索引节点代表一个文件;
-k:以KB(千字节)为单位显示文件大小; 
                                  -l:以长格式显示目录下的内容列表。输出的信息从左到右依次包括文件名,文件类型、权限模式、硬连接数、所有者、组、文件大小和文件的最后修改时间等; 
-m:用“,”号区隔每个文件和目录的名称; 
-n:以用户识别码和群组识别码替代其名称;
 -r:以文件名反序排列并输出目录内容列表; 
-s:显示文件和目录的大小,以区块为单位; 
-t:用文件和目录的更改时间排序;
-R:递归处理,将指定目录下的所有文件及子目录一并处理;

3.5 sort 排序命令

这个命令前面我们也是用过多次,功能很简单就是将输入按照一定方式排序,然后再输出,它支持的排序有按字典排序,数字排序,按月份排序,随机排序,反转排序,指定特定字段进行排序等等。

sort命令文件过滤分割与合并 
sort命令是在Linux里非常有用,它将文件进行排序,并将排序结果标准输出。
sort命令既可以从特定的文件,也可以从stdin中获取输入。
语法 sort(选项)(参数) 选项
-b:忽略每行前面开始出的空格字符;
-c:检查文件是否已经按照顺序排序; 
-d:排序时,处理英文字母、数字及空格字符外,忽略其他的字符; 
-f:排序时,将小写字母视为大写字母;
-i:排序时,除了040至176之间的ASCII字符外,忽略其他的字符;
-m:将几个排序号的文件进行合并;
-M:将前面3个字母依照月份的缩写进行排序; 
                                                                      -n:依照数值的大小排序;
-o<输出文件>:将排序后的结果存入制定的文件; 
                                                                       -r:以相反的顺序来排序;
-t<分隔字符>:指定排序时所用的栏位分隔字符;
+<起始栏位>-<结束栏位>:以指定的栏位来排序,范围由起始栏位到结束栏位的前一栏位。 
参数 文件:指定待排序的文件列表。 
                                           # -n是按照数字大小排序,-r是以相反顺序,-k是指定需要排序的栏位,-t指定栏位分隔符为冒号
默认为字典排序:
$ cat /etc/passswd | sort
反转排序:
$ cat /etc/passwd | sort -r
按特定字段排序:
$ cat /etc/passwd | sort -t':' -k 3

上面的-t参数用于指定字段的分隔符,这里是以":"作为分隔符;-k 字段号用于指定对哪一个字段进行排序。这里/etc/passwd文件的第三个字段为数字,默认情况下是以字典序排序的,如果要按照数字排序就要加上-n参数:

$ cat /etc/passwd | sort -t':' -k 3 -n
(十一)命令执行顺序控制与管道_第8张图片

3.6 uniq 去重命令

uniq命令可以用于过滤或者输出重复行。

uniq命令用于报告或忽略文件中的重复行,一般与sort命令结合使用。
 语法 uniq(选项)(参数) 选项 
                        -c或——count:在每列旁边显示该行重复出现的次数; 
                        -d或--repeated:仅显示重复出现的行列; 
-f<栏位>或--skip-fields=<栏位>:忽略比较指定的栏位; 
-s<字符位置>或--skip-chars=<字符位置>:忽略比较指定的字符; 
-u或——unique:仅显示出一次的行列; 
-w<字符位置>或--check-chars=<字符位置>:指定要比较的字符。 
过滤重复行

我们可以使用history命令查看最近执行过的命令(实际为读取${SHELL}_history文件,如我们环境中的~/.zsh_history文件),不过你可能只想查看使用了那个命令而不需要知道具体干了什么,那么你可能就会要想去掉命令后面的参数然后去掉重复的命令:

$ history | cut -c 8- | cut -d ' ' -f 1 | uniq

然后经过层层过滤,你会发现确是只输出了执行的命令那一列,不过去重效果好像不明显,仔细看你会发现它确实去重了,只是不那么明显,之所以不明显是因为uniq命令只能去连续重复的行,不是全文去重,所以要达到预期效果,我们先排序:

$ history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq
# 或者
$ history | cut -c 8- | cut -d ' ' -f 1 | sort -u

这就是 Linux/UNIX 哲学吸引人的地方,大繁至简,一个命令只干一件事却能干到最好。
输出重复行

输出重复过的行(重复的只输出一个)及重复次数
$ history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq -dc
# 输出所有重复的行
$ history | cut -c 8- | cut -d ' ' -f 1 | sort | uniq -D

3.7 head显示文件的开头的内容。在默认情况下,head命令显示文件的头10行内容。

用法:head [选项]... [文件]...  
将每个指定文件的头10 行显示到标准输出。  
如果指定了多于一个文件,在每一段输出前会给出文件名作为文件头。  
如果不指定文件,或者文件为"-",则从标准输入读取数据。  
  
长选项必须使用的参数对于短选项时也是必需使用的。  
  -c,  --bytes=[-]K     显示每个文件的前K 字节内容;  
                        如果附加"-"参数,则除了每个文件的最后K字节数据外  
                        显示剩余全部内容  
  -n, --lines=[-]K      显示每个文件的前K 行内容;  
                        如果附加"-"参数,则除了每个文件的最后K 行外显示  
                        剩余全部内容  
  -q, --quiet, --silent 不显示包含给定文件名的文件头  
  -v, --verbose         总是显示包含给定文件名的文件头  
      --help            显示此帮助信息并退出  
      --version         显示版本信息并退出  
  
K 后面可以跟乘号:  
b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024,  
GB 1000*1000*1000, G 1024*1024*1024, 对于T, P, E, Z, Y 同样适用。 

文本处理命令还有很多,下一节将继续介绍一些常用的文本处理的命令。

四、作业

熟悉 cut,sort,uniq命令以及参数

你可能感兴趣的:((十一)命令执行顺序控制与管道)