Skip to content

字符处理

管道

说起“管道”,很容易让人想起现实生活中使用的水管、输气管等,它们的作用在于运输气体或液体等物质,有了管道,会让我们方便很多。
在 Linux 中也存在着管道,它是一个固定大小的缓冲区,该缓冲区的大小为 1 页,即 4K 字节。
管道是一种使用非常频繁的通信机制,我们可以用管道符“|”来连接进程,由管道连接起来的进程可以自动运行,如同有一个数据流一样,所以管道表现为输入输出重定向的一种方法,它可以把一个命令的输出内容当作下一个命令的输入内容,两个命令之间只需要使用管道符连接即可。

举个例子,如果想要看一下/etc/init.d目录下文件的详细信息,可以使用ls -l /etc/init.d命令,不过这可能会出现因输出内容过多而造成翻屏的情况,这样一来,先输出的内容在屏幕上就看不到了。
其实这里就可以利用管道功能,将命令的输出使用 more 程序一页一页地显示出来

1
ls -l /etc/init.d | more

可以看出,通过管道,使ls -l /etc/init.d命令输出的内容作为下一个命令more的输入,这样就可以方便地查看输出内容了。

使用 grep 搜索文本

grep 是 Linux 下非常强大的基于行的文本搜索工具,使用该工具时,如果匹配到相关信息就会打印出符合条件的所有行。
下面列出了该命令常用的参数:

1
2
3
4
5
grep [-ivnc] 需要匹配的字符 文件名
-i: 不区分大小写     
-c: 统计包含匹配的行数     
-n: 输出行号    
-v: 反向匹配

为演示 grep 的用法,这里首先创建一个文件,文件名和文件内容如下:

1
2
3
4
➜  Desktop cat tomAndJerry.txt
The cat's name is Tom, what's the mouse's name?
The mouse's NAME is Jerry
They are good friends

下面要找出含有 name 的行:

1
2
➜  Desktop grep 'name' tomAndJerry.txt
The cat's name is Tom, what's the mouse's name?

打印出含有 name 的行的行编号:

1
2
➜  Desktop grep -n 'name' tomAndJerry.txt
1:The cat's name is Tom, what's the mouse's name?

由于 grep 区分大小写,所以虽然第二行中含有大写的 NAME,但是也不会匹配到。
如果希望忽略大小写,可以加上 -i 参数。

1
2
3
➜  Desktop grep -i 'name' tomAndJerry.txt
The cat's name is Tom, what's the mouse's name?
The mouse's NAME is Jerry

如果想知道文件中一共有多少包含 name 的行,可以使用下面的命令。
注意到第二条命令和第一条命令只有一个参数的差别,但是输出的结果却是不一样的。

1
2
3
4
➜  Desktop grep -c 'name' tomAndJerry.txt
1
➜  Desktop grep -ci 'name' tomAndJerry.txt
2

如果想打印出文件中不包含 name 的行,可以使用 grep 的反选参数 -v。

1
2
3
4
5
➜  Desktop grep -v 'name' tomAndJerry.txt
The mouse's NAME is Jerry
They are good friends
➜  Desktop grep -vi 'name' tomAndJerry.txt
They are good friends

以上命令都可以使用 cat 命令 + 管道符改写。比如上一个命令可以这样改写:

1
2
➜  Desktop cat tomAndJerry.txt | grep -vi 'name'
They are good friends

使用 sort 排序

很多情况下需要对无序的数据进行排序,这时就要用到 sort 排序了。
下面列出了该命令常用的参数:

1
2
3
4
5
sort [-ntkr] 文件名
-n: 采取数字排序    
-t: 指定分割符    
-k: 指定第几列      
-r: 反向排序

为演示 sort 的用法,这里首先创建一个文件,文件名和文件内容如下:

1
2
3
4
5
6
b:3
c:2
a:4
e:5
d:1
f:11

下面对内容进行排序:

1
2
3
4
5
6
7
➜  Desktop cat sort.txt | sort
a:4
b:3
c:2
d:1
e:5
f:11

直接排序时,默认按照每行的第一个字符进行排序,下面表示对输出内容进行反向排序:

1
2
3
4
5
6
7
➜  Desktop cat sort.txt | sort -r
f:11
e:5
d:1
c:2
b:3
a:4

可观察到,sort.txt 文件具有一个特点,第一个字符是字母,第三个字符是数字,中间用冒号隔开。
这样就可以用 -t 指定分隔符,并用 -k 指定用于排序的列了。

1
2
3
4
5
6
7
➜  Desktop cat sort.txt | sort -t ":" -k 2
d:1
f:11
c:2
b:3
a:4
e:5

