重学操作系统----07 | 进程、重定向和管道指令:xargs 指令的作用是?

目录

 

1、进程

2、ps

3、top

4、管道(Pipeline)

5、 STDIN STDOUT STDERR

6、管道的作用和分类

6.1 排序

6.2 去重

6.3 筛选

6.4 数行数

6.5 中间结果

7、xargs

8、面试题目


1、进程


为了弄清楚这节课程的内容,也就是管道,我们先来讨论一下进程。

我们知道,应用的可执行文件是放在文件系统里,把可执行文件启动,就会在操作系统里(具体来说是内存中)形成一个应用的副本,这个副本就是进程。

插一个小知识,以后你再遇到面试题:什么是进程?

可以回答:进程是应用的执行副本;而不要回答进程是操作系统分配资源的最小单位。前者是定义,后者是作用*。*

2、ps

如果你要看当前的进程,可以用ps指令。p 代表 processes,也就是进程;s 代表 snapshot,也就是快照。所谓快照,就是像拍照一样。

 ~]$ ps
  PID TTY          TIME CMD
 5879 pts/1    00:00:00 ps
46307 pts/1    00:00:00 bash

如上图所示,我启动了两个进程,ps和bash。ps 就是我刚刚启动的,被ps自己捕捉到了;bash是因为我开了这个控制台,执行的shell是bash。

当然操作系统也不可能只有这么几个进程,这是因为不带任何参数的ps指令显示的是同一个电传打字机(TTY上)的进程。TTY 这个概念是一个历史的概念,过去用来传递信息,现在已经被传真、邮件、微信等取代。

操作系统上的 TTY 是一个输入输出终端的概念,比如用户打开 bash,操作系统就为用户分配了一个输入输出终端。没有加任何参数的ps只显示在同一个 TTY 的进程。

如果想看到所有的进程,可以用ps -e,-e没有特殊含义,只是为了和-A区分开。我们通常不直接用ps -e而是用ps -ef,这是因为-f可以带上更多的描述字段,如下图所示:

 ~]$ ps -ef|head
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0  2019 ?        00:19:48 /sbin/init
root         2     0  0  2019 ?        00:00:02 [kthreadd]
root         3     2  0  2019 ?        04:04:57 [migration/0]
root         4     2  0  2019 ?        00:37:06 [ksoftirqd/0]
root         5     2  0  2019 ?        00:00:00 [stopper/0]
root         6     2  0  2019 ?        00:01:18 [watchdog/0]
root         7     2  0  2019 ?        05:21:58 [migration/1]
root         8     2  0  2019 ?        00:00:00 [stopper/1]
root         9     2  0  2019 ?        00:09:48 [ksoftirqd/1]
  • UID 指进程的所有者;
  • PID 是进程的唯一标识;
  • PPID 是进程的父进程 ID;
  • C 是 CPU 的利用率(就是 CPU 占用);
  • STIME 是开始时间;
  • TTY 是进程所在的 TTY,如果没有 TTY 就是 ?号;
  • TIME;
  • CMD 是进程启动时的命令,如果不是一个 Shell 命令,而是用方括号括起来,那就是系统进程或者内核过程。

另外一个用得比较多的是ps aux,它和ps -ef能力差不多,但是是 BSD 风格的。就是加州伯克利分校研发的 Unix 分支版本的衍生风格,这种风格其实不太好描述,我截了一张图,你可以体会一下:

 ~]$ ps -aux|head
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  19364  1204 ?        Ss    2019  19:48 /sbin/init
root         2  0.0  0.0      0     0 ?        S     2019   0:02 [kthreadd]
root         3  0.0  0.0      0     0 ?        S     2019 244:57 [migration/0]
root         4  0.0  0.0      0     0 ?        S     2019  37:06 [ksoftirqd/0]
root         5  0.0  0.0      0     0 ?        S     2019   0:00 [stopper/0]
root         6  0.0  0.0      0     0 ?        S     2019   1:18 [watchdog/0]
root         7  0.0  0.0      0     0 ?        S     2019 321:58 [migration/1]
root         8  0.0  0.0      0     0 ?        S     2019   0:00 [stopper/1]
root         9  0.0  0.0      0     0 ?        S     2019   9:48 [ksoftirqd/1]

在 BSD 风格中有些字段的叫法和含义变了

3、top


另外还有一个和ps能力差不多,但是显示的不是快照而是实时更新数据的top指令。因为自带的top显示的内容有点少, 所以我喜欢用一个叫作htop的指令,具体的安装全方法我会在 10 | 软件的安装: 编译安装和包管理器安装有什么优势和劣势?中给你介绍。本课时,我们先看一下使用效果,如下图所示:

4、管道(Pipeline)


现在你已经掌握了一点点进程的基础,下面我们来学习管道,管道(Pipeline)的作用是在命令和命令之间,传递数据。比如说一个命令的结果,就可以作为另一个命令的输入。我们了解了进程,所以这里说的命令就是进程。更准确地说,管道在进程间传递数据

5、 STDIN STDOUT STDERR


每个进程拥有自己的标准输入流、标准输出流、标准错误流。

