龙之介大人

bash和shell的入门下
8.1 数据流重导向数据流重导向 (redirect) 由字面上的意思来看,好像就是将『数据给他传导到其他地方去』...
扫描右侧二维码阅读全文
22
2019/08

bash和shell的入门下

8.1 数据流重导向

数据流重导向 (redirect) 由字面上的意思来看,好像就是将『数据给他传导到其他地方去』的样子? 没错~数据流重导向就是将某个指令执行后应该要出现在屏幕上的数据, 给他传输到其他的地方, 例如文件或者是装置 (例如打印机之类的)!这玩意儿在 Linux 的文本模式底下可重要的! 尤其是如 果我们想要将某些数据储存下来时,就更有用了!

8.1.2 什么是数据流重导向

什么是数据流重导向啊?这得要由指令的执行结果谈起!一般来说,如果你要执行一个指令,通常他 会是这样的:

我们执行一个指令的时候,这个指令可能会由文件读入资料,经过处理之后,再将数据输出到屏幕上。 在上图当中, standard output 与 standard error output 分别代表『标准输出 (STDOUT)』与『标准错 误输出 (STDERR)』, 这两个玩意儿默认都是输出到屏幕上面来的啊!那么什么是标准输出与标准 错误输出呢?

  • standard output 与 standard error output

简单的说,标准输出指的是『指令执行所回传的正确的讯息』,而标准错误输出可理解为『 指令执 行失败后,所回传的错误讯息』。举个简单例子来说,我们的系统默认有 /etc/crontab 但却无 /etc/vbirdsay, 此时若下达『 cat /etc/crontab /etc/vbirdsay 』这个指令时,cat 会进行:

标准输出:读取 /etc/crontab 后,将该文件内容显示到屏幕上;
标准错误输出:因为无法找到 /etc/vbirdsay,因此在屏幕上显示错误讯息

不管正确或错误的数据都是默认输出到屏幕上,所以屏幕当然是乱乱的!那能不能透过某些机制将这两股数据分开呢? 当然可以啊!那就是数据流重导向的功能啊!数据流重导向可以将 standard output (简称 stdout) 与 standard error output (简称 stderr) 分别传送到其他的文件或装置去,而分别传送所 用的特殊字符则如下所示:

  1. 标准输入 (stdin):代码为0,使用<或<<;
  2. 标准输出 (stdout):代码为 1 ,使用 > 或 >> ;
  3. 标准错误输出(stderr):代码为 2 ,使用 2> 或 2>> ;
  • 为了理解 stdout 与 stderr ,我们先来进行一个范例的练习:
#范例一:观察你的系统根目录 (/) 下各目录的文件名、权限与属性,并记录下来
[xiaoqi@study ~]$ ll /  <--屏幕有信息
[xiaoqi@study ~]$ ll / > ~/rootfile  <--屏幕无信息
[xiaoqi@study ~]$ ll ~/rootfile  <--建立了一个新文件
-rw-rw-r--. 1 xiaoqi xiaoqi 1017 8月  20 20:12 /home/xiaoqi/rootfile

为什么屏幕会完全没有数据呢?这是因为原本『 ll / 』所显示的数据已经被重新导向到 ~/rootfile 文件中了! 那个 ~/rootfile 的文件名可以随便取。

如果你下达『 cat ~/rootfile 』那就可以看到原本 应该在屏幕上面的数据啰。 如果我再次下达:『 ll /home > ~/rootfile 』后,那个 ~/rootfile 文件的 内容变成什么?

他将变成『仅有 ll /home 的数据』而已!原本的『 ll / 』数据就不见了吗?是的!因为该文件的建立方式是:

  1. 该文件 (本例中是 ~/rootfile) 若不存在,系统会自动的将他建立起来,但是
  2. 当这个文件存在的时候,那么系统就会先将这个文件内容清空,然后再将数据写入!
  3. 也就是若以 > 输出到一个已存在的文件中,那个文件就会被覆盖掉啰!

那如果我想要将数据累加而不想要将旧的数据删除,那应该怎么办?

利用两个大于的符号 (>>) 就好了!以上面的范例来说,你应该要改成『 ll / >> ~/rootfile 』即可。 如此一来,当(1) ~/rootfile 不存在时系统会主动建立这个文件;(2)若该文件已存在,则数据会在该文件的最下方累加进去!

上面谈到的是 standard output 的正确数据,那如果是 standard error output 的错误数据呢?

那就透过 2> 及 2>> !同样是覆盖 (2>) 与累加 (2>>) 的特性!我们在刚刚才谈到 stdout 代码是 1 而 stderr 代码是 2 , 所以这个 2> 是很容易理解的,而如果仅存在 > 时,则代表预设的代码 1 ! 也就是说:

  • 1> :以覆盖的方法将『正确的数据』输出到指定的文件或装置上;
  • 1>>:以累加的方法将『正确的数据』输出到指定的文件或装置上;
  • 2> :以覆盖的方法将『错误的数据』输出到指定的文件或装置上;
  • 2>>:以累加的方法将『错误的数据』输出到指定的文件或装置上;

要注意的是,1>>以及2>>中间是没有空格的.

当你以一般身份执行 find 这个指令的时候,由于权限的问题可能会产生一些错误信息。例如执行find / -name testing时,可能会产生类似find: /root: Permission denied之类的讯息。 例如底下这个范例:

#范例二:利用一般身份账号搜寻 /root 底下是否有名为 .bashrc 的文件存在
[xiaoqi@study ~]$ find /root -name .bashrc  <--以普通用户的身份
find: ‘/root’: 权限不够    <--Standard error output

[xiaoqi@study ~]$ find /home -name .bashrc    <--以普通用户的身份
/home/xiaoqi/.bashrc    <--Standard output

由于 /root 底下有root账号建立的家目录存在,那么这些账号的家目录是无权限进入进入.所以就会有 错误及正确数据了。

那么假如我想要将数据输出到 list 这个文件中呢?执行find /home -name .bashrc > list会有什么结果?

你会发现 list 里面存了刚刚那个『正确』的输出数据,至于屏幕上还是会有错误的讯息出现.

如果想要将正确的与错误的数据分别存入不同的文 件中需要怎么做?

#范例三:承范例二,将 stdout 与 stderr 分存到不同的文件去
[xiaoqi@study ~]$ find /home/ -name .bashrc > list_right 2> list_error

此时 屏幕上不会出现任何讯息 !因为刚刚执行的结果中,有 Permission 的那几行错误 信息都会跑到 list_error 这个文件中,至于正确的输出数据则会存到 list_right 这个文件中.

8.1.3 /dev/null 垃圾桶黑洞装置与特殊写法