在上面的命令中,当前的排序是按照以冒号隔开的第二部分进行的,不过第二行是f:11,这一行不应该在最后一行吗?
因为 11 是最大的。但其实命令的输出并不是错误的,因为按照排序的方式,只会看第一个字符,而 11 第一个字符是 1,按照字符来排序那它确实比 2 小。
如果想要指定按照“数字”的方式进行排序,则需要加上 -n 参数

1
2
3
4
5
6
7
➜  Desktop cat sort.txt | sort -t ":" -k 2 -n
d:1
c:2
b:3
a:4
e:5
f:11

使用 uniq 删除重复内容

如果文件(或标准输出)中有多行完全相同的内容,我们很自然希望能删除重复的行,同时还可以统计出完全相同的行出现的总次数,uniq 命令就能帮助解决这个问题。下面列出了该命令常用的参数:

1
2
3
uniq [-ic]
-i: 忽略大小写    
-c: 计算重复行数

为演示 uniq 的用法,这里首先创建一个文件,文件名和内容如下:

1
2
3
4
5
➜  Desktop cat uniq.txt
abc
123
abc
123

需要说明的是,uniq 一般都需要和 sort 命令一起使用,也就是先将文件使用 sort 进行排序(这样重复的内容就能显示在连续的几行中),然后再使用 uniq 删除掉重复的内容(uniq 的作用就在于删除连续的完全一致的行)。

观察一下以下两次命令的输出,第一次直接 cat 输出文件,然后使用 uniq 命令,输出的内容居然和原文件 uniq.txt 的内容是一样的,这是因为 uniq 命令只会对比相邻的行,如果有连续相同的若干行则删除重复内容,仅输出一行。
如果相同的行非连续,则 uniq 命令不具备删除效果。
第二次则在使用 sort 排序后再使用 uniq 命令,这时就达到了预期的效果。

1
2
3
4
5
6
7
8
➜  Desktop cat uniq.txt | uniq
abc
123
abc
123
➜  Desktop cat uniq.txt | sort | uniq
123
abc

使用 -c 参数就会在每行前面打印出该行重复的次数

1
2
3
➜  Desktop cat uniq.txt | sort | uniq -c
   2 123
   2 abc

使用 cut 截取文本

顾名思义,cut 就是截取的意思,它能处理的对象是“一行”文本,可从中选取出用户所需要的部分。
在有特定的分隔符时,可以指定分隔符,然后打印出以分隔符隔开的具体某一列或某几列,这里 cut 的用法如下:

1
cut -f 指定的列 -d 分隔符

举个例子,在文件/etc/passwd中,每行都是使用 6 个冒号隔开的 7 列文本,那么很容易使用 cut 的这个功能来提取出特定的细腻。
比如说我们需要打印出系统中的所有用户:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
➜  Desktop cat /etc/passwd | cut -f 1 -d ":"
nobody
root
daemon
_uucp
_taskgated
_networkd
_installassistant
_lp
_postfix
...

或者想同时打印出用户和这个用户的家目录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
➜  ~ cat /etc/passwd | cut -f 1,6 -d ":"
root:/root
bin:/bin
daemon:/sbin
adm:/var/adm
lp:/var/spool/lpd
sync:/sbin
shutdown:/sbin
halt:/sbin
mail:/var/spool/mail
operator:/root
games:/usr/games
ftp:/var/ftp
nobody:/
systemd-bus-proxy:/
systemd-network:/
dbus:/
polkitd:/
tss:/dev/null
sshd:/var/empty/sshd
postfix:/var/spool/postfix
chrony:/var/lib/chrony
ntp:/etc/ntp
nscd:/
tcpdump:/
admin:/home/admin
nginx:/var/cache/nginx

如果还想同时打印出每位用户的登录 shell:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
➜  ~ cat /etc/passwd | cut -f 1,6-7 -d ":"
root:/root:/bin/zsh
bin:/bin:/sbin/nologin
daemon:/sbin:/sbin/nologin
adm:/var/adm:/sbin/nologin
lp:/var/spool/lpd:/sbin/nologin
sync:/sbin:/bin/sync
shutdown:/sbin:/sbin/shutdown
halt:/sbin:/sbin/halt
mail:/var/spool/mail:/sbin/nologin
operator:/root:/sbin/nologin
games:/usr/games:/sbin/nologin
ftp:/var/ftp:/sbin/nologin
nobody:/:/sbin/nologin
systemd-bus-proxy:/:/sbin/nologin
systemd-network:/:/sbin/nologin
dbus:/:/sbin/nologin
polkitd:/:/sbin/nologin
tss:/dev/null:/sbin/nologin
sshd:/var/empty/sshd:/sbin/nologin
postfix:/var/spool/postfix:/sbin/nologin
chrony:/var/lib/chrony:/sbin/nologin
ntp:/etc/ntp:/sbin/nologin
nscd:/:/sbin/nologin
tcpdump:/:/sbin/nologin
admin:/home/admin:/bin/bash
nginx:/var/cache/nginx:/sbin/nologin