这几个标准流说起来很复杂,但其实都是文件

  • 标准输入流(用 0 表示)可以作为进程执行的上下文(进程执行可以从输入流中获取数据)。
  • 标准输出流(用 1 表示)中写入的结果会被打印到屏幕上。
  • 如果进程在执行过程中发生异常,那么异常信息会被记录到标准错误流(用 2 表示)中。

重定向

我们执行一个指令,比如ls -l,结果会写入标准输出流,进而被打印。这时可以用重定向符将结果重定向到一个文件,比如说ls -l > out,这样out文件就会有ls -l的结果;而屏幕上也不会再打印ls -l的结果。

具体来说

  • >符号叫作覆盖重定向;
  • >>叫作追加重定向。

>每次都会把目标文件覆盖,>>会在目标文件中追加。比如你每次启动一个程序日志都写入/var/log/somelogfile中,可以这样操作,如下所示:

start.sh >> /var/log/somelogfile


经过这样的操作后,每次执行程序日志就不会被覆盖了。

另外还有一种情况,比如我们输入:


ls1 > out
结果并不会存入out文件,因为ls1指令是不存在的。结果会输出到标准错误流中,仍然在屏幕上。这里我们可以把标准错误流也重定向到标准输出流,然后再重定向到文件。


ls1 &> out  这个写法等价于: ls1 > out 2>&1


相当于把ls1的标准输出流重定向到out,因为ls1 > out出错了,所以标准错误流被定向到了标准输出流。&代表一种引用关系,具体代表的是ls1 >out的标准输出流。

6、管道的作用和分类


有了进程和重定向的知识,接下来我们梳理下管道的作用。管道(Pipeline)将一个进程的输出流定向到另一个进程的输入流,就像水管一样,作用就是把这两个文件接起来。如果一个进程输出了一个字符 X,那么另一个进程就会获得 X 这个输入。

管道和重定向很像,但是管道是一个连接一个进行计算,重定向是将一个文件的内容定向到另一个文件,这二者经常会结合使用。

Linux 中的管道也是文件,有两种类型的管道:

  • 匿名管道(Unnamed Pipeline),这种管道也在文件系统中,但是它只是一个存储节点,不属于任何一个目录。说白了,就是没有路径。平常命令后面用| 连接的这种就是匿名管道. 
  • 命名管道(Named Pipeline),这种管道就是一个文件,有自己的路径。

FIFO
管道具有 FIFO(First In First Out),FIFO 和排队场景一样,先排到的先获得。所以先流入管道文件的数据,也会先流出去传递给管道下游的进程。

使用场景分析
接下来我们以多个场景举例帮助你深入学习管道。

6.1 排序


比如我们用ls,希望按照文件名排序倒序,可以使用匿名管道,将ls的结果传递给sort指令去排序。你看,这样ls的开发者就不用关心排序问题了。

6.2 去重


另一个比较常见的场景是去重,比如有一个字典文件,里面都是词语。如下所示:
Apple
Banana
Apple
Banana
……
如果我们想要去重可以使用uniq指令,uniq指令能够找到文件中相邻的重复行,然后去重。但是我们上面的文件重复行是交替的,所以不可以直接用uniq,因此可以先sort这个文件,然后利用管道将sort的结果重定向到uniq指令。指令如下:

6.3 筛选


有时候我们想根据正则模式筛选对应的内容。比如说我们想找到项目文件下所有文件名中含有Spring的文件。就可以利用grep指令,操作如下:


find ./ | grep Spring
find ./递归列出当前目录下所有目录中的文件。grep从find的输出流中找出含有Spring关键字的行。

如果我们希望包含Spring但不包含MyBatis就可以这样操作:


find ./ | grep Spring | grep -v MyBatis
grep -v是匹配不包含 MyBatis 的结果。

6.4 数行数


还有一个比较常见的场景是数行数。比如你写了一个 Java 文件想知道里面有多少行,就可以使用wc -l指令,如下所示:

但是如果你想知道当前目录下有多少个文件,可以用ls | wc -l,如下所示:

接下来请你思考一个问题:我们如何知道当前java的项目目录下有多少行代码?

提示一下。你可以使用下面这个指令:

$ find ./ -iname "*.java"|xargs wc -l

-iname和-name的区别?

 -iname pattern
              Like  -name,  but  the  match  is case insensitive.  For example, the patterns ‘fo*’ and ‘F??’ match the file names ‘Foo’, ‘FOO’, ‘foo’, ‘fOo’, etc.   In these patterns, unlike
              filename expansion by the shell, an initial ‘.’ can be matched by ‘*’.  That is, find -name *bar will match the file ‘.foobar’.   Please note that you should quote patterns  as
              a matter of course, otherwise the shell will expand any wildcard characters in them.


