管线命令“ | ”仅能处理经由前面一个指令传来的正确信息,也就是 standard output 的 信息,对于 stdandard error 并没有直接处理的能力。
管线命令主要有两个比较需要注意的地方:
- 管线命令仅会处理 standard output,对于 standard error output 会 以忽略
- 管线命令必须要能够接受来自前一个指令的数据成为 standard input 继续处理才行。
撷取命令: cut, grep
- cut
[dmtsai@study ~]$ cut -d'分隔字符' -f fields <==用于有特定分隔字符
[dmtsai@study ~]$ cut -c 字符区间 <==用于排列整齐的讯息
选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;
范例一:将 PATH 变量取出,我要找出第五个路径。
[dmtsai@study ~]$ echo ${PATH}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 1 | 2 | 3 | 4 | 5 | 6 |
[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 5
# 如同上面的数字显示,我们是以“ : ”作为分隔,因此会出现 /home/dmtsai/.local/bin
# 那么如果想要列出第 3 与第 5 呢?,就是这样:
[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 3,5
范例二:将 export 输出的讯息,取得第 12 字符以后的所有字串
[dmtsai@study ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 注意看,每个数据都是排列整齐的输出!如果我们不想要“ declare -x ”时,就得这么做:
[dmtsai@study ~]$ export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/home/dmtsai"
HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!
范例三:用 last 将显示的登陆者的信息中,仅留下使用者大名
[dmtsai@study ~]$ last
root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in
root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33)
root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16)
# last 可以输出“帐号/终端机/来源/日期时间”的数据,并且是排列整齐的
[dmtsai@study ~]$ last | cut -d ' ' -f 1
# 由输出的结果我们可以发现第一个空白分隔的字段代表帐号,所以使用如上指令:
# 但是因为 root pts/1 之间空格有好几个,并非仅有一个,所以,如果要找出
# pts/1 其实不能以 cut -d ' ' -f 1,2 喔!输出的结果会不是我们想要的。
- grep
cut 是将一行讯息当中,取出某部分我们想要的,而 grep 则是分析一行讯息, 若当中 有我们所需要的信息,就将该行拿出来~简单的语法是这样的:
[dmtsai@study ~]$ grep [-acinv] [--color=auto] '搜寻字串' filename
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字串' 内容的那一行!
--color=auto :可以将找到的关键字部分加上颜色的显示喔!
范例一:将 last 当中,有出现 root 的那一行就取出来;
[dmtsai@study ~]$ last | grep 'root'
范例二:与范例一相反,只要没有 root 的就取出!
[dmtsai@study ~]$ last | grep -v 'root'
范例三:在 last 的输出讯息中,只要有 root 就取出,并且仅取第一栏
[dmtsai@study ~]$ last | grep 'root' | cut -d ' ' -f 1
# 在取出 root 之后,利用上个指令 cut 的处理,就能够仅取得第一栏 !
范例四:取出 /etc/man_db.conf 内含 MANPATH 的那几行
[dmtsai@study ~]$ grep --color=auto 'MANPATH' /etc/man_db.conf
....(前面省略)....
MANPATH_MAP /usr/games /usr/share/man
MANPATH_MAP /opt/bin /opt/man
MANPATH_MAP /opt/sbin /opt/man
# 神奇的是,如果加上 --color=auto 的选项,找到的关键字部分会用特殊颜色显示喔!
排序命令: sort, wc, uniq
- sort
[dmtsai@study ~]$ sort [-fbMnrtuk] [file or stdin]
选项与参数:
-f :忽略大小写的差异,例如 A 与 a 视为编码相同;
-b :忽略最前面的空白字符部分;
-M :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
-n :使用“纯数字”进行排序(默认是以文字体态来排序的);
-r :反向排序;
-u :就是 uniq ,相同的数据中,仅出现一行代表;
-t :分隔符号,默认是用 [tab] 键来分隔;
-k :以那个区间 (field) 来进行排序的意思
范例一:个人帐号都记录在 /etc/passwd 下,请将帐号进行排序。
[dmtsai@study ~]$ cat /etc/passwd | sort
abrt:x:173:173::/etc/abrt:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
alex:x:1001:1002::/home/alex:/bin/bash
# 由上面的数据看起来, sort 是默认“以第一个”数据来排序,
# 而且默认是以“文字”型态来排序的喔!所以由 a 开始排到最后 !
范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三栏来排序,该如何?
[dmtsai@study ~]$ cat /etc/passwd | sort -t ':' -k 3
root:x:0:0:root:/root:/bin/bash
dmtsai:x:1000:1000:dmtsai:/home/dmtsai:/bin/bash
alex:x:1001:1002::/home/alex:/bin/bash
arod:x:1002:1003::/home/arod:/bin/bash
# 看到特殊字体的输出部分了吧?怎么会这样排列啊?呵呵!没错啦~
# 如果是以文字体态来排序的话,原本就会是这样,想要使用数字排序:
# cat /etc/passwd | sort -t ':' -k 3 -n
# 这样才行啊!用那个 -n 来告知 sort 以数字来排序啊!
范例三:利用 last ,将输出的数据仅取帐号,并加以排序
[dmtsai@study ~]$ last | cut -d ' ' -f 1 | sort
- uniq
[dmtsai@study ~]$ uniq [-ic]
选项与参数:
-i :忽略大小写字符的不同;
-c :进行计数
范例一:使用 last 将帐号列出,仅取出帐号栏,进行排序后仅取出一位;
[dmtsai@study ~]$ last | cut -d ' ' -f 1 | sort | uniq
范例二:承上题,如果我还想要知道每个人的登陆总次数呢?
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort | uniq -c
1
6 (unknown
47 dmtsai
4 reboot
7 root
1 wtmp
# 从上面的结果可以发现 reboot 有 4 次, root 登陆则有 7 次!大部分是以 dmtsai 来操作!
# wtmp 与第一行的空白都是 last 的默认字符,那两个可以忽略的!
- wc
[dmtsai@study ~]$ wc [-lwm]
选项与参数:
-l :仅列出行;
-w :仅列出多少字(英文单字);
-m :多少字符;
[dmtsai@study ~]$ cat /etc/man_db.conf | wc
131 723 5171
# 输出的三个数字中,分别代表: “行、字数、字符数”
范例二:我知道使用 last 可以输出登陆者,但是 last 最后两行并非帐号内容,那么请问,我该如何以一行指令串取得登陆系统的总人次?
[dmtsai@study ~]$ last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | \
> grep -v 'unknown' |wc -l
# 由于 last 会输出空白行, wtmp, unknown, reboot 等无关帐号登陆的信息,因此,我利用
# grep 取出非空白行,以及去除上述关键字那几行,再计算行数,就能够了解 !
双向重导向: tee
我想要将这个数据流的处理过程 中将某段讯息存下来,应该怎么做? 利用 tee 就可以 ~我们可以这样简单的看一下:
tee 会同时将数据流分送到文件去与屏幕 (screen);而输出到屏幕的,其实就是 stdout , 那就可以让下个指令继续处理!
[dmtsai@study ~]$ tee [-a] file
选项与参数:
-a :以累加 (append) 的方式,将数据加入 file 当中!
[dmtsai@study ~]$ last | tee last.list | cut -d " " -f1
# 这个范例可以让我们将 last 的输出存一份到 last.list 文件中;
[dmtsai@study ~]$ ls -l /home | tee ~/homefile | more
# 这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出讯息!
[dmtsai@study ~]$ ls -l / | tee -a ~/homefile | more
# 要注意! tee 后接的文件会被覆盖,若加上 -a 这个选项则能将讯息累加。
字符转换命令: tr, col, join, paste, expand
- tr
tr 可以用来删除一段讯息当中的文字,或者是进行文字讯息的替换!
[dmtsai@study ~]$ tr [-ds] SET1 ...
选项与参数:
-d :删除讯息当中的 SET1 这个字串;
-s :取代掉重复的字符!
范例一:将 last 输出的讯息中,所有的小写变成大写字符:
[dmtsai@study ~]$ last | tr '[a-z]' '[A-Z]'
# 事实上,没有加上单引号也是可以执行的,如:“ last | tr [a-z] [A-Z] ”
范例二:将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
[dmtsai@study ~]$ cat /etc/passwd | tr -d ':'
范例三:将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除
[dmtsai@study ~]$ cp /etc/passwd ~/passwd && unix2dos ~/passwd
[dmtsai@study ~]$ file /etc/passwd ~/passwd
/etc/passwd: ASCII text
/home/dmtsai/passwd: ASCII text, with CRLF line terminators <==就是 DOS 断行
[dmtsai@study ~]$ cat ~/passwd | tr -d '\r' > ~/passwd.linux
# 那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr
[dmtsai@study ~]$ ll /etc/passwd ~/passwd*
-rw-r--r--. 1 root root 2092 Jun 17 00:20 /etc/passwd
-rw-r--r--. 1 dmtsai dmtsai 2133 Jul 9 22:13 /home/dmtsai/passwd
-rw-rw-r--. 1 dmtsai dmtsai 2092 Jul 9 22:13 /home/dmtsai/passwd.linux
# 处理过后,发现文件大小与原本的 /etc/passwd 就一致了!
- col
[dmtsai@study ~]$ col [-xb]
选项与参数:
-x :将 tab 键转换成对等的空白键
范例一:利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空白
[dmtsai@study ~]$ cat -A /etc/man_db.conf <==此时会看到很多 ^I 的符号,那就是 tab
[dmtsai@study ~]$ cat /etc/man_db.conf | col -x | cat -A | more
# 嘿嘿!如此一来, [tab] 按键会被取代成为空白键,输出就美观多了!
- join
在处理“两个文件当中,有 "相同数据" 的那一行,才将他加在一起”。
[dmtsai@study ~]$ join [-ti12] file1 file2
选项与参数:
-t :join 默认以空白字符分隔数据,并且比对“第一个字段”的数据,
如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个!
-i :忽略大小写的差异;
-1 :这个是数字的 1 ,代表“第一个文件要用那个字段来分析”的意思;
-2 :代表“第二个文件要用那个字段来分析”的意思。
范例一:用 root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一栏
[root@study ~]# 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$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:*:16372:0:99999:7:::
daemon:*:16372:0:99999:7:::
# 由输出的数据可以发现这两个文件的最左边字段都是相同帐号!且以 : 分隔
[root@study ~]# join -t ':' /etc/passwd /etc/shadow | head -n 3
root:x:0:0:root:/root:/bin/bash:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:16372:0:99999:7:::
# 通过上面这个动作,我们可以将两个文件第一字段相同者整合成一列!
# 第二个文件的相同字段并不会显示(因为已经在最左边的字段出现了啊!)
范例二:我们知道 /etc/passwd 第四个字段是 GID ,那个 GID 记录在/etc/group 当中的第三个字段,请问如何将两个文件整合?
[root@study ~]# 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 ~]# join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
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:
# 同样的,相同的字段部分被移动到最前面了!所以第二个文件的内容就没再显示。
# 请读者们配合上述显示两个文件的实际内容来比对!
- paste
paste 就 直接“将两行贴在一起,且中间以 [tab] 键隔开”而已!简单的使用方法:
[dmtsai@study ~]$ paste [-d] file1 file2
选项与参数:
-d :后面可以接分隔字符。默认是以 [tab] 来分隔的!
- :如果 file 部分写成 - ,表示来自 standard input 的数据的意思。
范例一:用 root 身份,将 /etc/passwd 与 /etc/shadow 同一行贴在一起
[root@study ~]# paste /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash root:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin bin:*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16372:0:99999:7:::
# 注意喔!同一行中间是以 [tab] 按键隔开的!
范例二:先将 /etc/group 读出(用 cat),然后与范例一贴上一起!且仅取出前三行
[root@study ~]# cat /etc/group | paste /etc/passwd /etc/shadow - | head -n 3
# 这个例子的重点在那个 - 的使用!那玩意儿常常代表 stdin 喔!
- expand
就是在将 [tab] 按键转成空白键啦~可以这样玩:
[dmtsai@study ~]$ expand [-t] file
选项与参数:
-t :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空白键取代。
我们也可以自行定义一个 [tab] 按键代表多少个字符呢!
范例一:将 /etc/man_db.conf 内行首为 MANPATH 的字样就取出;仅取前三行;
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3
MANPATH_MAP /bin /usr/share/man
MANPATH_MAP /usr/bin /usr/share/man
MANPATH_MAP /sbin /usr/share/man
# 行首的代表标志为 ^ ,这个我们留待下节介绍!先有概念即可!
范例二:承上,如果我想要将所有的符号都列出来?(用 cat)
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 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 个字符的话?
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 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 的话,
# 情况就又不同了!这里也不好理解~您可以多设置几个数字来查阅就晓得!
分区命令: split
可以帮 你将一个大文件,依据文件大小或行数来分区,就可以将大文件分区成为小文件了!
[dmtsai@study ~]$ split [-bl] file PREFIX
选项与参数:
-b :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等;
-l :以行数来进行分区。
PREFIX :代表前置字符的意思,可作为分区文件的前导文字。
范例一:我的 /etc/services 有六百多K,若想要分成 300K 一个文件时?
[dmtsai@study ~]$ cd /tmp; split -b 300k /etc/services services
[dmtsai@study tmp]$ ll -k services*
-rw-rw-r--. 1 dmtsai dmtsai 307200 Jul 9 22:52 servicesaa
-rw-rw-r--. 1 dmtsai dmtsai 307200 Jul 9 22:52 servicesab
-rw-rw-r--. 1 dmtsai dmtsai 55893 Jul 9 22:52 servicesac
# 那个文件名可以随意取的啦!我们只要写上前导文字,小文件就会以
# xxxaa, xxxab, xxxac 等方式来创建小文件的!
范例二:如何将上面的三个小文件合成一个文件,文件名为 servicesback
[dmtsai@study tmp]$ cat services* >> servicesback
# 很简单吧?就用数据流重导向就好啦!简单!
范例三:使用 ls -al / 输出的信息中,每十行记录成一个文件
[dmtsai@study tmp]$ ls -al / | split -l 10 - lsroot
[dmtsai@study tmp]$ wc -l lsroot*
10 lsrootaa
10 lsrootab
4 lsrootac
24 total
# 重点在那个 - 啦!一般来说,如果需要 stdout/stdin 时,但偏偏又没有文件,
# 有的只是 - 时,那么那个 - 就会被当成 stdin 或 stdout ~
参数代换: xargs
[dmtsai@study ~]$ xargs [-0epn] command
选项与参数:
-0 :如果输入的 stdin 含有特殊字符,例如 `, \, 空白键等等字符时,这个 -0 参数
可以将他还原成一般字符。这个参数可以用于特殊状态喔!
-e :这个是 EOF (end of file) 的意思。后面可以接一个字串,当 xargs 分析到这个字串时,
就会停止继续工作!
-p :在执行每个指令的 argument 时,都会询问使用者的意思;
-n :后面接次数,每次 command 指令执行时,要使用几个参数的意思。
当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!
范例一:将 /etc/passwd 内的第一栏取出,仅取三行,使用 id 这个指令将每个帐号内容秀出来
[dmtsai@study ~]$ id root
uid=0(root) gid=0(root) groups=0(root) # 这个 id 指令可以查询使用者的 UID/GID 等信息
[dmtsai@study ~]$ id $(cut -d ':' -f 1 /etc/passwd | head -n 3)
# 虽然使用 $(cmd) 可以预先取得参数,但可惜的是, id 这个指令“仅”能接受一个参数而已!
# 所以上述的这个指令执行会出现错误!根本不会显示用户的 ID 啊!
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | id
uid=1000(dmtsai) gid=1000(dmtsai) groups=1000(dmtsai),10(wheel) # 我不是要查自己啊!
# 因为 id 并不是管线命令,因此在上面这个指令执行后,前面的东西通通不见!只会执行 id!
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs id
# 依旧会出现错误!这是因为 xargs 一口气将全部的数据通通丢给 id 处理~但 id 就接受 1 个啊最多!
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id
uid=0(root) gid=0(root) groups=0(root)
uid=1(bin) gid=1(bin) groups=1(bin)
uid=2(daemon) gid=2(daemon) groups=2(daemon)
# 通过 -n 来处理,一次给 一个参数,因此上述的结果就 OK 正常的显示 !
范例二:同上,但是每次执行 id 时,都要询问使用者是否动作?
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id
id root ?...y
uid=0(root) gid=0(root) groups=0(root)
id bin ?...y
.....(下面省略).....
# 呵呵!这个 -p 的选项可以让使用者的使用过程中,被询问到每个指令是否执行!
范例三:将所有的 /etc/passwd 内的帐号都以 id 查阅,但查到 sync 就结束指令串
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | xargs -e'sync' -n 1 id
# 仔细与上面的案例做比较。也同时注意,那个 -e'sync' 是连在一起的,中间没有空白键。
# 上个例子当中,第六个参数是 sync 啊,那么我们下达 -e'sync' 后,则分析到 sync 这个字串时,
# 后面的其他 stdin 的内容就会被 xargs 舍弃掉了!
使用 xargs 的原因是, 很多指令其 实并不支持管线命令,因此我们可以通过 xargs 来提供该指令引用 standard input 之用!举例 来说,我们使用如下的范例来说明:
范例四:找出 /usr/sbin 下面具有特殊权限的文件名,并使用 ls -l 列出详细属性
[dmtsai@study ~]$ find /usr/sbin -perm /7000 | xargs ls -l
-rwx--s--x. 1 root lock 11208 Jun 10 2014 /usr/sbin/lockdev
-rwsr-xr-x. 1 root root 113400 Mar 6 12:17 /usr/sbin/mount.nfs
-rwxr-sr-x. 1 root root 11208 Mar 6 11:05 /usr/sbin/netreport
.....(下面省略).....
# 使用“ ls -l $(find /usr/sbin -perm /7000) ”也可以来处理这个范例!
关于减号 - 的用途
在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的 stdin , 某些指令需要用到文件名称 (例如 tar) 来进行处理时,该 stdin 与 stdout 可以利用减号 "-" 来替代, 举例来说:
[root@study ~]# mkdir /tmp/homeback
[root@study ~]# tar -cvf - /home | tar -xvf - -C /tmp/homeback
上面这个例子是说:“我将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是 传送到 stdout; 经过管线后,将 tar -cvf - /home 传送给后面的 tar -xvf - ”。后面的这个 - 则 是取用前一个指令的 stdout, 因此,我们就不需要使用 filename 了!