【Linux进阶】使用grep、find、sed以及awk进行文本操作

  • A+
所属分类:linux技术
摘要

详情请见匹配规则。下面通过实战演示的方式介绍grep命令的常见用法,为此首先我们需要一个示例文本文件,这里使用CentOS操作系统/etc目录下的passwd文件:


目录

一、元字符

详情请见匹配规则

二、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命令

参考Linux 文件搜索神器 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命令

参考干货!上古神器 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

子命令 ia 用法基本一样,只不过 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/^/tt/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

  • iI 均替换为 --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/(.)(.)(.*)$/13/' 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/(.)(.)(.*)$/213/' 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 

下面的正则中:

  • ([^,]) 中的 表示指定括号为逃逸字符,^含义为
  • 12 分别表示使用分组。
[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 AhoPeter WeinbergerBrian Kernighan的姓氏首字符。

2. awk工作原理

awk的命令格式如下:

awk 'BEGIN{ commands } pattern{ commands } END{ commands }' 

基于上述命令格式,awk的工作流程可分为三个部分:

  • 读输入文件之前执行的代码段(由BEGIN关键字标识);
  • 主循环执行输入文件的代码段;
  • 读输入文件之后的代码段(由END关键字标识)。

下面的流程图详细描述出了awk的工作流程:

【Linux进阶】使用grep、find、sed以及awk进行文本操作

  1. 通过关键字BEGIN执行BEGIN块的内容,即BEGIN后花括号{}的内容;
  2. 完成BEGIN块的执行,开始执行BODY块;
  3. 读入由n换行符分割的记录,一条记录即为一行;
  4. 将记录按指定的域分隔符划分为域(相当于数据库的字段取值),其中$0表示所有域(即一行内容),$1表示第一个域,$n表示第n个域;
  5. 依次执行各BODY块,pattern部分匹配该行内容成功后,才会执行awk命令中commands部分的内容;
  6. 循环读取并执行各行直到文件结束,完成BODY块执行;
  7. 开始END块执行,END块可以输出最终结果。

开始块BEGIN

开始块的语法格式如下:

BEGIN {awk-commands} 

开始块就是在程序启动的时候执行的代码部分,并且它在整个过程中只执行一次。一般情况下,我们可以在开始块中初始化一些变量。

BEGINawk的关键字,因此它必须是大写的。

注意:开始块部分是可选的,你的程序可以没有开始块部分。

主体块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 

大括号里边的就是awkcommand只能被单引号包含,其中,$1..$N表示第几列,$0表示整个的行内容。

格式化输出

awk还支持类型C语言printf函数的格式化输出:

[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk '{printf "%-20s %-8s %-10s %-8sn",$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 

记录数NRFNR

如下,打印出读取文件的行数,因为是按行读取,在应用场景中,行数可以等同于行号,用来输出对应行的行号:

[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 "tt" "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里面字母间是以:来分割的,所以:

  • 第一种写法,由于没有指定FSawk默认是以空格来分割,每一行所有内容都当作$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 = "ttt" } { 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 = "tt" } { 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数组

内置函数

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 

条件语句与循环

条件

awk条件语句与循环

利用NRFNR这两个内置变量,使用其条件语句,有一个有趣的应用,即比较两个文件awk_demo.txtawk_sep_demo.txt是否一致,以awk_demo.txt作为参考,不一致的输出行号和该行信息:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print "NR:" NR "tt" "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的时候NRFNR都是从1开始计数,这时NR == FNR将该行赋值给数组;
  • NR != FNR此时表示已读取到第二个文件,将数组中的内容和当前行$0进行比较,如果不相同,则输出行号和该行信息。

下面的例子利用条件判断,根据每一条记录所属的用户,分别将各条记录输出至文件root.txtbin.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的数组可以理解为字典或Mapkey可以是数值和字符串,这种数据类型在平时很常用。

[root@iZbp15brp59m56cywazt3yZ ~]# ps -aux | awk 'BEGIN { OFS = "ttt" } { 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脚本编写与运行

为了从整体上理解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        TOTALn"         printf "-------------------------------------------------------------n" } {         math += $3         English += $4         computer += $5         print $1, "t", $2, "t", $3, "t", $4, "tt", $5, "tt", $3 + $4 + $5 } END {         printf "-------------------------------------------------------------n"         print "TOTAL:", "tt", math, "t", English, "tt", computer         print "AVERAGE:", "t", math / NR, "t", English / NR, "tt", 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 阶段,我们先打印了表尾的格式,并打印总成绩,以及计算了平均值。