【Linux进阶】使用grep、find、sed以及awk进行文本操作
使用grep、find、sed以及awk进行文本操作
一、元字符
详情请见匹配规则。
二、grep
命令
下面通过实战演示的方式介绍grep命令的常见用法,为此首先我们需要一个示例文本文件,这里使用CentOS操作系统/etc
目录下的passwd
文件:
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description: CentOS Linux release 8.2.2004 (Core)
Release: 8.2.2004
Codename: Core
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# cat /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
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
systemd-coredump:x:999:997:systemd Core Dumper:/:/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
polkitd:x:998:996:User for polkitd:/:/sbin/nologin
libstoragemgmt:x:997:995:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
unbound:x:996:993:Unbound DNS resolver:/etc/unbound:/sbin/nologin
setroubleshoot:x:995:991::/var/lib/setroubleshoot:/sbin/nologin
cockpit-ws:x:994:990:User for cockpit web service:/nonexisting:/sbin/nologin
cockpit-wsinstance:x:993:989:User for cockpit-ws instances:/nonexisting:/sbin/nologin
sssd:x:992:988:User for sssd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
chrony:x:991:987::/var/lib/chrony:/sbin/nologin
rngd:x:990:986:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
nscd:x:28:28:NSCD Daemon:/:/sbin/nologin
1. 过滤出包含某字符串的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep "Kernel" /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
# 忽略大小写
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i "kernel" /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
# 同时输出行号
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -n "kernel" /etc/passwd
13:nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
2. 过滤出以某字符串开头(结尾)的行
# 过滤出以sshd开头的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep "^sshd" /etc/passwd
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
# 过滤出以shutdown结尾的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep "shutdown$" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
3. 过滤出包含某字符串及其相邻的行
# 将包含Kernel的行以及其下边的一行过滤出来
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -A1 "kernel" /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
# 将包含Kernel的行以及其上边的一行过滤出来
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -B1 "kernel" /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
# 将包含Kernel的行以及其上边和下边的一行过滤出来
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -C1 "kernel" /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
4. 过滤出不包含某关键字的行
# 过滤出不包含nologin的行,并输出其行号
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -v -n "nologin" /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
6:sync:x:5:0:sync:/sbin:/bin/sync
7:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8:halt:x:7:0:halt:/sbin:/sbin/halt
5. 过滤出包含多个字符串中任意一个的行
# 过滤出包含shutdown或kernel的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -e "shutdown" -e "kernel" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
# 上述命令等价于下列命令
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -E "shutdown|kernel" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
6. 查看目录中包含某字符串的所有文件
# 递归查询/etc/目录下包含"nobody"在内的所有文件
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -r -n "nobody" /etc/
/etc/ssh/sshd_config:60:#AuthorizedKeysCommandUser nobody
/etc/aliases:29:nobody: root
/etc/aliases:65:nfsnobody: root
/etc/group-:24:nobody:x:65534:
/etc/gshadow-:24:nobody:::
/etc/passwd-:13:nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
/etc/shadow-:13:nobody:*:18358:0:99999:7:::
/etc/group:24:nobody:x:65534:
/etc/gshadow:24:nobody:::
/etc/passwd:13:nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
/etc/shadow:13:nobody:*:18358:0:99999:7:::
/etc/idmapd.conf:43:#Nobody-User = nobody
/etc/idmapd.conf:44:#Nobody-Group = nobody
/etc/pinforc:93:SAFE-USER=nobody
/etc/pinforc:94:SAFE-GROUP=nobody
三、find
命令
1. 按文件名查找
- 查找当前目录下所有
c
语言文件:
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# pwd
/usr/local/aegis/PythonLoader/lib/python2.7
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# find ./ -name "*.c"
./config/config.c
./distutils/tests/xxmodule.c
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# find ./ -name *.c
./config/config.c
./distutils/tests/xxmodule.c
- 在当前目录下,查找大写字母开头的
txt
文件:
[root@iZbp1gjysfmcbcojeshiw7Z lib2to3]# pwd
/usr/lib64/python3.6/lib2to3
[root@iZbp1gjysfmcbcojeshiw7Z lib2to3]# find ./ -name "[A-Z]*.txt"
./Grammar.txt
./PatternGrammar.txt
[root@iZbp1gjysfmcbcojeshiw7Z lib2to3]# find ./ -name "[A-Z]*.txt" -print
./Grammar.txt
./PatternGrammar.txt
其中 -print
表示行为(即 action
),默认指定; find
命令另一个常用的行为是 -prune
,表示不搜索某一个目录。
- 在当前目录下,查找不是以
fix
开头的.py
文件:
[root@iZbp14vmgrtj1265z7za9nZ fixers]# pwd
/usr/local/aegis/PythonLoader/lib/python2.7/lib2to3/tests/data/fixers
[root@iZbp14vmgrtj1265z7za9nZ fixers]#
[root@iZbp14vmgrtj1265z7za9nZ fixers]# find ./ -name "*.py" -print
./parrot_example.py
./myfixes/__init__.py
./myfixes/fix_first.py
./myfixes/fix_preorder.py
./myfixes/fix_explicit.py
./myfixes/fix_last.py
./myfixes/fix_parrot.py
./no_fixer_cls.py
./bad_order.py
[root@iZbp14vmgrtj1265z7za9nZ fixers]# find ./ -name "fix*" -prune -o -name "*.py" -print
./parrot_example.py
./myfixes/__init__.py
./no_fixer_cls.py
./bad_order.py
- 在当前目录下,查找不在
myfixes
目录下的.py
文件:
[root@iZbp14vmgrtj1265z7za9nZ fixers]# find ./ -path "./myfixes" -prune -o -name "*.py" -print
./parrot_example.py
./no_fixer_cls.py
./bad_order.py
需要注意的是,当希望按照文件名称搜索时忽略大小写,则应该使用选项 -iname
而不是 -name
。
2. 按文件类型查找
- 在当前目录下,查找软连接文件,且指定最大递归深度为1:
[root@iZbp14vmgrtj1265z7za9nZ /]# pwd
/
[root@iZbp14vmgrtj1265z7za9nZ /]# find ./ -maxdepth 1 -type l -print
./bin
./sbin
./lib
./lib64
- 在当前目录下,查找
log
结尾的普通文件,f
表示普通文件类型:
[root@iZbp14vmgrtj1265z7za9nZ /]# find ./ -type f -a -name "*.log"
3. 按文件大小查找
- 查找大小超过
64k
的文件:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -size +64k -print
./moduli
4. 按文件时间查找
- 查找当前目录下:
- 修改时间在48小时以上的普通文件;
- 修改时间在72小时以上的普通文件;
- 修改时间在24小时以内的普通文件;
- 修改时间在24小时以上,48小时以内的普通文件。
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime +1 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime +2 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime -1 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime -2 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime 1 -type f -print
实际上,对于选项 -atime
和 -ctime
也有类似的语法。
- 查找比
ssh_host_rsa_key
新或旧的文件
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -newer "ssh_host_rsa_key" -type f -print
./sshd_config
./ssh_host_dsa_key
./ssh_host_dsa_key.pub
./ssh_host_ecdsa_key
./ssh_host_ecdsa_key.pub
./ssh_host_ed25519_key
./ssh_host_ed25519_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ ! -newer "ssh_host_rsa_key" -type f -print
./moduli
./ssh_config
./ssh_config.d/05-redhat.conf
./ssh_host_rsa_key
./ssh_host_rsa_key.pub
5. 按文件权限查找
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# ll
total 608
-rw-r--r--. 1 root root 577388 Feb 5 2020 moduli
-rw-r--r--. 1 root root 1716 Feb 5 2020 ssh_config
drwxr-xr-x. 2 root root 28 Nov 20 2020 ssh_config.d
-rw------- 1 root root 4296 Jun 1 14:27 sshd_config
-rw------- 1 root root 1401 Jun 1 14:26 ssh_host_dsa_key
-rw-r--r-- 1 root root 618 Jun 1 14:26 ssh_host_dsa_key.pub
-rw------- 1 root root 525 Jun 1 14:26 ssh_host_ecdsa_key
-rw-r--r-- 1 root root 190 Jun 1 14:26 ssh_host_ecdsa_key.pub
-rw------- 1 root root 419 Jun 1 14:26 ssh_host_ed25519_key
-rw-r--r-- 1 root root 110 Jun 1 14:26 ssh_host_ed25519_key.pub
-rw------- 1 root root 2622 Jun 1 14:26 ssh_host_rsa_key
-rw-r--r-- 1 root root 582 Jun 1 14:26 ssh_host_rsa_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type d -a -perm 755
.
./ssh_config.d
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type f -perm 644
./moduli
./ssh_config
./ssh_config.d/05-redhat.conf
./ssh_host_rsa_key.pub
./ssh_host_dsa_key.pub
./ssh_host_ecdsa_key.pub
./ssh_host_ed25519_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type f -perm 600
./sshd_config
./ssh_host_rsa_key
./ssh_host_dsa_key
./ssh_host_ecdsa_key
./ssh_host_ed25519_key
- 查找当前目录下所有用户都有执行权限的文件:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -perm -111
./
./ssh_config.d
- 查找当前目录下至少一个用户有写权限的文件:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# ll | sed \'1d\' | nl
1 -rw-r--r--. 1 root root 577388 Feb 5 2020 moduli
2 -rw-r--r--. 1 root root 1716 Feb 5 2020 ssh_config
3 drwxr-xr-x. 2 root root 28 Nov 20 2020 ssh_config.d
4 -rw------- 1 root root 4296 Jun 1 14:27 sshd_config
5 -rw------- 1 root root 1401 Jun 1 14:26 ssh_host_dsa_key
6 -rw-r--r-- 1 root root 618 Jun 1 14:26 ssh_host_dsa_key.pub
7 -rw------- 1 root root 525 Jun 1 14:26 ssh_host_ecdsa_key
8 -rw-r--r-- 1 root root 190 Jun 1 14:26 ssh_host_ecdsa_key.pub
9 -rw------- 1 root root 419 Jun 1 14:26 ssh_host_ed25519_key
10 -rw-r--r-- 1 root root 110 Jun 1 14:26 ssh_host_ed25519_key.pub
11 -rw------- 1 root root 2622 Jun 1 14:26 ssh_host_rsa_key
12 -rw-r--r-- 1 root root 582 Jun 1 14:26 ssh_host_rsa_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -maxdepth 1 -perm /222 | nl
1 ./
2 ./moduli
3 ./ssh_config
4 ./ssh_config.d
5 ./sshd_config
6 ./ssh_host_rsa_key
7 ./ssh_host_rsa_key.pub
8 ./ssh_host_dsa_key
9 ./ssh_host_dsa_key.pub
10 ./ssh_host_ecdsa_key
11 ./ssh_host_ecdsa_key.pub
12 ./ssh_host_ed25519_key
13 ./ssh_host_ed25519_key.pub
6. 按组合条件查找
- 查找当前目录下,所属用户为
root
的目录:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type d -a -user root -print
.
./ssh_config.d
- 查找当前目录下的非普通文件:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -not -type f
./
./ssh_config.d
[root@iZbp14vmgrtj1265z7za9nZ ssh]#
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ ! -type f
./
./ssh_config.d
- 查找当前目录下的非空文件:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . ! -empty
7. 查找出文件后做相应处理
通过 find
命令查找出某个文件之后,我们可以继续使用 -exec
或 -ok
,对其进行进一步的处理,如:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -name "*.pub" -exec ls -alh {} \;
-rw-r--r-- 1 root root 582 Jun 1 14:26 ./ssh_host_rsa_key.pub
-rw-r--r-- 1 root root 618 Jun 1 14:26 ./ssh_host_dsa_key.pub
-rw-r--r-- 1 root root 190 Jun 1 14:26 ./ssh_host_ecdsa_key.pub
-rw-r--r-- 1 root root 110 Jun 1 14:26 ./ssh_host_ed25519_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -name "*.pub" -ok ls -alh {} \;
< ls ... ./ssh_host_rsa_key.pub > ? n
< ls ... ./ssh_host_dsa_key.pub > ? n
< ls ... ./ssh_host_ecdsa_key.pub > ? y
-rw-r--r-- 1 root root 190 Jun 1 14:26 ./ssh_host_ecdsa_key.pub
< ls ... ./ssh_host_ed25519_key.pub > ? y
-rw-r--r-- 1 root root 110 Jun 1 14:26 ./ssh_host_ed25519_key.pub
由上述可知: -ok
与 -exec
功能一样,只是前者操作时会提示用户确认,仅此而已。当然,在生产环境上,还是推荐使用 -ok
。
在这里说明一下{}
和\;
:
-
{}
其实它就是一个占位符,在find
命令的执行过程中会不断地替换成当前找到的文件,相当于ls -l 找到的文件
; -
\;
是-exec
的命令结束标记,因为规定-exec
后面的命令必须以;
结束,但;
在 shell 中有特殊含义,必须要转义,所以写成\;
。
四、sed
命令
1. sed
简介
sed
全名叫stream editor
,即流编辑器,其使用方式与交互式文本编辑器截然不同的。 在使用交互式文本编辑器如vim
时,用户使用键盘通过交互的方式插入、删除、替换文本;sed
这样的流编辑器使用提前编写好的一系列命令来编辑文本,这些命令在编辑器处理文本之前就需要写好;- 因为这样的使用方式,
sed
尤其适用于某些情况下的文本编辑,如:希望通过自动化的方式批量修改大量的待编辑文本。
2. 工作流程
针对 sed
这样的流编辑器,其工作流程大致如下:
- 从输入中一次读入一行数据;
- 将该行数据依次和预先写好的命令进行匹配;
- 当命令匹配成功时,对流中的数据进行相应修改;
- 将修改后的数据输出至
STDOUT
。
3. 基本语法
4. 案例实战
首先,为了便于后面演示,通过下列命令创建一个测试文本 sed_demo.txt
:
[root@iZbp15spmmi74px0lk8l6nZ ~]# head -n5 /etc/passwd > sed_demo.txt
[root@iZbp15spmmi74px0lk8l6nZ ~]# cat -n sed_demo.txt
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
定址
默认情况下,sed
会对文本的每一行都执行读取、匹配、操作、输出这些步骤,但很多时候我们只想对部分行执行这些步骤,而定位期望处理的目标行就叫做定址,根据方式不同,又可分为数字定址和正则定址。
数字定址
数字定址顾名思义就是通过数字的方式确定文本要处理的行:
- 仅将第1行中的所有
root
替换为ROOT
:
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed \'1s/root/ROOT/g\' sed_demo.txt | nl
1 ROOT:x:0:0:ROOT:/ROOT:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 仅将第2-4行中的所有
sbin
替换为SBIN
:
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed \'2,4s/sbin/SBIN/g\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/SBIN/nologin
3 daemon:x:2:2:daemon:/SBIN:/SBIN/nologin
4 adm:x:3:4:adm:/var/adm:/SBIN/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 仅将从第2行开始,往下数3行,即2-5行中的所有
sbin
替换为SBIN
:
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed \'2,+3s/sbin/SBIN/g\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/SBIN/nologin
3 daemon:x:2:2:daemon:/SBIN:/SBIN/nologin
4 adm:x:3:4:adm:/var/adm:/SBIN/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/SBIN/nologin
- 将除第3行以外所有行中的
sbin
替换为SBIN
:
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed \'3!s/sbin/SBIN/g\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/SBIN/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/SBIN/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/SBIN/nologin
正则定址
- 仅将最后一行中的所有
sbin
替换为SBIN
:
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed \'$s/sbin/SBIN/g\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/SBIN/nologin
- 将匹配到以
daemon
开头的行到以adm
开头的行及其之间的所有行进行删除:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'/^daemon/,/^adm/d\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 删除文档中的所有空行:
[root@iZbp142l91zbbe0hqz89bgZ ~]# cat -n sed_demo.txt
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3
4
5 daemon:x:2:2:daemon:/sbin:/sbin/nologin
6
7 adm:x:3:4:adm:/var/adm:/sbin/nologin
8 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'/^$/d\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
数字定址和正则定址混用
- 匹配从第1行到以
adm
开头的行,并将匹配的行进行删除:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'1,/^adm/d\' sed_demo.txt | nl
1 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
基本子命令
追加行子命令 a
- 在所有行下方追加
/etc/passwd
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'a /etc/passwd\' sed_demo.txt
root:x:0:0:root:/root:/bin/bash
/etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
/etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
/etc/passwd
adm:x:3:4:adm:/var/adm:/sbin/nologin
/etc/passwd
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
/etc/passwd
- 仅在第1,2行之后追加
/etc/passwd
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'1,2a /etc/passwd\' sed_demo.txt
root:x:0:0:root:/root:/bin/bash
/etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
/etc/passwd
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
- 仅将第1行及其向下两行后追加
/etc/passwd
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'1,+2a /etc/passwd\' sed_demo.txt
root:x:0:0:root:/root:/bin/bash
/etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
/etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
/etc/passwd
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 仅在最后一行后追加
/etc/passwd
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'$a /etc/passwd\' sed_demo.txt
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
/etc/passwd
插入行子命令 i
子命令 i
和 a
用法基本一样,只不过 i
是在指定行上方插入指定的内容行:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'$i /etc/passwd\' sed_demo.txt
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
/etc/passwd
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
替换子命令 s
上面的案例中,通过定址指定的待操作行之后的 s
即表示替换,这也是 sed
最常用的一种功能:
其基本语法为:
sed
[
address
]
s /
pat
/
rep
/
flags
- 仅将每行匹配到的第一个
bin
替换为BIN
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/bin/BIN/\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/BIN/bash
2 BIN:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sBIN:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sBIN/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sBIN/nologin
- 将每行匹配到的所有
bin
替换为BIN
(其中g
代表global
即全局之意):
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/bin/BIN/g\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/BIN/bash
2 BIN:x:1:1:BIN:/BIN:/sBIN/nologin
3 daemon:x:2:2:daemon:/sBIN:/sBIN/nologin
4 adm:x:3:4:adm:/var/adm:/sBIN/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sBIN/nologin
- 将每行第2次匹配的
bin
替换为BIN
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/bin/BIN/2\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:BIN:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sBIN/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 将每行第2次出现到该行结束所有的
bin
替换为BIN
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/bin/BIN/2g\' sed_demo.txt | nl
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:BIN:/BIN:/sBIN/nologin
3 daemon:x:2:2:daemon:/sbin:/sBIN/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 在每一行的行首插入符号两个制表符:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/^/\t\t/g\' sed_demo.txt
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
- 在每一行的行尾插入
-------
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/$/-------/g\' sed_demo.txt
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-------
- 将2-3行的
sbin
替换为SBIN
,同时将第3行至最后一行的nologin
替换为NOLOGIN
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'2,3s/sbin/SBIN/g; 3,$s/nologin/NOLOGIN/g\' sed_demo.txt
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
替换行子命令 c
- 将1-3行替换为
/etc/passwd
:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'1,3c /etc/passwd\' sed_demo.txt
/etc/passwd
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
删除行子命令 d
- 删除1-3行:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'1,3d\' sed_demo.txt
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
设置行号子命令 =
- 仅为第1-2行设置行号:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'1,2=\' sed_demo.txt
1
root:x:0:0:root:/root:/bin/bash
2
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
其实就是将当前行的下一行内容也读进缓存区,一起做匹配和修改,需要注意的是:当前行的 \n
仍然保留。
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'=\' sed_demo.txt | sed \'N;s/\n/\t/\'
1 root:x:0:0:root:/root:/bin/bash
2 bin:x:1:1:bin:/bin:/sbin/nologin
3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
4 adm:x:3:4:adm:/var/adm:/sbin/nologin
5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
为便于理解上述命令,我们先看一下单独运行 sed \'=\' sed_demo.txt
的效果:
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed \'=\' sed_demo.txt
1
root:x:0:0:root:/root:/bin/bash
2
bin:x:1:1:bin:/bin:/sbin/nologin
3
daemon:x:2:2:daemon:/sbin:/sbin/nologin
4
adm:x:3:4:adm:/var/adm:/sbin/nologin
5
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
即行号会插入在在每一行的前一行,而后 sed \'N;s/\n/\t/\'
是:
- 先将行号和其下一行一起读取;
- 然后将换行符(
\n
)替换为制表符(\t
)。
忽略大小写子命令 i
- 将
i
和I
均替换为--I--
:
# 仅替换每一行的第一个i或I
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed \'s/nologin/NOLOGIN/\' sed_demo.txt | nl | sed \'s/i/--I--/i\'
1 root:x:0:0:root:/root:/b--I--n/bash
2 b--I--n:x:1:1:bin:/bin:/sbin/NOLOGIN
3 daemon:x:2:2:daemon:/sb--I--n:/sbin/NOLOGIN
4 adm:x:3:4:adm:/var/adm:/sb--I--n/NOLOGIN
5 lp:x:4:7:lp:/var/spool/lpd:/sb--I--n/NOLOGIN
# 替换
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed \'s/nologin/NOLOGIN/\' sed_demo.txt | nl | sed \'s/i/--I--/gi\'
1 root:x:0:0:root:/root:/b--I--n/bash
2 b--I--n:x:1:1:b--I--n:/b--I--n:/sb--I--n/NOLOG--I--N
3 daemon:x:2:2:daemon:/sb--I--n:/sb--I--n/NOLOG--I--N
4 adm:x:3:4:adm:/var/adm:/sb--I--n/NOLOG--I--N
5 lp:x:4:7:lp:/var/spool/lpd:/sb--I--n/NOLOG--I--N
基本选项
多个命令连接 -e
上述命令 sed \'2,3s/sbin/SBIN/g; 3,$s/nologin/NOLOGIN/g\' sed_demo.txt
等价于下列命令:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed -e \'2,3s/sbin/SBIN/g\' -e \'3,$s/nologin/NOLOGIN/g\' sed_demo.txt
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
下面命令仅打印出了发生替换的行 (关闭每行都打印的同时,指定 p
来打印仅发生替换的行):
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed -n \'s/nologin/NOLOGIN/p\' sed_demo.txt | nl
1 bin:x:1:1:bin:/bin:/sbin/NOLOGIN
2 daemon:x:2:2:daemon:/sbin:/sbin/NOLOGIN
3 adm:x:3:4:adm:/var/adm:/sbin/NOLOGIN
4 lp:x:4:7:lp:/var/spool/lpd:/sbin/NOLOGIN
修改源文件 -i
需要注意的是:至此, sed
修改匹配到的内容后,默认不会将修改保存到原文件,而是直接输出修改后模式空间(可理解为缓存)的内容到STDOUT
,如果要修改原文件需要指定 -i
选项。
支持扩展正则表达式 -r
或 -e
- 删除文件每行的第二个字符:
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed -r \'s/(.)(.)(.*)$/\1\3/\' sed_demo.txt
rot:x:0:0:root:/root:/bin/bash
bn:x:1:1:bin:/bin:/sbin/nologin
demon:x:2:2:daemon:/sbin:/sbin/nologin
am:x:3:4:adm:/var/adm:/sbin/nologin
l:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 交换每行的第一个字符和第二个字符:
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed -r \'s/(.)(.)(.*)$/\2\1\3/\' sed_demo.txt
orot:x:0:0:root:/root:/bin/bash
ibn:x:1:1:bin:/bin:/sbin/nologin
ademon:x:2:2:daemon:/sbin:/sbin/nologin
dam:x:3:4:adm:/var/adm:/sbin/nologin
pl:x:4:7:lp:/var/spool/lpd:/sbin/nologin
特殊变量
- 使用变量
&
可以代表匹配出的结果:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed \'s/nologin/"&"/g\' sed_demo.txt
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"
使用正则
- 去除下面文字中HTML标签(其中
[^>]*
表示非\'>\'
的字符出现0次或多次)
[root@iZbp142l91zbbe0hqz89bgZ ~]# echo \'<b>This</b> is what <span style="x">I</span> meant\' | sed \'s/<[^>]*>//g\'
This is what I meant
下面的正则中:
\([^,]\)
中的\
表示指定括号为逃逸字符,^
含义为非;\1
和\2
分别表示使用分组。
[root@iZbp142l91zbbe0hqz89bgZ ~]# echo "hello,123,world" | sed \'s/\([^,]\),.*,\(.*\)/\1=\2/\'
hello=world
sed -i \'s/upjas.bind.address=19.20.11.134/upjas.bind.address=\'`hostname -i`\'/g\' upjas_setenv.properties
五、awk
命令
本部分主要参考下列文章,仅记录以供个人学习参考:
1. awk
简介
上面介绍的sed
可以实现非交互式的字符串替换,grep
能够实现有效的过滤功能。与两者相比,awk
是一款强大的文本分析工具,尤其擅长对日志、csv
文件等格式化数据进行分析并生成报告。
该命令之所以叫awk
是因为其取了三位创始人Alfred Aho
,Peter Weinberger
和Brian Kernighan
的姓氏首字符。
2. awk
工作原理
awk
的命令格式如下:
awk \'BEGIN{ commands } pattern{ commands } END{ commands }\'
基于上述命令格式,awk
的工作流程可分为三个部分:
- 读输入文件之前执行的代码段(由
BEGIN
关键字标识);- 主循环执行输入文件的代码段;
- 读输入文件之后的代码段(由
END
关键字标识)。
下面的流程图详细描述出了awk
的工作流程:
- 通过关键字
BEGIN
执行BEGIN
块的内容,即BEGIN
后花括号{}
的内容;- 完成
BEGIN
块的执行,开始执行BODY
块;- 读入由
\n
换行符分割的记录,一条记录即为一行;- 将记录按指定的域分隔符划分为域(相当于数据库的字段取值),其中
$0
表示所有域(即一行内容),$1
表示第一个域,$n
表示第n
个域;- 依次执行各
BODY
块,pattern
部分匹配该行内容成功后,才会执行awk
命令中commands
部分的内容;- 循环读取并执行各行直到文件结束,完成
BODY
块执行;- 开始
END
块执行,END
块可以输出最终结果。
开始块BEGIN
开始块的语法格式如下:
BEGIN {awk-commands}
开始块就是在程序启动的时候执行的代码部分,并且它在整个过程中只执行一次。一般情况下,我们可以在开始块中初始化一些变量。
BEGIN
是awk
的关键字,因此它必须是大写的。
注意:开始块部分是可选的,你的程序可以没有开始块部分。
主体块BODY
主体部分的语法格式如下:
/pattern/ {awk-commands}
对于每一个输入的行都会执行一次主体部分的命令。
默认情况下,对于输入的每一行,awk
都会执行命令。但是,我们可以将其限定在指定的模式中。
注意:在主体块部分没有关键字存在。
结束块END
结束块的语法格式如下:
END {awk-commands}
结束块是在程序结束时执行的代码。END
也是awk
的关键字,它也必须大写。
注意:与开始块相似,结束块也是可选的。
3. awk
实战演示
为了演示方便,我们使用下列命令,先创建一个用于演示用的TXT文档:
[root@iZbp1ewxwj89u3zpajnxz4Z sys]# ls -l > /root/awk_demo.txt
[root@iZbp1ewxwj89u3zpajnxz4Z sys]# cd /root
[root@iZbp1ewxwj89u3zpajnxz4Z ~]# cat awk_demo.txt
drwxr-xr-x 2 root root 0 May 21 2021 block
drwxr-xr-x 35 root root 0 May 21 2021 bus
drwxr-xr-x 54 root root 0 May 21 2021 class
drwxr-xr-x 4 root root 0 May 21 2021 dev
drwxr-xr-x 15 root root 0 May 21 2021 devices
drwxr-xr-x 6 root root 0 May 21 2021 firmware
drwxr-xr-x 6 root root 0 May 21 2021 fs
drwxr-xr-x 2 root root 0 May 21 16:37 hypervisor
drwxr-xr-x 14 root root 0 May 21 2021 kernel
drwxr-xr-x 114 root root 0 May 21 2021 module
drwxr-xr-x 2 root root 0 May 21 16:37 power
输出
输出指定列
# 输出文档awk_demo.txt的第1,4,8列
[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk \'{print $1,$4,$8}\' awk_demo.txt
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 16:37
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 16:37
大括号里边的就是awk
的command
,只能被单引号包含,其中,$1..$N
表示第几列,$0
表示整个的行内容。
格式化输出
awk
还支持类型C语言printf
函数的格式化输出:
[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk \'{printf "%-20s %-8s %-10s %-8s\n",$1,$2,$3,$4}\' awk_demo.txt
drwxr-xr-x 2 root root
drwxr-xr-x 35 root root
drwxr-xr-x 54 root root
drwxr-xr-x 4 root root
drwxr-xr-x 15 root root
drwxr-xr-x 6 root root
drwxr-xr-x 6 root root
drwxr-xr-x 2 root root
drwxr-xr-x 14 root root
drwxr-xr-x 114 root root
drwxr-xr-x 2 root root
其中,和C语言类型,%s
表示字符串占位符,-20
表示列宽度为20
且左对齐。
输出过滤的行
仅输出第3列为root
且第8列为16:37
的行:
[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk \'$3 == "root" && $8 == "16:37" {print $0}\' awk_demo.txt
drwxr-xr-x 2 root root 0 May 21 16:37 hypervisor
drwxr-xr-x 2 root root 0 May 21 16:37 power
awk
支持各种比较运算符号!=
、>
、<
、>=
、<=
,其中$0
表示整行的所有内容。
使用内置变量
字段数NF
如下,awk
在读取文件时,按行读取,每一行的字段数(列数),赋值给内置变量NF
,打印出来的就是每行的字段总数:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{print "字段数:" NF}\' awk_demo.txt
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
字段数:9
如果只需要最后一列的数据,由于每一行的列数可能不一,最后一列无法指定固定的列数,此时就可以使用NF
来表示列数,\'{print $NF}\'
表示打印出等于总列数的那一列的数据。
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{print $NF}\' awk_demo.txt
block
bus
class
dev
devices
firmware
fs
hypervisor
kernel
module
power
记录数NR
和FNR
如下,打印出读取文件的行数,因为是按行读取,在应用场景中,行数可以等同于行号,用来输出对应行的行号:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{print "行号为:" NR}\' awk_demo.txt
行号为:1
行号为:2
行号为:3
行号为:4
行号为:5
行号为:6
行号为:7
行号为:8
行号为:9
行号为:10
行号为:11
NR
还可以用作判断输出,如下简单例子:
# 过滤第8列为"10:52"且行号大于9的记录,打印时仅打印行号、每条记录第一个字段、每条记录最后一个字段
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'$8 == "10:52" {if(NR>9)print NR,$1,$NF}\' awk_demo.txt
11 drwxr-xr-x power
FNR
也是读取文件的行数,但是和NR
不同的是,当读取的文件有两个或两个以上时,NR
读取完一个文件,行数继续增加,而FNR
重新从1开始记录:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{print "NR:" NR "\t\t" "FNR:" FNR}\' awk_demo.txt awk_demo.txt
NR:1 FNR:1
NR:2 FNR:2
NR:3 FNR:3
NR:4 FNR:4
NR:5 FNR:5
NR:6 FNR:6
NR:7 FNR:7
NR:8 FNR:8
NR:9 FNR:9
NR:10 FNR:10
NR:11 FNR:11
NR:12 FNR:1
NR:13 FNR:2
NR:14 FNR:3
NR:15 FNR:4
NR:16 FNR:5
NR:17 FNR:6
NR:18 FNR:7
NR:19 FNR:8
NR:20 FNR:9
NR:21 FNR:10
NR:22 FNR:11
输入字段分隔符FS
awk
中默认分隔一行各个字段的符号为空格,而实际的文本数据并不总是以空格为分隔符,我们可以通过FS
变量指定分隔符,为了后续演示方便,下面先创建一个以:
分隔的文档:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{ if (NR < 6) print $0 }\' /etc/passwd > /root/awk_sep_demo.txt
[root@iZbp15brp59m56cywazt3yZ ~]# cat awk_sep_demo.txt
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
如下所示,因为awk_sep_demo.txt
里面字母间是以:
来分割的,所以:
- 第一种写法,由于没有指定
FS
,awk
默认是以空格来分割,每一行所有内容都当作$1
来显示; - 第二个指定
FS
以:
分割,所以能按照我们期望的方式来打印。
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{print $1}\' awk_sep_demo.txt
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
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'BEGIN { FS = ":" } { print $1 }\' awk_sep_demo.txt
root
bin
daemon
adm
lp
实际上,通过选项-F
也可以实现类似的功能:
[root@iZbp15brp59m56cywazt3yZ ~]# awk -F \':\' \'{ print $1 }\' awk_sep_demo.txt
root
bin
daemon
adm
lp
再推广一下,如果想要一次性指定多个分隔符,如:
、,
和;
等,可以使用类似-F \'[;:,]\'
的格式。
输出字段分隔符OFS
输出字段分割符,默认为空格,实际需求可能要求输出是以若干个制表符分割,可以使用OFS
进行格式化输出:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'BEGIN { FS = ":"; OFS = "\t\t\t" } { print $1, $2, $3 }\' awk_sep_demo.txt
root x 0
bin x 1
daemon x 2
adm x 3
lp x 4
输入行分隔符RS
输入行分隔符RS
,用于判断输入部分的行的起始位置,默认是换行符,下面将其改为:
:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{if(NR == 1) print $0}\' awk_sep_demo.txt | awk \'BEGIN { RS = ":" } { print $0 }\'
root
x
0
0
root
/root
/bin/bash
你可能注意到/bin/bash
之后有一空行,原因在于:通过awk \'{if(NR == 1) print $0}\' awk_sep_demo.txt
获得的是文件中的第一行,该行最后一个看不见的换行符\'\n\'
。
输出行分割符ORS
输出行分割符,默认的是换行符,它的机制和OFS
机制一样,对输出格式有要求时,可以进行格式化输出:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'BEGIN { ORS = "\t\t" } { print }\' awk_sep_demo.txt
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
简单数据统计
如下,下面的命令统计当前目录下,所有文件大小的总和:
[root@iZbp15brp59m56cywazt3yZ ~]# ls -l
total 20
-rw-r--r-- 1 root root 511 May 26 11:07 awk_demo.txt
-rw-r--r-- 1 root root 183 May 26 14:50 awk_sep_demo.txt
-rw-r--r-- 1 root root 33 May 26 16:37 bin.txt
-rw-r--r-- 1 root root 118 May 26 16:37 others.txt
-rw-r--r-- 1 root root 32 May 26 16:37 root.txt
[root@iZbp15brp59m56cywazt3yZ ~]# ls -l | awk \'{ sum += $5 } END { print sum }\'
877
第5列表示文件大小,每读取一行就会将该文件大小计算到sum
变量中,在最后END
阶段打印出sum
,也就是所有文件的大小总和。
4. awk
进阶
数组
内置函数
awk
内置支持一系列函数,其中length
计算字符串长度,toupper
函数转换字符串为大写。
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'BEGIN { FS = ":" } { if (length($1) == 3) print $1, "\t", toupper($1) }\' awk_sep_demo.txt
bin BIN
adm ADM
条件语句与循环
条件
利用NR
和FNR
这两个内置变量,使用其条件语句,有一个有趣的应用,即比较两个文件awk_demo.txt
和awk_sep_demo.txt
是否一致,以awk_demo.txt
作为参考,不一致的输出行号和该行信息:
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{print "NR:" NR "\t\t" "FNR:" FNR}\' awk_demo.txt awk_sep_demo.txt
NR:1 FNR:1
NR:2 FNR:2
NR:3 FNR:3
NR:4 FNR:4
NR:5 FNR:5
NR:6 FNR:6
NR:7 FNR:7
NR:8 FNR:8
NR:9 FNR:9
NR:10 FNR:10
NR:11 FNR:11
NR:12 FNR:1
NR:13 FNR:2
NR:14 FNR:3
NR:15 FNR:4
NR:16 FNR:5
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'{ if (NR == FNR) { array[NR] = $0 } else { if (array[FNR] != $0) { print FNR, array[FNR] } } }\' awk_demo.txt awk_sep_demo.txt
1 drwxr-xr-x 2 root root 0 May 26 2021 block
2 drwxr-xr-x 35 root root 0 May 26 2021 bus
3 drwxr-xr-x 54 root root 0 May 26 2021 class
4 drwxr-xr-x 4 root root 0 May 26 2021 dev
5 drwxr-xr-x 15 root root 0 May 26 2021 devices
上述awk
语句的含义为:
- 当读取第一个文件
awk_demo.txt
的时候NR
和FNR
都是从1开始计数,这时NR == FNR
将该行赋值给数组; - 当
NR != FNR
此时表示已读取到第二个文件,将数组中的内容和当前行$0
进行比较,如果不相同,则输出行号和该行信息。
下面的例子利用条件判断,根据每一条记录所属的用户,分别将各条记录输出至文件root.txt
、bin.txt
以及others.txt
:
[root@iZbp15brp59m56cywazt3yZ ~]# cat awk_sep_demo.txt
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
[root@iZbp15brp59m56cywazt3yZ ~]# awk \'BEGIN { FS = ":" } { if($1 == "root") print > "root.txt"; \
else if($1 == "bin") print > "bin.txt"; \
else print > "others.txt" }\' awk_sep_demo.txt
[root@iZbp15brp59m56cywazt3yZ ~]# cat root.txt
root:x:0:0:root:/root:/bin/bash
[root@iZbp15brp59m56cywazt3yZ ~]# cat bin.txt
bin:x:1:1:bin:/bin:/sbin/nologin
[root@iZbp15brp59m56cywazt3yZ ~]# cat others.txt
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
循环
下面的例子用到了数组
和for
循环,值得一提的是,awk
的数组可以理解为字典或Map
,key
可以是数值和字符串,这种数据类型在平时很常用。
[root@iZbp15brp59m56cywazt3yZ ~]# ps -aux | awk \'BEGIN { OFS = "\t\t\t" } { if(NR != 1) array[$1] += $6 } END { for(i in array) print i, array[i]}\'
systemd+ 8876
chrony 3864
polkitd 21816
dbus 5484
rngd 6488
libstor+ 1860
root 283532
需要注意的是,由于第一行为表头,需要通过条件判断if(NR != 1)
将其排除。
用户自定义函数
awk
脚本编写与运行
为了从整体上理解awk
工作机制,我们再来看一个综合的示例,假设有一个学生成绩单:
[root@iZbp15brp59m56cywazt3yZ ~]# cat scores.txt
Marry 2143 78 84 77
Jack 2321 66 78 45
Tom 2122 48 77 71
Mike 2537 87 97 95
Bob 2415 40 57 62
由于此示例程序稍显复杂,在命令行上不易读,另外呢,也想通过此案例介绍另外一种 awk
的执行方式,我们的awk
脚本如下:
[root@iZbp15brp59m56cywazt3yZ ~]# cat cal_scores.awk
#!/bin/awk -f
BEGIN {
math = 0
English = 0
computer = 0
printf "NAME NO. MATH ENGLISH COMPUTER TOTAL\n"
printf "-------------------------------------------------------------\n"
}
{
math += $3
English += $4
computer += $5
print $1, "\t", $2, "\t", $3, "\t", $4, "\t\t", $5, "\t\t", $3 + $4 + $5
}
END {
printf "-------------------------------------------------------------\n"
print "TOTAL:", "\t\t", math, "\t", English, "\t\t", computer
print "AVERAGE:", "\t", math / NR, "\t", English / NR, "\t\t", computer / NR
}
执行awk
结果如下:
[root@iZbp15brp59m56cywazt3yZ ~]# awk -f cal_scores.awk scores.txt
NAME NO. MATH ENGLISH COMPUTER TOTAL
-------------------------------------------------------------
Marry 2143 78 84 77 239
Jack 2321 66 78 45 189
Tom 2122 48 77 71 196
Mike 2537 87 97 95 279
Bob 2415 40 57 62 159
-------------------------------------------------------------
TOTAL: 319 393 350
AVERAGE: 63.8 78.6 70
我们可以将复杂的 awk
语句写入脚本文件 cal_scores.awk
,然后通过 -f
选项指定从脚本文件执行。
- 在
BEGIN
阶段,我们初始化了相关变量,并打印了表头的格式; - 在
body
阶段,我们读取每一行数据,计算该学科和该同学的总成绩; - 在
END
阶段,我们先打印了表尾的格式,并打印总成绩,以及计算了平均值。