想象一下,如果我知道错误讯息会发生,所以要将错误讯息忽略掉而不显示或储存呢? 这个时候黑 洞装置 /dev/null 就很重要了!这个 /dev/null 可以吃掉任何导向这个装置的信息!

#范例四:承范例三,将错误的数据丢弃,屏幕上显示正确的数据
[xiaoqi@study ~]$ find /home/ -name .bashrc 2> /dev/null 
/home/xiaoqi/.bashrc    <--只有 stdout 会显示到屏幕上, stderr 被丢弃了

再想象一下,如果我要将正确与错误数据通通写入同一个文件去呢?这个时候就得要使用特殊的写法 了! 我们同样用底下的案例来说明:

#范例五:将指令的数据全部写入名为 list 的文件中
[xiaoqi@study ~]$ find /home/ -name .bashrc > list 2> list    <--错误
[xiaoqi@study ~]$ cat list
find: ‘/home/root’: 权限不够

[xiaoqi@study ~]$ find /home/ -name .bashrc > list 2>&1     <--正确
[xiaoqi@study ~]$ cat list
/home/xiaoqi/.bashrc
find: ‘/home/root’: 权限不够

[xiaoqi@study ~]$ find /home/ -name .bashrc &> list            <--正确
[xiaoqi@study ~]$ cat list
/home/xiaoqi/.bashrc
find: ‘/home/root’: 权限不够

上述示例第一行错误的原因是,由于两股数据同时写入一个文件,又没有使用特殊的语法, 此时两股数据可能会交叉写入该文件内,造成次序的错乱。

所以虽然最终 list 文件还是会产生,但是里面的数据排列就会怪怪的,而不是原本屏幕上的输出排序。至于写入同一个文件的特殊语法如上表所示,你可以使用 2>&1 也可以使用 &> ! 一般来说,比较推荐使用 2>&1 的语法!

8.1.4 standard input : < 与 <<

了解了 stderr 与 stdout 后,那么那个 < 又是什么呀?

以最简单的说法来说, 那就是『将原 本需要由键盘输入的数据,改由文件内容来取代』的意思。我们先由底下的 cat指令操作来了解一 下什么叫做键盘输入!

#范例六:利用 cat 指令来建立一个文件的简单流程
[xiaoqi@study ~]$ cat > catfile
testing
cat file test
                <--使用ctrl+d离开
[xiaoqi@study ~]$ cat catfile 
testing
cat file test

由于加入 > 在 cat 后,所以那个 catfile 会被主动的建立,而内容就是刚刚键盘上面输入的那两行 数据了。

那我能不能用纯文本文件取代键盘的输入,也就是说,用某个文件的内容来取代键盘 ? 可以的!如下所示:

#范例七:用 stdin 取代键盘的输入以建立新文件的简单流程
[xiaoqi@study ~]$ cat > catfile < ~/.bashrc 
[xiaoqi@study ~]$ ll catfile ~/.bashrc 
-rw-rw-r--. 1 xiaoqi xiaoqi 231 8月  21 14:15 catfile
-rw-r--r--. 1 xiaoqi xiaoqi 231 11月 20 2015 /home/xiaoqi/.bashrc
#注意看,这两个文件的大小会一模一样!几乎像是使用 cp 来复制一般!

这东西非常的有帮助!尤其是用在类似 mail 这种指令的使用上。理解 < 之后,再来则是怪可怕一 把的 <<这个连续两个小于的符号了。 他代表的是『结束的输入字符』的意思!

举例来讲:我要用 cat 直接将输入的讯息输出到 catfile 中, 且当由键盘输入 eof 时,该次输入就结束,那我可 以这样做:

[xiaoqi@study ~]$ cat > catfile  << "EOF"
> this is a test
> ok now stop
> EOF        <--输入这关键词,立刻就结束而不需要输入 [ctrl]+d
[xiaoqi@study ~]$ cat catfile 
this is a test
ok now stop            <--有这两行,不会存在关键词那一行!

利用 << 右侧的控制字符,我们可以终止一次输入, 而不必输入 [crtl]+d 来结束.

8.1.5那么为何要使用命令输出重导向呢?

  • 屏幕输出的信息很重要,而且我们需要将他存下来的时候;
  • 背景执行中的程序,不希望他干扰屏幕正常的输出结果时;
  • 一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的执行结果,希望他可以存下来时;
  • 一些执行命令的可能已知错误讯息时,想以『 2> /dev/null 』将他丢掉时;
  • 错误讯息与正确讯息需要分别输出时。

当然还有很多的功能的,最简单的就是网友们常常问到的:为何我的 root 都会收到系统 crontab 寄来的错误讯息呢这个是常见的错误, 而如果我们已经知道这个错误讯息是可以忽略的时候! 2> errorfile 这个功能就很重要了!

  • 假设我要将 echo "error message" 以 standard error output 的格式来输出,该如何处置?
既然有 2>&1 来将 2> 转到 1> 去,那么应该也会有 1>&2 吧?没错!就是这个概念!因此你可以这样作:
[xiaoqi@study ~]$ echo "error message" 1>&2
error message
[xiaoqi@study ~]$ echo "error message" 2> /dev/null  1>&2

你会发现第一条有讯息输出到屏幕上,第二条则没有讯息!这表示该讯息已经是透过 2> /dev/null 丢到垃圾桶去了.

8.2 命令执行的判断依据: ; , &&, ||

在某些情况下,很多指令我想要一次输入去执行,而不想要分次执行时,该如何是好?基本上你有两个选择:一个是透过 shell script 撰写脚本去执行,一种则是透过底下的介绍来一次输入多重指令.

8.2.1 cmd ; cmd(不考虑指令相关性的连续指令下达)

在某些时候,我们希望可以一次执行多个指令,例如在关机的时候我希望可以先执行两次 sync 同步 化写入磁盘后才 shutdown 计算机,那么可以怎么作呢?

[xiaoqi@study ~]$ sync;sync;shutdown -h now

在指令与指令中间利用分号 (;) 来隔开,这样一来,分号前的指令执行完后就会立刻接着执行后面的 指令了。

换个角度来想,万一我想要在某个目录底下建立一个文件,也就是说,如果该目录存在的话,那我才建立这个文件,如果不存在,那就算了。也就是说这两个指令彼此之间是有相关性的,前一个指令是否成功的执行与后一个指令是否要执行有关!

那就得使用到 &&||

8.2.2 $?(指令回传值) 与 && 或 ||

如同上面谈到的,两个指令之间有相依性,而这个相依性主要判断的地方就在于前一个指令执行的结 果是否正确。

记得本章之前我们曾介绍过指令回传值吧!就是透过这个回传值.