-name pattern
              Base of file name (the path with the leading directories removed) matches shell pattern pattern.  The metacharacters (‘*’, ‘?’, and ‘[]’) match a ‘.’ at the start of  the  base
              name  (this  is a change in findutils-4.2.2; see section STANDARDS CONFORMANCE below).  To ignore a directory and the files under it, use -prune; see an example in the descrip-
              tion of -path.  Braces are not recognised as being special, despite the fact that some shells including Bash imbue braces with a special meaning in shell patterns.   The  file-
              name  matching is performed with the use of the fnmatch(3) library function.   Don’t forget to enclose the pattern in quotes in order to protect it from expansion by the shell.


6.5 中间结果


管道一个接着一个,是一个计算逻辑。有时候我们想要把中间的结果保存下来,这就需要用到tee指令。tee指令从标准输入流中读取数据到标准输出流

tee还有一个能力,就是自己利用这个过程把输入流中读取到的数据存到文件中。比如下面这条指令:


find ./ -i "*.java" | tee JavaList | grep Spring


这句指令的意思是从当前目录中找到所有含有 Spring 关键字的 Java 文件。tee 本身不影响指令的执行,但是 tee 会把 find 指令的结果保存到 JavaList 文件中。

tee这个执行就像英文字母中的 T 一样,连通管道两端,下面又开了口。这个开口,在函数式编程里面叫作副作用

7、xargs


xargs指令从标准数据流中构造并执行一行行的指令。xargs从输入流获取字符串,然后利用空白、换行符等切割字符串,在这些字符串的基础上构造指令,最后一行行执行这些指令。

举个例子,如果我们重命名当前目录下的所有 .a 的文件,想在这些文件前面加一个前缀prefix_。比如说x.a文件需要重命名成prefix_x.a,我们就可以用xargs指令构造模块化的指令。

现在我们有x.a``y.a``z.a三个文件,如下图所示:

然后使用下图中的指令构造我们需要的指令:

我们用ls找到所有的文件;

-I参数是查找替换符,这里我们用GG替代ls找到的结果;-I GG后面的字符串 GG 会被替换为x.a``x.b或x.z;

echo是一个在命令行打印字符串的指令。使用echo主要是为了安全,帮助我们检查指令是否有错误。

我们用xargs构造了 3 条指令。这里我再多讲一个词,叫作样板代码。如果你没有用xargs指令,而是用一条条mv指令去敲,这样就构成了样板代码。

最后去掉 echo,就是我们想要的结果,如下所示:

管道文件
上面我们花了较长的一段时间讨论匿名管道,用|就可以创造和使用。匿名管道也是利用了文件系统的能力,是一种文件结构。当你学到模块六文件系统的内容,会知道匿名管道拥有一个自己的inode,但不属于任何一个文件夹。

还有一种管道叫作命名管道(Named Pipeline)。命名管道是要挂到文件夹中的,因此需要创建。用mkfifo指令可以创建一个命名管道,下面我们来创建一个叫作pipe1的命名管道,如下图所示:

命名管道和匿名管道能力类似,可以连接一个输出流到另一个输入流,也是 First In First Out。

当执行cat pipe1的时候,你可以观察到,当前的终端处于等待状态。因为我们cat pipe1的时候pipe1中没有内容。

如果这个时候我们再找一个终端去写一点东西到pipe中,比如说:

复制代码
echo "XXX" > pipe1
这个时候,cat pipe1就会返回,并打印出xxx,如下所示:

我们可以像上图那样演示这段程序,在cat pipe1后面增加了一个&符号。这个&符号代表指令在后台执行,不会阻塞用户继续输入。然后我们通过echo指令往pipe1中写入东西,接着就会看到xxx被打印出来。

8、面试题目

xargs 的作用?

【解析】 xargs 将标准输入流中的字符串分割成一条条子字符串,然后再按照我们自己想要的方式构建成一条条指令,大大拓展了 Linux 指令的能力。

比如我们可以用来按照某种特定的方式逐个处理一个目录下所有的文件;根据一个 IP 地址列表逐个 ping 这些 IP,收集到每个 IP 地址的延迟等。

思考题
请问下面这段 Shell 程序的作用是什么?


mkfifo pipe1
mkfifo pipe2
echo -n run | cat - pipe1 > pipe2 &
cat <pipe2 > pipe1

答:

1、创建了两个pipe. 

2、echo -n 参数代表

 -n     do not output the trailing newline
紧接着的管道

  • 当cat用 > 重定向输出到一个管道文件时,如果没有其他进程从管道文件中读取内容,cat会阻塞。
  • 当cat用 < 读取一个管道内容时,如果管道中没有输入,也会阻塞
  • 从这个角度来看,总共有 3 次重定向
  • 将 - 也就是输入流的内容和pipe1内容 合并 重定向到pipe2;

3、第四行解释

  • 将pipe2内容重定向到cat
  • 将cat的内容重定向到pipe1。

仔细观察下路径:pipe1->pipe2->pipe1,构成了一个环。 这样导致管道pipe1管道pipe2中总是有数据(没有数据的时间太短)。于是,就构成了一个无限循环。我们打开执行这个程序后,可以用htop查看当前的 CPU 使用情况,会发现 CPU 占用率很高。

参考: 

https://www.linuxjournal.com/article/2156 

猜你喜欢

转载自blog.csdn.net/MyySophia/article/details/113783119