以上 cut 使用的场景是在处理的行中有特定分隔符的时候,但如果要处理的行是没有分隔符的,那是不是 cut 就没有用武之地了?
答案是否定的,cut 还可以打印指定的字符,这时候 cut 的用法如下:

1
cut -c 指定的列

继续使用/etc/passwd为例子,假设想要打印出每行第 1~5 个字符,以及 7~10 个字符的内容,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
➜  ~ cat /etc/passwd | cut -c 1-5,7-10
root::0:0
bin:x1:1:
daemo:x:2
adm:x3:4:
lp:x::7:l
sync::5:0
shutdwn:x
halt::7:0
mail::8:1
operaor:x
gamesx:12
ftp:x14:5
nobod:x:9
systed-bu
systed-ne
dbus::81:
polkid:x:
tss:x59:5
sshd::74:
postfx:x:
chron:x:9
ntp:x38:3
nscd::28:
tcpdup:x:
adminx:10
nginxx:99

使用 tr 做文本转换

tr 命令比较简单,其主要作用在于文本转换或删除。
这里假设要把文件/etc/passwd中的小写字母转换为大写字母,然后再尝试删除文本中的冒号,如下所示:

1
2
3
4
5
6
7
8
➜  ~ cat test.txt
acDx:55566xyzOk
➜  ~ cat test.txt | tr '[a-z]' '[A-Z]'
ACDX:55566XYZOK
➜  ~ cat test.txt
acDx:55566xyzOk
➜  ~ cat test.txt | tr -d ':'
acDx55566xyzOk

使用 paste 做文本合并

paste 的作用在于将文件按照行进行合并,中间使用 tab 隔开。
假设有两个文件分别为 a.txt、b.txt,下面使用 paste 命令来合并文件,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
➜  ~ cat a.txt
1
2
3
➜  ~ cat b.txt
a
b
c
➜  ~ paste a.txt b.txt
1   a
2   b
3   c

也可以使用 -d 指定在合并文件时行间的分隔符

1
2
3
4
➜  ~ paste -d : a.txt b.txt
1:a
2:b
3:c

使用 split 分割大文件

早些前,“文件分割”这 4 个字还是比较流行的,当时受限制于移动存储设备的限制,大文件的转移往往需要通过分割成小文件分别存储来实现,之后会再使用合并的方式还原成原始文件。
虽然随着现代移动存储、网络存储的发展,分割大文件的做法已经不那么流行了,但是了解以下还是必要的。
在 Linux 下使用 split 命令来实现文件的分割,支持按照行数分割和按照大小分割这两种模式。
要说明的是,二进制文件因为没有“行”的概念,所以二进制文件无法使用行分割,而只能按照文件大小进行分割。
相关命令如下所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
假设有一个 512 MB 的大文件    
按照行进行分割,-l 参数指定每 500 行作为一个小文件   

split -l 500 big-file.txt small_file_

分割完成后,当前目录下会生成很多小文件

ls small_file_*
small_file_aa small_file_ab small_file ac small_file_ad....

如果文件是二进制的,则之后按照文件大小分割   

split -b 64m big_bin small_bin_

分割完成后,当前目录下会生成很多大小为 64 MB 的文件 

xargs 命令

xargs 是给命令传递参数的一个过滤器,也是组合多个命令的一个工具。
xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据。
xargs 也可以将单行或多行文本输入转换为其他格式,例如多行变单行,单行变多行。
xargs 默认的命令是 echo,这意味着通过管道传递给 xargs 的输入将会包含换行和空白,不过通过 xargs 的处理,换行和空白将被空格取代。
xargs 是一个强有力的命令,它能够捕获一个命令的输出,然后传递给另外一个命令。

之所以能用到这个命令,关键是由于很多命令不支持|管道来传递参数,而日常工作中有有这个必要,所以就有了 xargs 命令,例如:

1
2
find /sbin -perm +700 |ls -l       #这个命令是错误的
find /sbin -perm +700 |xargs ls -l   #这样才是正确的

xargs 一般是和管道一起使用。

1
somecommand |xargs -item  command

批量删除docker镜像为none的方法

docker images | grep none | awk '{print $3}' | xargs docker rmi