再复习一次若前一个指令执行的结果为正确,在 Linux 底下会回传一个 $? = 0 的值。 那么我们怎么透过这个回传值来判断后续的指令是否要执行呢?这就得要藉由 &&|| 的 帮忙了!

需要注意的是:两个 & 之间是没有空格的!那个 |则是 [Shift]+[] 的按键结果。

指令下达情况                             说明

cmd1 && cmd2    1. 若 cmd1 执行完毕且正确执行($?=0),则开始执行 cmd2。
                2. 若 cmd1 执行完毕且为错误 ($?≠0),则 cmd2 不执行。
                 
cmd1 || cmd2    1. 若 cmd1 执行完毕且正确执行($?=0),则 cmd2 不执行。
                2. 若 cmd1 执行完毕且为错误 ($?≠0),则开始执行 cmd2。

上述的 cmd1 及 cmd2 都是指令。

回到最初的问题:1.先判断一个目 录是否存在;2.若存在才在该目录底下建立一个文件。由于我们尚未介绍如何判断式 (test) 的使用,在这里我们使用 ls 以及回传值来判断目录是否存在.让我们进行底下这个练习看看:

#范例一:使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 建立 /tmp/abc/hehe
[xiaoqi@study ~]$ ls /tmp/abc && touch /tmp/abc/hehe
ls: 无法访问/tmp/abc: 没有那个文件或目录
#ls 很干脆的说明找不到该目录,但并没有 touch 的错误,表示 touch 并没有执行


[xiaoqi@study ~]$ mkdir /tmp/abc
[xiaoqi@study ~]$ ls /tmp/abc && touch /tmp/abc/hehe
[xiaoqi@study ~]$ ll /tmp/abc/
总用量 0
-rw-rw-r--. 1 xiaoqi xiaoqi 0 8月  21 14:51 hehe

如果 /tmp/abc 不存在时,touch 就不会被执行,若 /tmp/abc 存在的话,那么 touch 就会 开始执行.

不过,我们还得手动自行建立目录,伤脑筋~能不能自动判断,如果没有该目录就给予建立呢? 参考一下底下的例子先:

#范例二:测试 /tmp/abc 是否存在,若不存在则予以建立,若存在就不作任何事情
[xiaoqi@study ~]$ rm -rf /tmp/abc            <--删除刚才建立的目录
[xiaoqi@study ~]$ ls /tmp/abc || mkdir /tmp/abc
ls: 无法访问/tmp/abc: 没有那个文件或目录            <--目录不存在
[xiaoqi@study ~]$ ll -d /tmp/abc/
drwxrwxr-x. 2 xiaoqi xiaoqi 6 8月  21 14:53 /tmp/abc/    <--结果出现了,上一步有mkdir abc这个目录

如果你一再重复ls /tmp/abc || mkdir /tmp/abc 画面也不会出现重复 mkdir 的错误!这是因为 /tmp/abc 已经存在,所以后续的 mkdir 就不会进行!

好了,让我们再次的讨论一下, 如果我想要建立 /tmp/abc/hehe 这个文件, 但我并不知道 /tmp/abc 是否存在,那该如何是好?

#范例三:我不清楚 /tmp/abc 是否存在,但就是要建立 /tmp/abc/hehe 文件
[xiaoqi@study ~]$ ls /tmp/abc/ || mkdir /tmp/abc && touch /tmp/abc/hehe

上面这个范例三总是会尝试建立 /tmp/abc/hehe.不论 /tmp/abc 是否存在。那么范例三应该如 何解释呢? 由于 Linux 底下的指令都是由左往右执行的,所以范例三有几种结果我们来分析一下:

  • (1)若 /tmp/abc 不存在故回传 $?≠0,则 (2)因为 || 遇到非为 0 的 $? 故开始 mkdir /tmp/abc,由于 mkdir /tmp/abc 会成功进行,所以回传 $?=0 (3)因为 && 遇到 $?=0 故会执行 touch /tmp/abc/hehe,最终 hehe 就被建立了;
  • (1)若 /tmp/abc 存在故回传 $?=0,则 (2)因为 || 遇到 0 的 $? 不会进行,此时 $?=0 继续向后传,故 (3) 因为 && 遇到 $?=0 就开始建立 /tmp/abc/hehe 了!最终 /tmp/abc/hehe 被建立起来。

整个流程图示如下:

上面这张图显示的两股数据中,上方的线段为不存在 /tmp/abc 时所进行的指令行为,下方的线段则 是存在 /tmp/abc 所在的指令行为。

如上所述,下方线段由于存在 /tmp/abc 所以导致 $?=0 ,让中间 的 mkdir 就不执行了! 并将 $?=0 继续往后传给后续的 touch 去利用.

例题

  • 以 ls 测试 /tmp/vbirding 是否存在,若存在则显示 "exist" ,若不存在,则显示 "not exist"!

这又牵涉到逻辑判断的问题,如果存在就显示某个数据,若不存在就显示其他数据,那我可以这样做:

[xiaoqi@study ~]$ ls /tmp/vbirding && echo "exist" || echo "not exist"
ls: 无法访问/tmp/vbirding: 没有那个文件或目录
not exist

意思是说,当 ls /tmp/vbirding 执行后,若正确,就执行 echo "exist" ,若有问题,就执行 echo "not exist" !那么如果写成如下的状况会出现什么?

[xiaoqi@study ~]$ ls /tmp/vbirding || echo "exist" && echo "not exist"    
ls: 无法访问/tmp/vbirding: 没有那个文件或目录
exist
not exist

这其实是有问题的,为什么呢?由上图的流程介绍我们知道指令是一个一个往后执行,因此在上面的例子当中,如果 /tmp/vbirding 不存在时,他会进行如下动作:

  1. ls /tmp/vbirding 不存在,因此回传一个非为 0 的数值;
  2. 接下来经过 || 的判断,发现前一个指令回传非为 0 的数值,因此,程序开始执行 echo "not exist" ,而 echo "not exist" 程序肯定可以执行成功,因此会回传一个 0 值给后面的指令;
  3. 经过 && 的判断,咦!是 0 啊!所以就开始执行 echo "exist"

经过这个例题的练习,你应该会了解,由于指令是一个接着一个去执行的,因此,如果真要使用判断,10.6 管线命令 (pipe)那么这个 && 与 || 的顺序就不能搞错。一般来说,假设判断式有三个,也就是:

command1 && command2 || command3

而且顺序通常不会变,因为一般来说, command2command3 会放置肯定可以执行成功的指令,因此,依据上面例题的逻辑分析,您就会晓得为何要如此放置了!

8.3 管线命令 (pipe)

就如同前面所说的, bash 命令执行的时候有输出的数据会出现! 那么如果这群数据必需要经过几 道手续之后才能得到我们所想要的格式,应该如何来设定?

