Linux【2】-管理文件-4-cut

正如其名,cut 小能手的专长就是“剪”。具体来说,就是在文件中负责剪切数据用的。cut 是以每一行为一个处理对象的,这种机制和 sed 命令一样。

一、参数说明

[root@www ~]# cut -d'分隔字符' -f fields <==用于有特定分隔字符
[root@www ~]# cut -c 字符区间 <==用于排列整齐的讯息

选项与参数:

cut 命令逐行地处理输入,并从中取出某些列。这里说的「列」有三种模式:
-b # 以字节作为标准取出列
-f :依据 -d 的分隔字符将一段讯息分割成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;
-d :后面接分隔字符。与 -f 一起使用;

cut 命令还支持 --complement 参数,意思是取补集。比如在我们刚才的例子中,取补集就意味着取出第一列、第二列、第六列至第八列以及第十列。

二、常规用法

为了让大家对 cut 有一个初步印象,我们来举一个例子。当你执行 who 命令时,会输出类似下面的内容:

[rocrocket@roclinux ~]$ who
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)

如果我们想提取每一行的第 3 个字节,就这样:

[rocrocket@roclinux ~]$ who|cut -b 3
c
c
c

看明白了吧,-b选项后面可以设定要提取哪一个字节,其实 -b 和 3 之间没有空格也是可以的,但推荐有空格,提高可读性。

2.1 cut 的定位依据

所谓“定位依据”,通俗地讲就是,我该如何告诉 cut 我想定位到哪一段内容进行剪切呢?

其实,cut 命令共接受三类定位方法:

  • 第一,按字节(bytes)定位,用-b选项。
  • 第二,按字符(characters)定位,用-c选项。
  • 第三,按域(fields)定位,用-f选项。

好像不太懂呢,没关系,在下面的内容中,我们会为大家详细介绍这几种定位方法的知识和技巧,现在就开始!

2.2 字节定位的技巧

在“字节”定位中,如果我想提取每一行的第 3、第 4、第 5 和第 8 个字节,该怎么办呢?

其实,-b选项支持形如“3-5”这样的写法,而且多个定位数字之间还可以用逗号隔开,还是让我们看看例子吧:

#在本篇文章中, 为了示例需要, 都使用了rocrocket这个账户名

[rocrocket@roclinux ~]$ who|cut -b 3-5,8
croe
croe
croe

但有一点要注意,cut 命令如果使用了-b选项,那么在执行此命令时,cut 会先把 -b 后面所有的定位数字按照从小到大的顺序排序,然后再依次提取。所以你可以随意颠倒定位数字的顺序。比如下面这个例子就可以证明这个结论:

[rocrocket@roclinux ~]$ who|cut -b 8,3-5
croe
croe
croe

看,即使我们把定位数字的顺序颠倒着写,所提取出的内容仍然是 croe,没有一丝丝的变动。

2.3 有关定位数字的小技巧

定位数字的设置,其实是非常灵活的,比如我们只限定最大定位数或最小定位数,都是可以的,来,看例子:

#who命令的输出内容, 我们继续以它为例子啦
[rocrocket@roclinux ~]$ who
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)

#-3就表示从头到第3个字节
[rocrocket@roclinux ~]$ who|cut -b -3
roc
roc
roc

#3-就表示从第3个字节到结尾
[rocrocket@roclinux ~]$ who|cut -b 3-
crocket :0           2016-03-29 11:07
crocket pts/0        2016-03-29 11:23 (:0.0)
crocket pts/1        2016-03-29 14:15 (:0.0)

想必你也看到了,-3 表示从第一个字节到第三个字节,而 3- 表示从第三个字节到行尾。如果你细心,可以看到这两种情况下,都包括了第三个字节“c”,说明 cut 对于区间采用的都是“闭区间”。

如果你是一位 GEEK,那么我们再为你介绍一个知识细节。如果我们执行 who|cut-b-3,3-,你觉得会如何呢?是否会出现两个 c 字符呢?

