欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

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

程序员文章站 2022-03-03 22:23:56
(文章目录) 一、元字符 详情请见匹配规则。 二、grep命令 下面通过实战演示的方式介绍grep命令的常见用法,为此首先我们需要一个示例文本文件,这里使用CentOS操作系统/etc目录下的passwd文件: [root@iZbp1gjysfmcbcojeshiw7Z python2.7]# ls ......

一、元字符

详情请见。

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

实际上,对于 也有类似的语法。

  • 查找比 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/^/\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

  • 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/(.)(.)(.*)$/\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 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 %-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

记录数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 "\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里面字母间是以:来分割的,所以:

  • 第一种写法,由于没有指定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 = "\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

条件语句与循环

条件

利用nrfnr这两个内置变量,使用其条件语句,有一个有趣的应用,即比较两个文件awk_demo.txtawk_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的时候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 = "\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 阶段,我们先打印了表尾的格式,并打印总成绩,以及计算了平均值。