这就牵涉到管线命令的问题了 (pipe) ,管线命令使用的是|这个界定符号! 另外,管线命令与连续下达命令是不一样的! 这点底下我们会再说明。底下我们先举一个例子来说明一下简单的管线命令。

[xiaoqi@study ~]$ ls -la | less

如此一来,使用 ls 指令输出后的内容,就能够被 less 读取,并且利用 less 的功能,我们就能够前 后翻动相关的信息了.非常方便!

我们就来了解一下这个管线命令 |的用途吧! 其实这个管 线命令 | 仅能处理经由前面一个指令传来的正确信息,也就是 standard output 的信息,对于 stdandard error 并没有直接处理的能力。那么整体的管线命令可以使用下图表示:

在每个管线后面接的第一个数据必定是『指令』喔!而且这个指令必须要能够接受 standard input 的 数据才行,这样的指令才可以是为『管线命令』.

例如 less more head tail 等都是可以接受 standard input 的管线命令,至于例如 ls cp mv 等就不是管线命令了!因为 ls cp mv并不会接受来自 stdin 的数据。 也就是说,管线命令主要有两个比较需要注意的地方:

  • 管线命令仅会处理 standard output,对于 standard error output 会予以忽略
  • 管线命令必须要能够接受来自前一个指令的数据成为 standard input 继续处理才行。
想一想,如果你硬要让 standard error 可以被管线命令所使用,那该如何处理?其实就 是透过上一小节的数据流重导向即可! 让 2>&1 加入指令中~就可以让 2> 变成 1>

8.3.1 撷取命令: cut, grep

什么是撷取命令啊?说穿了,就是将一段数据经过分析后,取出我们所想要的。或者是经由分析关键词,取得我们所想要的那一行! 不过,要注意的是,一般来说,撷取讯息通常是针对『一行一行』 来分析的, 并不是整篇讯息分析的.

cut

cut 不就是『切』吗?没错啦!这个指令可以将一段讯息的某一段给他『切』出来~ 处理的讯息是 以『行』为单位喔!底下我们就来谈一谈:

[xiaoqi@study ~]$ cut -d'分隔字符' -f fields <==用于有特定分隔字符 
[xiaoqi@study ~]$ cut -c 字符区间             <==用于排列整齐的讯息 选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思; 
-c :以字符 (characters) 的单位取出固定字符区间


#范例一:将 PATH 变量取出,我要找出第五个路径。
[xiaoqi@study ~]$ echo ${PATH}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/xiaoqi/.local/bin:/home/xiaoqi/bin
#|       1     |        2    |        3       |    4     |                5         |            6      |


[xiaoqi@study ~]$ echo ${path} | cut -d ':' -f 5
/home/xiaoqi/.local/bin
#如同上面的数字显示,我们是以『 : 』作为分隔,因此会出现 /home/xiaoqi/.local/bin

#那么如果想要列出第 3 与第 5 呢?,就是这样:
[xiaoqi@study ~]$ echo ${path} | cut -d ':' -f 3,5
/usr/local/sbin:/home/xiaoqi/.local/bin