[rocrocket@roclinux ~]$ who|cut -b -3,3-
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)

答案是并没有出现连续两个重叠的 c 字符。

2.4 来说说字符定位吧

首先我们给大家举一个以字符为定位标志的最简单的例子。

下面的例子你应该似曾相识吧,提取第 3、第 4、第 5 和第 8 个字符:

[rocrocket@roclinux ~]$ who|cut -c 3-5,8
croe
croe
croe

不过,看着怎么和 -b 没有什么区别啊?莫非 -b 和 -c 作用一样?其实不然,看似相同,只是因为这个例子举得不好,who 输出的都是单字节字符,所以用 -b 和 -c 没有区别,如果我们提取中文,区别就看出来了。来,看看中文提取的情况:

#这是我们的文件内容, 就以它来举例
[rocrocket@roclinux ~]$ cat cut_ch.txt
星期一
星期二
星期三
星期四

#我们仍然采用字节定位来试试, 哇, 都是乱码
[rocrocket@roclinux ~]$ cut -b 3 cut_ch.txt
�
�
�
�

#我们改用字符定位, 看, 效果还不错, 乱码消失了
[rocrocket@roclinux ~]$ cut -c 3 cut_ch.txt
一
二
三
四

看到了吧,用 -c 则会以字符为单位来提取内容;而 -b 只会傻傻地以字节(8 位二进制位)来计算,输出的就是乱码了。

既然提到了这个知识点,就再补充一句,也是为 GEEK 准备的哦。当遇到多字节字符时,可以使用-n选项,-n选项用于告诉 cut 命令不要将多字节字符拆开,而是合并在一起显示。例子如下:

    #看好, 用-b是乱码
    [rocrocket@roclinux ~]$ cat cut_ch.txt |cut -b 2
    �
    �
    �
    �

#而我们知道三个字节组成一个汉字, 于是用了-n选项, 这回就可以正确显示了
[rocrocket@roclinux ~]$ cat cut_ch.txt |cut -nb 1,2,3
星
星
星
星

2.5 按域定位好奇怪

还记得我们在本文开头说的“cut 的三种定位方法”么,其中的第三个方法是“按域定位法”。那为什么会有“域”呢,是不是很奇怪?下面来为大家解释一下。

因为刚才提到的 -b 和 -c 只能在固定格式的文档中提取信息,而对于非固定格式的文档则束手无策。这时候“域”就派上用场啦。

下面的讲解内容是在假设你对 /etc/passwd 文件的内容和组织形式比较了解的情况下进行的,所以,如果你不了解 /etc/passwd 文件结构,建议一定要先补习一下相关的知识。

如果你观察过 /etc/passwd 文件,你会发现,它并不像 who 的输出信息那样具有固定的格式,而是比较零散的样子。但是,所幸的是,文件中有不少冒号,这些冒号在这个文件的每一行中都起到了非常重要的作用,它恰当地隔开了每一个有含义的项。这里“有含义的项”,我们就可以认为是“域”。

很幸运,cut 命令支持“按照冒号为间隔”进行内容提取,具体地说就是可以“设置间隔符”,并指定“提取第几个域”。

以 /etc/passwd 的前五行内容为例:

#展示/etc/passwd文件的前五行
[rocrocket@roclinux ~]$ cat /etc/passwd|head -n 5
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

#域派上用场啦, 厉害吧
[rocrocket@roclinux ~]$ cat /etc/passwd|head -n 5|cut -d : -f 1
root
bin
daemon
adm
lp

看到了吧,用 -d 来设置间隔符为冒号,然后用 -f 来设置我要提取的第一个域,再按回车,所有的用户名就都列出来了,有成就感吧。

当然,在设定 -f 选项时,也可以使用如“3-5”或者“4-”类似的格式:

#域也支持设置定位数字
[rocrocket@roclinux ~]$ cat /etc/passwd|head -n 5|cut -d : -f 1,3-5
root:0:0:root
bin:1:1:bin
daemon:2:2:daemon
adm:3:4:adm
lp:4:7:lp

#再举一个更复杂的例子
[rocrocket@roclinux ~]$ cat /etc/passwd|head -n 5|cut -d : -f 1,3-5,7
root:0:0:root:/bin/bash
bin:1:1:bin:/sbin/nologin
daemon:2:2:daemon:/sbin/nologin
adm:3:4:adm:/sbin/nologin
lp:4:7:lp:/sbin/nologin

#-2的作用应该还记得吧, 就是从开头到第2个域
[rocrocket@roclinux ~]$ cat /etc/passwd|head -n 5|cut -d : -f -2
root:x
bin:x
daemon:x
adm:x
lp:x

2.6 火眼金睛识别空格和制表符

有时候,文件内容中如果出现了制表符(Tab),是很难辨认的,现在我们就来介绍一个方法,让大家可以看出一段空格到底是由若干个空格组成的还是由一个制表符组成的,独门秘籍哦!

#这个文件内容中有一段较长的空白, 在space和finish之间, 是Tab还是空格呢?
[rocrocket@roclinux ~]$ cat tab_space.txt
this is tab finish.
this is several space      finish.

#求助sed命令
[rocrocket@roclinux ~]$ sed -n l tab_space.txt
this is tab\tfinish.$
this is several space\tfinish.$

看到了吧,如果是制表符(Tab),那么会显示为\t符号;如果是空格,就会原样显示。通过此方法我们就可以判断出是制表符还是空格了,这样也就可以更精准地去 cut 了。

注意,上面 sed-n 后面的字符是 L 的小写字母哦,不要看错。字母“l”、数字“1”、竖线“|”,这三个字符比制表符还难分辨。

2.7 教你把间隔符设置为空格或制表符

cut 的 -d 选项的默认间隔符就是制表符(Tab),所以当你就是要使用制表符的时候,完全可以省略 -d 选项,而直接用 -f 选项来指定域就可以了。是不是幸福来得太突然了呢。

而如果你想将间隔符设置为空格,那么就按下面的例子来做就好了,其实就是用两个单引号将一个空格括起来:

[rocrocket@roclinux ~]$ cat tab_space.txt |cut -d ' ' -f 1
this
this

注意,两个单引号之间真的要有一个空格哦,不能偷懒。

而且,你只能在 -d 后面设置一个空格,不能设置多个空格,因为 cut 只允许间隔符是一个字符。

#看看写两个空格的后果
[rocrocket@roclinux ~]$ cat tab_space.txt |cut -d '  ' -f 1
cut: the delimiter must be a single character
Try 'cut --help' for more information.

正好借着这个例子,让你说出 cut 命令的一个不足之处,你会如何回答呢?对,其中一个不足就是不善于处理“多空格”情况。

如果文件里面的某些域是由若干个空格来间隔的,那么用 cut 就有点麻烦了,因为 cut 只擅长处理“以一个字符间隔”的文本内容。

2.7 奇怪的重复现象

我们经常会将 ps 命令和 cut 命令配合在一起使用,但总是会出现重复的行,非常诡异,这是怎么回事呢?

我们一起来看情景重现。将 ps 命令和 cut 命令配合使用,并提取输出内容中每行的第3个字符:

#这是ps命令的输出, 注意输出内容中, 第一行前面有2个空格, 第二、三行前面有1个空格

[rocrocket@roclinux ~]$ ps
  PID TTY          TIME CMD
2977 pts/0    00:00:00 bash
5032 pts/0    00:00:00 ps

#ps命令和cut命令配合在一起使用
[rocrocket@roclinux ~]$ ps|cut -b3
P
9
0
0

看,最后的 0 重复了两次!!而且,我也试过 ps ef 或 ps aux 均有此问题。