#范例二:将 export 输出的讯息,取得第 12 字符以后的所有字符串
[xiaoqi@study ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/xiaoqi"
declare -x HOSTNAME="study.centos.xiaoqi"
......
#注意看,每个数据都是排列整齐的输出!如果我们不想要『 declare -x 』时,就得这么做:
[xiaoqi@study ~]$ export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/home/xiaoqi"
HOSTNAME="study.centos.xiaoqi"
....
#知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
#我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等


#范例三:用 last 将显示的登入者的信息中,仅留下用户大名
[xiaoqi@study ~]$ last
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 14:13   still logged in   
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 12:52 - 14:13  (01:20)    
#last 可以输出『账号/终端机/来源/日期时间』的数据,并且是排列整齐的


[xiaoqi@study ~]$ last | cut -d ' ' -f 1
xiaoqi
xiaoqi
xiaoqi
#由输出的结果我们可以发现第一个空白分隔的字段代表账号,所以使用如上指令:
#但是因为 root pts/1 之间空格有好几个,并非仅有一个,所以,如果要找出
#pts/1 其实不能以 cut -d ' ' -f 1,2 喔!输出的结果会不是我们想要的。

cut 主要的用途在于将『同一行里面的数据进行分解!』最常使用在分析一些数据或文字数据的时候! 这是因为有时候我们会以某些字符当作分区的参数,然后来将数据加以切割,以取得我们所需要的数 据。

尤其是在分析 log 文件的时候!不过,cut 在处理多空格相连的 数据时,可能会比较吃力一点,所以某些时刻可能会使用下一章的 awk 来取代的!

grep

刚刚的 cut 是将一行讯息当中,取出某部分我们想要的,而 grep 则是分析一行讯息, 若当中有我 们所需要的信息,就将该行拿出来~简单的语法是这样的:

[xiaoqi@study ~]$ grep [-acinv] [--color=auto] '搜寻字符串' filename  
选项与参数: 
-a :将 binary 文件以 text 文件的方式搜寻数据 
-c :计算找到 '搜寻字符串' 的次数 
-i :忽略大小写的不同,所以大小写视为相同 
-n :顺便输出行号 
-v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行! 
--color=auto :可以将找到的关键词部分加上颜色的显示喔! 


#范例一:将 last 当中,有出现 root 的那一行就取出来;
[xiaoqi@study ~]$ last | grep  'xiaoqi'   
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 14:13   still logged in   
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 12:52 - 14:13  (01:20) 

#范例二:与范例一相反,只要没有 root 的就取出!
[xiaoqi@study ~]$ last | grep -v 'xiaoqi'
(unknown :0           :0               Mon Aug 19 21:33   still logged in   
reboot   system boot  3.10.0-327.el7.x Mon Aug 19 21:33 - 15:45 (1+18:11)   
 
#范例三:在 last 的输出讯息中,只要有 root 就取出,并且仅取第一栏
[xiaoqi@study ~]$ last | grep 'xiaoqi' | cut -d ' ' -f 1   
xiaoqi
xiaoqi
#在取出 root 之后,利用上个指令 cut 的处理,就能够仅取得第一栏!

#范例四:取出 /etc/man_db.conf 内含 MANPATH 的那几行 
[xiaoqi@study ~]$ cat /etc/man_db.conf | grep 'MANPATH' 
#MANDATORY_MANPATH                     manpath_element
#MANPATH_MAP           path_element    manpath_element

grep 是个很棒的指令!他支持的语法实在是太多了,用在正则表达式里头,能够处理的数据实在是多的很.不过,我们这里先不谈正则语法.下一章再来说明. 先了解一下.

grep 可以解析一行文字,取得关键词,若该行有存在关键词,就会整行列出来!另外, CentOS 7 当中,预设的 grep 已经主动加上 --color=auto 在 alias 内了.

8.4 排序命令: sort, wc, uniq

很多时候,我们都会去计算一次数据里头的相同型态的数据总数,举例来说, 使用 last 可以查得系 统上面有登入主机者的身份。那么我可以针对每个使用者查出他们的总登入次数吗? 此时就得要排 序与计算之类的指令来辅助了!

8.4.1 sort

sort 是很有趣的指令,他可以帮我们进行排序,而且可以依据不同的数据型态来排序喔! 例如数字与文字的排序就不一样。此外,排序的字符与语系的编码有关,因此,如果您需要排序时,建议使用 LANG=C 来让语系统一,数据排序比较好一些。

[dmtsai@study ~]$ sort [-fbMnrtuk] [file or stdin] 
选项与参数:
-f :忽略大小写的差异,例如 A 与 a 视为编码相同; 
-b :忽略最前面的空格符部分;
-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法; 
-n :使用『纯数字』进行排序(默认是以文字型态来排序的); -r :反向排序;
-u :就是 uniq ,相同的数据中,仅出现一行代表;
-t :分隔符,预设是用 [tab] 键来分隔; 
-k :以那个区间 (field) 来进行排序的意思


#范例一:个人账号都记录在 /etc/passwd 下,请将账号进行排序。
[xiaoqi@study ~]$ cat /etc/passwd | sort
abrt:x:173:173::/etc/abrt:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
...
#从上面数据来看,sort的预设是以第一个数据来排序
#而且默认是以『文字』型态来排序的喔!所以由 a 开始排到最后!
范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三栏来排序,该怎么写?
[xiaoqi@study ~]$ cat /etc/passwd | sort -t ':' -k 3
root:x:0:0:root:/root:/bin/bash
xiaoqi:x:1000:1000:xiaoqi:/home/xiaoqi:/bin/bash
qemu:x:107:107:qemu user:/:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin

#看高亮字体的输出部分,为什么会这样排呢?
#如果是以文字型态来排序的话,原本就会是这样,想要使用数字排序:
#cat /etc/passwd | sort -t ':' -k 3 -n
[xiaoqi@study ~]$ cat /etc/passwd | sort -t ':' -k 3 -n - | grep --color=auto "[0-9]" 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
#这样才行!用那个 -n 来告知 sort 以数字来排序!

#范例三:利用 last ,将输出的数据仅取账号,并加以排序
[xiaoqi@study ~]$ last | cut -d ' ' -f 1 | sort

sort 同样是很常用的指令呢!因为我们常常需要比较一些信息啦!举个上面的第二个例子来说好了! 今天假设你有很多的账号,而且你想要知道最大的使用者 ID 目前到哪一号了,使用 sort 一 下子就可以知道答案!

8.4.2 uniq

如果我排序完成了,想要将重复的资料仅列出一个显示,可以怎么做呢?

[dmtsai@study ~]$ uniq [-ic] 
选项与参数:
-i :忽略大小写字符的不同; 
-c :进行计数

#范例一:使用 last 将账号列出,仅取出账号栏,进行排序后仅取出一位;
[xiaoqi@study ~]$ last | cut -d ' ' -f 1 | sort | uniq 

reboot
(unknown
wtmp
xiaoqi


范例二:承上题,如果我还想要知道每个人的登入总次数呢?
[xiaoqi@study ~]$ last | cut -d ' ' -f 1 | sort | uniq -c
      1 
     18 reboot
     17 (unknown
      1 wtmp
     31 xiaoqi
#从上面的结果可以发现,reboot有18次,unknown有17次.大多数是用xiaoqi来操作
#wtmp 与第一行的空白都是 last 的默认字符,那两个可以忽略的!

这个指令用来将『重复的行删除掉只显示一个』,举个例子来说, 你要知道这个月份登入你主机的 用户有谁,而不在乎他的登入次数,那么就使用上面的范例!

  1. 先将所有的数据列出;
  2. 再将人名 独立出来;
  3. 经过排序;
  4. 只显示一个!

由于这个指令是在将重复的东西减少,所以当然需要配合排序过的文件来处理

8.4.3 wc

如果我想要知道 /etc/man_db.conf 这个文件里面有多少字?多少行?多少字符的话, 可以怎么做呢? 其实可以利用 wc 这个指令来达成喔!他可以帮我们计算输出的讯息的整体数据!

[dmtsai@study ~]$ wc [-lwm]
选项与参数:
-l :仅列出行;
-w :仅列出多少字(英文单字); 
-m :多少字符;


#范例一:那个 /etc/man_db.conf 里面到底有多少相关字、行、字符数?
[xiaoqi@study ~]$ cat /etc/man_db.conf | wc
    131     723    5171
#输出的三个数字中,分别代表: 『行、字数、字符数』


#范例二:我知道使用 last 可以输出登入者,但是 last 最后两行并非账号内容,那么请问,
#      我该如何以一行指令串取得登入系统的总人次?
[xiaoqi@study ~]$ last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | grep -v 'unknown' | wc -l
31
#由于 last 会输出空白行, wtmp, unknown, reboot 等无关账号登入的信息,因此,我利用
#grep 取出非空白行,以及去除上述关键词那几行,再计算行数,就能够了解!

wc 也可以当作指令?这是相当有用的计算文件内容的一个工具组!
举个例子来说, 当你要知道目前你的账号文件中有多少个账号时,就使用这个方法:cat /etc/passwd | wc -l啦!因为 /etc/passwd 里头一行代表一个使用者!
所以知道行数就晓得有多少的账号在里头了!而如果要计算一个文件里头有多少个字符时,就使用 wc -m 这个选项!

8.5 双向重导向: tee

想个简单的东西,我们由前一节知道 > 会将数据流整个传送给文件或装置,因此我们除非去读取该 文件或装置, 否则就无法继续利用这个数据流。万一我想要将这个数据流的处理过程中将某段讯息 存下来,应该怎么做?

利用 tee 就可以啰~我们可以这样简单的看一下:

tee 会同时将数据流分送到文件去与屏幕 (screen);而输出到屏幕的,其实就是 stdout ,那就可以让下个指令继续处理喔!

[dmtsai@study ~]$ tee [-a] file
选项与参数:
-a :以累加 (append) 的方式,将数据加入 file 当中!


[xiaoqi@study ~]$ last | tee last.list | cut -d ' ' -f 1
[xiaoqi@study ~]$ cat last.list 
xiaoqi   pts/0        macbookpro.lan   Thu Aug 22 08:13   still logged in   
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 14:13 - 22:37  (08:23)  
#这个范例可以让我们将 last 的输出存一份到 last.list 文件中;


[xiaoqi@study ~]$ ls -l / | tee ~/homefile | more
[xiaoqi@study ~]$ cat ~/homefile 
总用量 32
lrwxrwxrwx.   1 root root    7 8月   9 03:42 bin -> usr/bin
dr-xr-xr-x.   4 root root 4096 8月  11 19:17 boot
#这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出讯息!


[xiaoqi@study ~]$ ls -l / | tee -a ~/homefile | more
#要注意! tee 后接的文件会被覆盖,若加上 -a 这个选项则能将讯息累加。

tee 可以让 standard output 转存一份到文件内并将同样的数据继续送到屏幕去处理! 这样除了可以 让我们同时分析一份数据并记录下来之外,还可以作为处理一份数据的中间暂存盘记录之用

tee 这和指令在很多选择/填充的认证考试中很容易考呢!

8.6 字符转换命令: tr, col, join, paste, expand

我们在 vim 程序编辑器当中,提到过 DOS 断行字符与 Unix 断行字符的不同,并且可以使用 dos2unix 与 unix2dos 来完成转换。

好了,那么思考一下,是否还有其他常用的字符替代?
举例来 说,要将大写改成小写,或者是将数据中的 [tab] 按键转成空格键?还有,如何将两篇讯息整合成一 篇?

底下我们就来介绍一下这些字符转换命令在管线当中的使用方法:

8.6.1 tr

tr 可以用来删除一段讯息当中的文字,或者是进行文字讯息的替换!

[dmtsai@study ~]$ tr [-ds] SET1 ... 
选项与参数:
-d :删除讯息当中的 SET1 这个字符串; 
-s :取代掉重复的字符!

#范例一:将 last 输出的讯息中,所有的小写变成大写字符:
[xiaoqi@study ~]$ last | tr '[a-z]' '[A-Z]'
XIAOQI   PTS/0        MACBOOKPRO.LAN   THU AUG 22 08:13   STILL LOGGED IN   
XIAOQI   PTS/0        MACBOOKPRO.LAN   WED AUG 21 14:13 - 22:37  (08:23)    
XIAOQI   PTS/0        MACBOOKPRO.LAN   WED AUG 21 12:52 - 14:13  (01:20)    
#事实上,没有加上单引号也是可以执行的,如:『 last | tr [a-z] [A-Z] 』


#范例二:将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
[xiaoqi@study ~]$ last | tr -d ':'
xiaoqi   pts/0        macbookpro.lan   Thu Aug 22 0813   still logged in   
xiaoqi   pts/0        macbookpro.lan   Wed Aug 21 1413 - 2237  (0823)    



#范例三:将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除
[xiaoqi@study ~]$ cp /etc/passwd ~/passwd && unix2dos ~/passwd 
[xiaoqi@study ~]$ file /etc/passwd ~/passwd 
/etc/passwd:         ASCII text
/home/xiaoqi/passwd: ASCII text, with CRLF line terminators  <--dos断行

[xiaoqi@study ~]$ cat ~/passwd | tr -d '\r' > ~/passwd.linux
#那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr

[xiaoqi@study ~]$ file /etc/passwd passwd passwd.linux 
/etc/passwd:  ASCII text
passwd:       ASCII text, with CRLF line terminators
passwd.linux: ASCII text

其实这个指令也可以写在正则表示法里头!
因为他也是由正规表示法的方式来取代数据的! 以上面的例子来说,使用 [] 可以设定一串字呢!也常常用来取代文件中的怪异符号!

例如上面第三个例子当中,可以去除 DOS 文件留下来的 ^M 这个断行的符号!这东西相当的有用!相信处理 Linux & Windows 系统中的人们最麻烦的一件事就是这个事情啦!亦即是 DOS 底下会自动的在每行行尾加入 ^M 这个断行符号!这个时候除了以前讲过的 dos2unix 之外,我们也可以使用这个 tr 来 将 ^M 去除! ^M 可以使用 \r 来代替!

8.6.2 col

[dmtsai@study ~]$ col [-xb] 
选项与参数:
-x :将 tab 键转换成对等的空格键

#范例一:利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空白
[root@study tmp]# cat -A /etc/man_db.conf  <--此时会看到很多 ^I 的符号,那就是 tab
[xiaoqi@study ~]$ cat -A /etc/man_db.conf | col -x | cat -A | more
#如此一来, [tab] 按键会被取代成为空格键!

虽然 col 有他特殊的用途,不过,很多时候,他可以用来简单的处理将 [tab] 按键取代成为空格键!

例如上面的例子当中,如果使用 cat -A 则 [tab] 会以 ^I 来表示。但经过 col -x 的处理,则会将 [tab] 取代成为对等的空格键!

8.6.3 join

join 看字面上的意义 (加入/参加) 就可以知道,他是在处理两个文件之间的数据, 而且,主要是在处理『两个文件当中,有 "相同数据" 的那一行,才将他加在一起』的意思。

我们利用底下的简单例 子来说明:

[dmtsai@study ~]$ join [-ti12] file1 file2
选项与参数:
-t :join 默认以空格符分隔数据,并且比对『第一个字段』的数据,
    如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个! 
-i :忽略大小写的差异;
-1 :这个是数字的 1 ,代表『第一个文件要用那个字段来分析』的意思; 
-2 :代表『第二个文件要用那个字段来分析』的意思。


#范例一:用 root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一栏
[root@study xiaoqi]# head -n 3 /etc/passwd /etc/shadow 
==> /etc/passwd <==
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/shadow <==
root:$6$IMbw1vkZUaa9tcgT$2IIAFR4RXN4Fy93tfGae3OrELBJN98EYFhFpubiGtcXUim99egm8OWBqj1Eeqx1Ioz3yubj8prWv05Drwsypz0::0:99999:7:::
bin:*:16659:0:99999:7:::
daemon:*:16659:0:99999:7:::
#由输出的资料可以发现这两个文件的最左边字段都是相同账号!且以 : 分隔

[root@study xiaoqi]# join -t ':' /etc/passwd /etc/shadow | head -n 3
root:x:0:0:root:/root:/bin/bash:$6$IMbw1vkZUaa9tcgT$2IIAFR4RXN4Fy93tfGae3OrELBJN98EYFhFpubiGtcXUim99egm8OWBqj1Eeqx1Ioz3yubj8prWv05Drwsypz0::0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:*:16659:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:16659:0:99999:7:::
#透过上面这个动作,我们可以将两个文件第一字段相同者整合成一列!
#第二个文件的相同字段并不会显示(因为已经在最左边的字段出现了啊!)


#范例二:我们知道 /etc/passwd 第四个字段是 GID ,那个 GID 记录在
       /etc/group 当中的第三个字段,请问如何将两个文件整合?
[root@study xiaoqi]# head -n 3 /etc/passwd /etc/group
==> /etc/passwd <==
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/group <==
root:x:0:
bin:x:1:
daemon:x:2:
#从上面可以看到,确实有相同的部分喔!赶紧来整合一下!
[root@study xiaoqi]# join -t ':' -1 4 /etc/passwd -2 3 /etc/group
0:root:x:0:root:/root:/bin/bash:root:x:
1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:
2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:
#同样的,相同的字段部分被移动到最前面了!所以第二个文件的内容就没再显示。 
#请读者们配合上述显示两个文件的实际内容来比对!

这个 join 在处理两个相关的数据文件时,就真的是很有帮助的啦!

例如上面的案例当中,我的 /etc/passwd, /etc/shadow, /etc/group 都是有相关性的, 其中 /etc/passwd, /etc/shadow 以账号为相关性, 至于 /etc/passwd, /etc/group 则以所谓的 GID (账号的数字定义) 来作为他的相关性。根据这个相关性,我们可以将有关系的资料放置在一起!这在处理数据可是相当有帮助的!

此外,需要特别注意的是,在使用 join 之前,你所需要处理的文件应该要事先经过排序 (sort) 处理! 否则有些比对的项目会被略过呢!特别注意!

8.6.4 paste

这个 paste 就要比 join 简单多了!相对于 join 必须要比对两个文件的数据相关性, paste 就直接 『将两行贴在一起,且中间以 [tab] 键隔开』而已!

 [dmtsai@study ~]$ paste [-d] file1 file2
选项与参数:
-d :后面可以接分隔字符。预设是以 [tab] 来分隔的!
- :如果 file 部分写成 - ,表示来自 standard input 的资料的意思。


#范例一:用 root 身份,将 /etc/passwd 与 /etc/shadow 同一行贴在一起
[root@study xiaoqi]# paste /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash root:$6$IMbw1vkZUaa9tcgT$2IIAFR4RXN4Fy93tfGae3OrELBJN98EYFhFpubiGtcXUim99egm8OWBqj1Eeqx1Ioz3yubj8prWv05Drwsypz0::0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin        bin:*:16659:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16659:0:99999:7:::
#注意!同一行中间是以 [tab] 按键隔开的!

#范例二:先将 /etc/group 读出(用 cat),然后与范例一贴上一起!且仅取出前三行
[root@study xiaoqi]# cat /etc/group | paste /etc/passwd /etc/shadow - | head -3
root:x:0:0:root:/root:/bin/bash root:$6$IMbw1vkZUaa9tcgT$2IIAFR4RXN4Fy93tfGae3OrELBJN98EYFhFpubiGtcXUim99egm8OWBqj1Eeqx1Ioz3yubj8prWv05Drwsypz0::0:99999:7:::      root:x:0:
bin:x:1:1:bin:/bin:/sbin/nologin        bin:*:16659:0:99999:7:::        bin:x:1:
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16659:0:99999:7:::     daemon:x:2:
#这个例子的重点在那个 - 的使用!那玩意儿常常代表 stdin!

8.6.5 expand

这玩意儿就是在将 [tab] 按键转成空格键.

 [dmtsai@study ~]$ expand [-t] file
选项与参数:
-t :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空格键取代。
    我们也可以自行定义一个 [tab] 按键代表多少个字符呢!

#范例一:将 /etc/man_db.conf 内行首为 MANPATH 的字样就取出;仅取前三行;
[root@study xiaoqi]# grep '^MANPATH' /etc/man_db.conf | head -3
MANPATH_MAP     /bin                    /usr/share/man
MANPATH_MAP     /usr/bin                /usr/share/man
MANPATH_MAP     /sbin                   /usr/share/man

#范例二:承上,如果我想要将所有的符号都列出来?(用 cat)
[root@study xiaoqi]# grep '^MANPATH' /etc/man_db.conf | head -3 | cat -A
MANPATH_MAP^I/bin^I^I^I/usr/share/man$
MANPATH_MAP^I/usr/bin^I^I/usr/share/man$
MANPATH_MAP^I/sbin^I^I^I/usr/share/man$
#[tab] 按键可以被 cat -A 显示成为 ^I

#范例三:承上,我将 [tab] 按键设定成 6 个字符的话?
[root@study xiaoqi]# grep '^MANPATH' /etc/man_db.conf | head -3 | expand -t 6 - | cat -A
MANPATH_MAP /bin              /usr/share/man$
MANPATH_MAP /usr/bin          /usr/share/man$
MANPATH_MAP /sbin             /usr/share/man$
123456123456123456123456123456123456123456123456...
#仔细看一下上面的数字说明,因为我是以 6 个字符来代表一个 [tab] 的长度,所以,
#MAN... 到 /usr 之间会隔 12 (两个 [tab]) 个字符喔!如果 tab 改成 9 的话,
#情况就又不同了!这里也不好理解~您可以多设定几个数字来查阅!

expand 也是挺好玩的~他会自动将 [tab] 转成空格键~所以,以上面的例子来说, 使用 cat -A 就 会查不到 ^I 的字符啰~此外,因为 [tab] 最大的功能就是格式排列整齐!

我们转成空格键后,这 个空格键也会依据我们自己的定义来增加大小~ 所以,并不是一个 ^I 就会换成 8 个空白喔!这个地方要特别注意的哩!

此外,您也可以参考一下 unexpand 这个将空白转成 [tab] 的指令功能!

8.7 分区命令: split

如果你有文件太大,导致一些携带式装置无法复制的问题,找 split 就对了! 他可以帮你将 一个大文件,依据文件大小或行数来分区,就可以将大文件分区成为小文件了! 快速又有效!

 [dmtsai@study ~]$ split [-bl] file PREFIX
选项与参数:
-b :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等; 
-l :以行数来进行分区。
PREFIX :代表前导符的意思,可作为分区文件的前导文字。

#范例一:我的 /etc/services 有六百多 K,若想要分成 300K 一个文件时?
[root@study xiaoqi]# cd /tmp/
[root@study tmp]# split -b 300k /etc/services service
[root@study tmp]# ll -k service* 
-rw-r--r--. 1 root root 307200 8月  22 16:33 serviceaa
-rw-r--r--. 1 root root 307200 8月  22 16:33 serviceab
-rw-r--r--. 1 root root  55893 8月  22 16:33 serviceac
#那个文件名可以随意取!我们只要写上前导文字,小文件就会以
#xxxaa, xxxab, xxxac 等方式来建立小文件的!

#范例二:如何将上面的三个小文件合成一个文件,档名为 servicesback
[root@study tmp]# cat service* >> serviceback
#就使用数据流重导向就好了

#范例三:使用 ls -al / 输出的信息中,每十行记录成一个文件
[root@study tmp]# ls -la / | split -l 10 - lsroot 
[root@study tmp]# wc -l lsroot*
  10 lsrootaa
  10 lsrootab
   2 lsrootac
  22 总用量
#重点在那个 - 啦!一般来说,如果需要 stdout/stdin 时,但偏偏又没有文件,
#有的只是 - 时,那么那个 - 就会被当成 stdin 或 stdout ~

在 Windows 操作系统下,你要将文件分区需要如何作?伤脑筋吧!在 Linux 底下就简单的多了!

你要将文件分区的话,那么就使用 -b size 来将一个分区的文件限制其大小,如果是行数的话,那么 就使用 -l line 来分区!好用的很!如此一来,你就可以轻易的将你的文件分区成某些软件能够支持 的最大容量.

8.8 参数代换:xargs

  • xargs 是在做什么的呢?
就以字面上的意义来看, x 是加减乘除的乘号,args 则是 arguments (参数) 的意思,所以说,这个玩意儿就是在产生某个指令的参数的意思!

xargs 可以读入 stdin 的数据,并 且以空格符或断行字符作为分辨,将 stdin 的资料分隔成为 arguments 。因为是以空格符作为分隔,所以,如果有一些档名或者是其他意义的名词内含有空格符的时候, xargs 可能就会误判了.

[dmtsai@study ~]$ xargs [-0epn] command
选项与参数:
-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等等字符时,这个 -0 参数
    可以将他还原成一般字符。这个参数可以用于特殊状态喔!
-e :这个是 EOF (end of file) 的意思。后面可以接一个字符串,当 xargs 分析到这个字符串时,
    就会停止继续工作!
-p :在执行每个指令的 argument 时,都会询问使用者的意思;
-n :后面接次数,每次 command 指令执行时,要使用几个参数的意思。 
    当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!


#范例一:将 /etc/passwd 内的第一栏取出,仅取三行,使用 id 这个指令将每个账号内容秀出来
[root@study tmp]# id root
uid=0(root) gid=0(root) 组=0(root)
#这个 id 指令可以查询用户的 UID/GID 等信息

[root@study tmp]# id $(cut -d ':' -f 1 /etc/passwd | head -n 3)
id: 额外的操作数 "bin:x:1:1:bin:/bin:/sbin/nologin"
#虽然使用 $(cmd) 可以预先取得参数,但可惜的是, id 这个指令『仅』能接受一个参数而已!
#所以上述的这个指令执行会出现错误!根本不会显示用户的 ID 啊!

[root@study tmp]# cut -d ':' -f 1 /etc/passwd | head -n 3 | id
uid=0(root) gid=0(root) 组=0(root) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 #查询自己的id
#因为 id 并不是管线命令,因此在上面这个指令执行后,前面的东西通通不见!只会执行 id!

[root@study tmp]# cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs id
id: 额外的操作数 "bin:x:1:1:bin:/bin:/sbin/nologin"
Try 'id --help' for more information.
#依旧会出现错误!这是因为 xargs 一口气将全部的数据通通丢给 id 处理~但 id 就接受 1 个啊最多!

[root@study tmp]# cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id
uid=0(root) gid=0(root) 组=0(root)
uid=1(bin) gid=1(bin) 组=1(bin)
uid=2(daemon) gid=2(daemon) 组=2(daemon)
#透过 -n 来处理,一次给予一个参数,因此上述的结果就 OK 正常的显示啰!


#范例二:同上,但是每次执行 id 时,都要询问使用者是否动作?
[root@study tmp]# cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id
id root ?...y
uid=0(root) gid=0(root) 组=0(root)
id bin ?...y
uid=1(bin) gid=1(bin) 组=1(bin)
#这个 -p 的选项可以让用户的使用过程中,被询问到每个指令是否执行!


范例三:将所有的 /etc/passwd 内的账号都以 id 查阅,但查到 sync 就结束指令串
[root@study tmp]# cut -d ':' -f 1 /etc/passwd | xargs -e'sync' -n 1 id 
uid=0(root) gid=0(root) 组=0(root)
uid=1(bin) gid=1(bin) 组=1(bin)
uid=2(daemon) gid=2(daemon) 组=2(daemon)
uid=3(adm) gid=4(adm) 组=4(adm)
uid=4(lp) gid=7(lp) 组=7(lp)
#仔细与上面的案例做比较。也同时注意,那个 -e'sync' 是连在一起的,中间没有空格键。
#上个例子当中,第六个参数是 sync 啊,那么我们下达 -e'sync' 后,则分析到 sync 这个字符串时,
#后面的其他 stdin 的内容就会被 xargs 舍弃掉了!

其实,在 man xargs 里面就有三四个小范例,可以自行参考一下内容。

此外, xargs 真的是很好用的指令!真的需要好好的参详参详!会使用 xargs 的原因是,很多指令其实并不支持管线命令,因此我们可以透过 xargs 来提供该指令引用 standard input 之用!

  • 举例来说,我们使用如下 的范例来说明:
范例四:找出 /usr/sbin 底下具有特殊权限的档名,并使用 ls -l 列出详细属性
[root@study tmp]# find /usr/sbin/ -perm /7000 | xargs ls -l
-rwx--s--x. 1 root lock      11208 6月  10 2014 /usr/sbin/lockdev
-rwsr-xr-x. 1 root root     113400 11月 20 2015 /usr/sbin/mount.nfs
-rwxr-sr-x. 1 root root      11208 11月 20 2015 /usr/sbin/netreport
#聪明应该会想到使用『 ls -l $(find /usr/sbin -perm /7000) 』来处理这个范例! 
#都 OK!能解决问题的方法,就是好方法!

8.9 关于减号 - 的用途

管线命令在 bash 的连续的处理程序中是相当重要的!另外,在 log file 的分析当中也是相当重要的 一环,所以请特别留意!

另外,在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的 stdin , 某些指令需要用到文件名 (例如 tar) 来进行处理时,该 stdin 与 stdout 可以利用减号 "-" 来替代.

  • 举例来说:
[root@study ~]# mkdir /tmp/homeback
[root@study ~]# tar -cvf - /home | tar -xvf - -C /tmp/homeback

上面这个例子是说:我将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是传送到 stdout; 经过管线后,将 ` 传送给后面的 tar -xvf -

后面的这个 - 则是取用前 一个指令的 stdout, 因此,我们就不需要使用 filename 了!这是很常见的例子!

最后修改:2020 年 01 月 21 日 04 : 31 PM

发表评论