而当 cut 和其他命令配合时,都没有此类问题,比如 cut 和 who 配合,输出就很正常:

#这是who的输出
[rocrocket@roclinux ~]$ who
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)

#这是who命令和cut命令配合的输出
[rocrocket@roclinux ~]$ who|cut -b3
c
c
c

悬念就营造到这里啦,我们现在就来揭开这一神秘现象的面纱。

这个看似怪异的令人百思不得其解的现象,其实是这样造成的:如果你对管道原理比较熟悉的话,你会知道 ps|cut 组合命令其实会产生两个进程,即 ps 进程和 cut 进程。而当 ps 进程执行时,也会列出 cut 命令对应的进程的信息,并且将所有进程的信息都通过管道输出给 cut 进程。所以,cut 截取后,就多出了一行,之所以会重复上一行内容,是由于我们恰巧取到了和上一行内容相同的字符而已。

如果还是不太明白的话,我们就来测试下执行 ps 和 ps|cat 的输出,你或许就知道原因了。

#这是ps的输出
[rocrocket@roclinux ~]$ ps
  PID TTY          TIME CMD
29208 pts/2    00:00:00 bash
55211 pts/2    00:00:00 ps

#这是ps|cat的输出, 可以看到最后出现了cat进程那一行
[rocrocket@roclinux ~]$ ps | cat
  PID TTY          TIME CMD
29208 pts/2    00:00:00 bash
55212 pts/2    00:00:00 ps
55213 pts/2    00:00:00 cat

看到了吧,ps|cat 会导致输出多了一行,同理,你应该就知道为什么 ps|cut 会多一行了吧!

三、讨论

1.1 将 PATH 变量取出,我要找出第五个路径。

[root@www ~]# echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/X11R6/bin:/usr/games:
# 1 | 2 | 3 | 4 | 5 | 6 | 7

[root@www ~]# echo $PATH | cut -d ':' -f 5
# 如同上面的数字显示,我们是以『 : 』作为分隔,因此会出现 /usr/local/bin
# 那么如果想要列出第 3 与第 5 呢?,就是这样:
[root@www ~]# echo $PATH | cut -d ':' -f 3,5

1.2 将 export 输出的讯息,取得第 12 字符以后的所有字符串

[root@www ~]# export
declare -x HISTSIZE="1000"
declare -x INPUTRC="/etc/inputrc"
declare -x KDEDIR="/usr"
declare -x LANG="zh_TW.big5"
.....(其他省略).....
# 注意看,每个数据都是排列整齐的输出!如果我们不想要『 declare -x 』时,
# 就得这么做:

[root@www ~]# export | cut -c 12-
HISTSIZE="1000"
INPUTRC="/etc/inputrc"
KDEDIR="/usr"
LANG="zh_TW.big5"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!

1.3 用 last 将显示的登入者的信息中,仅留下用户大名

[root@www ~]# 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 可以输出『账号/终端机/来源/日期时间』的数据,并且是排列整齐的

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

可以用sed ’s/ * / /‘来将很多空格变为一个

也可以这样;

好在我们有 tr 命令。使用 -s 参数,可以逐行地将连续的字符 unique 成单独的一个字符。

\$ who | tr -s ' ' | cut -d ' ' -f 1,3,4
Liam 2016-11-08 00:07
Liam 2016-11-08 00:23
Liam 2016-11-08 00:15

但是如果分隔符是tab的话,就只有用awk罗,cut就无能为力了。 cut 主要的用途在于将『同一行里面的数据进行分解!』最常使用在分析一些数据或文字数据的时候! 这是因为有时候我们会以某些字符当作分割的参数,然后来将数据加以切割,以取得我们所需要的数据。 鸟哥也很常使用这个功能呢!尤其是在分析 log 档案的时候!不过,cut 在处理多空格相连的数据时,可能会比较吃力一点。

参考资料

个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn

Sam avatar
About Sam
专注生物信息 专注转化医学