Shell 三剑客

一、Shell 三剑客概述

1. Shell 三剑客

Shell 三剑客是指 grepsedawk 这三个在 Linux/Unix 系统中常用的命令行工具。

  • grep :grep 是一个文本搜索工具,用于根据给定的模式(正则表达式)在文件或输入流中搜索匹配的行。grep 的主要作用是 查找过滤 特定文本。

  • sed:sed 是一个流编辑器,可以对文本进行查找、替换、删除等操作。它可以直接在文本流中编辑,不需要打开文件,非常适合批量处理。

  • awk:awk 是一个文本处理工具,适合对列进行处理和分析。它不仅可以进行查找和替换,还能执行数学计算和数据统计,特别适合处理以列形式组织的数据,如日志、CSV 文件等。

面试题:grep、sed、awk 有何区别?

  • grep 用于查找特定内容,适合快速过滤日志或配置文件。

  • sed 用于修改和编辑文本,非常适合批量替换或删除操作。

  • awk 更强大,适用于列状数据的处理,能够提取字段、统计数据或进行数学计算。

在运维工作中,我会根据不同的需求选择合适的工具:当需要简单查找时用 grep ,批量修改文件时用 sed ,处理复杂的日志或统计数据时使用 awk

2. Shell 三剑客适用场景

  1. 日志处理与搜索:使用 grep 搜索关键词,结合 sed 和 awk 进行进一步处理和分析。

  2. 配置文件管理:使用 sed 和 awk 进行批量修改、添加或删除配置项。

  3. 数据提取与转换:利用 awk 提取、分析和转换结构化文本数据,处理 CSV、JSON 等格式。

  4. 监控与报警:使用 grep、sed 和 awk 提取关键信息生成监控报告,监控系统状态和性能。

  5. 日常运维任务:自动化任务如查找过期文件、清理日志、统计日志大小等。

  6. 系统管理:使用 sed 和 awk 处理系统状态信息,如用户信息、进程信息、磁盘使用情况等。

3. Shell 三剑客执行说明

默认情况下,grep、sed 以及 awk 在处理数据时默认都是按 处理。


二、Shell 三剑客之 grep 指令

1. grep 基本语法

grep 主要作用:过滤来自一个文件或标准输入匹配模式内容。

除了 grep 外,还有 egrep。egrep 是 grep 的扩展,相当于 grep -E

基本语法: grep [OPTION]... PATTERN [FILE]...

支持的正则 描述
-E, --extended-regexp 模式是扩展正则表达式 (ERE) => 字符簇、()、|或
-P, --perl-regexp 模式是 Perl 正则表达式 => \d\w\s
-i, --ignore-case 忽略大小写
-w, --word-regexp 模式匹配整个单词
-v, --invert-match 打印不匹配的行
输出控制 描述
-n, --line-number 打印行号
-h, --no-filename 不输出文件名
-o, --only-matching 只打印匹配的内容
-r, --recursive 递归目录
-c, --count 统计匹配行数

2. grep 案例演示

准备数据集
复制代码
# vim demo.txt
Hello, this is an example file.
It contains some lines of text.
Let's use grep to search for specific patterns. 

案例 1:在文件中搜索包含单词 "example" 的行

复制代码
grep "example" demo.txt 

案例 2:忽略大小写地搜索匹配模式的行

在文件中搜索不区分大小写的单词 "hello" 的行

复制代码
grep -i "hello" demo.txt

案例 3:反转匹配,只打印不匹配模式的行

在文件中搜索不包含 "text" 的行

复制代码
grep -v "text" demo.txt

案例 4:显示匹配行的行号

在文件中搜索包含单词 "example" 的行,并显示行号

复制代码
grep -n "example" demo.txt

案例 5:统计匹配的行数

复制代码
grep -c "example" demo.txt

案例 6:仅匹配整个单词

在文件中搜索包含单词 "example" 的行,是整个单词,而不是一个单词的一部分

复制代码
grep -w "example" demo.txt

案例 7:递归地搜索目录及其子目录下的文件

搜索 /root/ 目录下包含内容 "Hello, this is an example file." 的所有文件

复制代码
grep -r "Hello, this is an example file." /root/

三、Shell 三剑客之 sed 指令

作用: sed 命令功能比较全,可以进行增删改查操作 => 运维工作中:修改操作相对而言是最多。

1. sed 指令说明

我们都知道,在 Linux 中一切皆文件,比如配置文件,日志文件,启动文件等等。如果我们相对这些文件进行一些编辑查询等操作时,我们可能会想到一些 vi, vim, cat, more 等命令。但是这些命令效率不高,这就好比一块空地准备搭建房子,请了 10 个师傅拿着铁锹挖地基,花了一个月的时间才挖完,而另外一块空地则请了个挖土机,三下五除二就搞定了,这就是效率。而在 Linux 中的 "挖土机" 有三种型号:顶配 awk,中配 sed,标配 grep。使用这些工具,我们能够在达到同样效果的前提下节省大量的重复性工作,提高效率。

sed 是 Stream Editor(字符流编辑器)的缩写,简称流编辑器。什么是流?大家可以想象一下流水线,sed 就像一个车间一样,文件中的每行字符都是原料,运到 sed 车间,然后经过一系列的加工处理,最后从流水线下来就变成货物了。

以前工厂中没有流水线时,生产一件商品需要十几个工种互相配合,这样下来利润太低,后来就有了流水线,生产一件商品虽然还是有十几道工序,但都是机器化生产,工人只是辅助作用,这样利润就大大提高了,产量也大大提高了。

编辑文件也是这样,以前我们修改一个配置文件,需要移动光标到某一行,然后添加点文字,然后又移动光标到另一行,注释点东西......可能修改一个配置文件下来需要花费数十分钟,还有可能改错了配置文件,又得返工。这还是一个配置文件,如果数十个数百个呢?因此当你学会了 sed 命令,你会发现利用它处理文件中的一系列修改是很有用的。只要想到在大约 100 多个文件中,处理 20 个不同的编辑操作可以在几分钟之内完成,你就会知道 sed 的强大了。

2. 软件功能和版本

复制代码
sed --version # 查看 sed 软件版本

3. 语法格式

复制代码
sed [options] [sed-commands] [input-file]  
sed [选项] 【sed 命令】 【输入文件】

说明:

  1. 注意 sed 软件以及后面选项,sed 命令和输入文件,每个元素之间都至少有一个空格。

  2. 为了避免混淆,笔记中称呼 sed 为 sed 软件。sed-commands(sed 命令) 是 sed 软件内置的一些命令选项,为了和前面的 options(选项)区分,故称为 sed 命令。

  3. sed-commands 既可以是单个 sed 命令,也可以是多个 sed 命令组合。

  4. input-file (输入文件) 是可选项,sed 还能够从标准输入如管道获取输入。

4. 命令执行流程

概括流程: sed 软件从文件或管道中读取一行,处理一行,输出一行;再读取一行,再处理一行,再输出一行....

小知识: 一次一行的设计使得 sed 软件性能很高,sed 在读取非常庞大的文件时不会出现卡顿的现象。大家都用过 vi 命令,用 vi 命令打开几十 M 或更大的文件,会发现有卡顿现象,这是因为 vi 命令打开文件是一次性将文件加载到内存,然后再打开,因此卡顿的时间长短就取决于从磁盘到内存的读取速度了。而且如果文件过大的话还会造成内存溢出现象。Sed 软件就很好的避免了这种情况,打开速度非常快,执行速度也很快。

详细流程: 现有一个文件 person.txt, 共有五行文本,sed 命令读入文件 person.txt 的第一行 "101, Tom, CEO", 并将这行文本存入模式空间(sed 软件在内存中的一个临时缓存,用于存放读取到的内容,比喻为工厂流水线的传送带。)

文件 person.txt 在模式空间的完整处理流程

  1. 判断第 1 行是否是需要处理的行,如果不是要处理的行就重新从文件读取下一行,如果是要处理的行,则接着往下走。

  2. 对模式空间的内容执行 sed 命令,比如 a(append 追加,在行之后),i(insert 插入,在行之前),s(替换)...

  3. 将模式空间中经过 sed 命令处理后的内容输出到屏幕上,然后清空模式空间。

  4. 读取下一行文本,然后重新执行上面的流程,直到文件结束。

Sed 软件有两个内置的存储空间:

  • 模式空间(pattern space):是 sed 软件从文本读取一行文本然后存入的缓冲区(这个缓冲区是在内存中的),然后使用 sed 命令操作模式空间的内容。

  • 保持空间(hold space):是 sed 软件另外一个缓冲区,用来存放临时数据,也是在内存中,但是模式空间和保持空间的用途是不一样的。sed 可以交换保持空间和模式空间的数据,但是不能在保持空间上执行普通的 sed 命令,也就是说我们可以在保持空间存储数据。

5. sed 选项说明

sed 选项 sed 命令 文件

option选项 解释说明(带*的为重点)
-n 取消默认的 sed 软件的输出,常与 sed 命令的 p 连用*
-r 使用扩展正则表达式(grep -E),默认情况 sed 只识别基本正则表达式*
-i 直接修改文件内容,而不是输出到终端,如果不使用 -i 选项 sed 软件只是修改在内存中的数据,并不会影响磁盘上的文件*
sed-commands 解释说明(带*的为重点)
a append 追加,在指定行后添加一行或多行文本*
c change,取代指定的行
d delete,删除指定的行*
i insert 插入,在指定行前添加一行或多行文本*
p print,打印模式空间内容,通常 p 会与选项 -n 一起使用*
q 退出 sed
r 从指定文件读取数据
s 取代,s#old#new#g===这里 g 是 s 命令的替代标志,注意和 g 命令区分。*

重点记住:增删改查操作 => 增加 a、删除 d、修改 c 或 s、查询 p

特殊符号 解释说明(带*的为重点)
! (取反),对指定行以外的所有行应用命令*
= 打印当前行行号
~ "first ~ step"表示从 first 行开始,以步长 step 递增

6. sed 增加操作

数据集准备

复制代码
cat > person.txt <<EOF
101, Tom, CEO
102, Rose, CTO
103, Alex, COO
104, Jack, CFO
105, Jennifer, CIO
EOF 
# EOF 必须成对出现,表示终止输入

命令说明: 使用一条 cat 命令创建多行文本,文件包含上面的内容,后面的操作都会使用这个文件。

增加操作就是往文件指定位置追加或插入指定文本。

这个功能非常有用,比如我们平时往配置文件写入几行文本,最常用的是 vi 或 vim 命令,但是这 2 个命令是一种交互式的命令,还需要我们在 vi/vim 编辑器界面输入字符串然后保存退出,操作有些繁琐但是还能用。但是当我们学会了 Shell 脚本后,我们就会发现在脚本中不能正常使用 vi 或 vim 命令,为什么呢?

我们学习 Shell 脚本主要是为了解放我们的双手,执行一个脚本,然后自动往文件中写入数据,不需要我们再动手。因此我们想到了 sed 软件,它能够帮助我们实现目的。

这里我们需要用到 2 个 sed 命令,分别是:

  • "a" : 追加文本到指定行后,记忆方法:a 的全拼是 append,意思是追加。

  • "i" :插入文本到指定行前,记忆方法:i 的全拼是 insert,意思是插入。

单行增:

首先我们看一下单行增加的用法,说白了就是在文件中增加一行文本,我们以前学过 echo 命令可以在文件的末尾追加文本,比较简单,但是我们还有其他的复杂需求,比如在第 10 行插入一行数字等等,这里就需要 sed 出马了。我们来看一下面的例子:

复制代码
sed '2a 106,Smith,CSO' person.txt 

注:

  • 2 代表指定对第 2 行操作,其他的行忽略;

  • a 代表追加的意思,2a 即在第 2 行后追加文本;

  • 2a 后面加上空格,然后跟上你想要插入的多行文本即可。这里的每行文本使用 \n 连接就可以写成一行了。

多行增:
复制代码
sed '2a 106,Eric,CSO\n107,Susan,CCO' person.txt 

注: \n 代表换行符

小结:
  • sed 默认操作文件内容是在内存中的模式空间中进行实现,不会影响原文件内容。

  • 增加操作有两种方式(行号 + i,指定行前面添加内容),(行号 + a,指定行后面添加内容)。

  • 如果希望增加操作影响原文件,可以添加一个选项 -i

7. sed 删除操作

删除指定行文本。

这个功能也是非常得有用,比如我们想删除文件中的某些行,以前最常用的是 vi 或 vim 命令,但现在我们知道了 sed 命令,就应该使用这个高逼格的命令完成任务了。

这里我们需要用到 1 个 sed 命令:

  • "d": 删除文本,记忆方法:d 的全拼是 delete,意思是删除。

因为删除功能比较简单,因此我们结合地址范围一起说明。我们前面学过 sed 命令可以对一行文本为目标进行处理(在单行前后增加一行或多行文本),接下来我们看一下如何对多行文本为目标操作。

指定执行的地址范围:

sed 软件可以对单行或多行文本进行处理。如果在 sed 命令前面不指定地址范围,那么默认会匹配所有行。

用法: n1[,n2]{sed-commands}

地址用逗号分隔开,n1,n2 可以用数字,正则表达式,或者二者的组合表示。

地址范围 含义
10 对第 10 行操作
10,20 对 10 到 20 行操作,包括第 10,20 行
1~2 first~step,对 1,3,5,7......行操作
10,$ 对 10 到最后一行 ($代表最后一行) 操作,包括第 10 行
/Tom/ 对匹配 Tom 的行操作
/Tom/,/Alex/ 对匹配 Tom 的行到匹配 Alex 的行操作
/Tom/,$ 对匹配 Tom 的行到最后一行操作
1,/Alex/ 对第 1 行到匹配 Alex 的行操作

案例 1:下面用具体的例子演示一下 sed 删除操作实现

复制代码
sed 'd' person.txt

命令说明: 如果在 sed 命令前面不指定地址范围,那么默认会匹配所有行,然后使用 d 命令删除功能就会删除这个文件的所有内容。

案例 2:

复制代码
sed '2d' person.txt

命令说明: 这个单行删除想必大家能理解,指定删除第 2 行的文本。

案例 3:

复制代码
sed '2,5d' person.txt

命令说明: '2,5d' 指定删除第 2 行到第 5 行的内容,d 代表删除操作。

案例 4:

复制代码
sed '/Jack/d' person.txt

命令说明: 在 sed 软件中,使用正则的格式和 awk 一样,使用 2 个 / 包含指定的正则表达式,即 /正则表达式/

案例 5:

复制代码
sed '/Tom/,/Alex/d' person.txt

命令说明: 这是正则表达式形式的多行删除,也是以逗号分隔 2 个地址,最后结果是删除包含 "Tom" 的行到包含 "Alex" 的行。

案例 6:

复制代码
sed '3,$d' person.txt

命令说明: 学过正则表达式后我们知道 $ 代表行尾,但是在 sed 中就有一些变化了,$ 在 sed 中代表文件的最后行。因此本例子的含义是删除第 3 行到最后一行的文本,包含第 3 行和最后一行,因此剩下第 1,2 行的内容。

案例 7:

复制代码
sed '$a 106,Tom,CSO' person.txt

命令说明: 为了不造成同学们实验文本改来改去导致不同意,因此我用上面的命令语句只是临时修改内存数据,然后通过管道符号传给 sed 软件。

特殊符号~(步长)解析

格式: "first~step" 表示从开始,以步长 step 递增,这个在数学中叫做等差数列。

使用:

  • 1~2 匹配 1,3,5,7... #--> 用于只输出奇数行,大伙仔细观察一下每个数字的差值。

  • 2~2 匹配 2,4,6,8... #--> 用于只输出偶数行。

  • 1~3 匹配 1,4,7,10...

  • 2~3 匹配 2,5,8,11...

案例 8:

复制代码
seq 10   
1   
2   
3   
4   
5   
6   
7   
8   
9   
10 
​
seq 10 | sed -n '1~2p'  
1  
3  
5  
7  
9 

命令说明:

上面的命令主要验证特殊符号 ~ 的效果,其他 sed 命令用法 n 和 p 请见后文详解,大家只需要知道这个命令可以将 1~2 指定的行显示出来即可。

上面例子测试了 1~2 的效果,大家也可以手动测试一下 2~21~32~3,看一下他们的结果是不是符合等差数列。

案例 9:删除所有奇数行

复制代码
sed '1~2d' person.txt

命令说明: "1~2" 这是指定行数的另一种格式,从第 1 行开始以步长 2 递增的行(1,3,5),因此删掉第 1,3,5 行,即所有的奇数行。

案例 10:特殊符号!

感叹号 ! 我们在很多命令里都接触过,大部分都是 取反 的意思,在 sed 中也不例外。

复制代码
sed '2,3!d' person.txt  

命令说明: 在地址范围 2,3 后面加上 !,如果不加 ! 表示删除第 2 行和第 3 行,结果如下面的例子所示,然后加上 ! 的结果就是除了第 2 行和第 3 行以外的内容都删除,这个方法可以作为显示文件的第 2,3 行题目的补充方法。

小结:
  • sed 软件不仅可以添加数据行,也可以删除数据行,删除数据行必须使用(d)命令。

  • sed 删除软件 => (sed '范围 d' 文件) => 默认都是在内存中完成,需要影响原文件,则添加一个 -i

8. sed 修改操作(重点)

sed 修改操作,我们最常见的操作就是改配置文件,改参数等等。

首先说一下按行替换,这个功能用的很少,所以大家了解即可。这里用到的 sed 命令是:"c":用新行取代旧行,记忆方法:c 的全拼是 change,意思是替换。

复制代码
sed '2c 106,Smith,CSO' person.txt 

命令说明: 使用 sed 命令 c 将原来第 2 行内容替换成 106,Smith,CSO, 整行替换。

关键词替换

接下来说的这个功能,有工作经验的同学应该非常的熟悉,因为使用 sed 软件 80% 的场景就是使用替换功能。

这里用到的 sed 命令,选项:

  • "s": 单独使用 --> 将每一行中第一处匹配的字符串进行替换 --> sed 命令。

  • "g": 每一行进行全部替换 --> sed 命令 s 的替换标志之一(全局替换),非 sed 命令。

  • "-i": 修改文件内容 ---> sed 软件的选项,注意和 sed 命令 i 区别。

sed 软件替换模型
复制代码
sed -i 's/旧的内容/替换后内容/' 文件内容
sed -i 's/旧的内容/替换后内容/g' 文件内容
sed -i 's#旧的内容#替换后内容#g' 文件内容

路径替换式:

复制代码
s#/home#/root#g 

g: global 全局替换。

sed 本身按照行进行操作的,如果在 1 行中有多个要替换的关键词,不加 g,则代表只替换满足条件第一个关键词;如果添加 g,则代表替换满足条件的所有关键词!

观察特点:
  1. 两边是引号,引号里面的两边分别为 s 和 g,中间是三个一样的字符 /# 作为定界符。字符 # 能在替换内容包含字符 / 有助于区别。定界符可以是任意字符如:| 等,但当替换内容包含定界符时,需要转义 \。经过长期实践,建议大家使用 # 作为定界符。

  2. 定界符 /#,第一个和第二个之间的就是被替换的内容,第二个和第三个之间的就是替换后的内容。

  3. s#目标内容#替换内容#g,"目标内容" 能用正则表达式,但替换内容不能用,必须是具体的。因为替换内容使用正则的话会让 sed 软件无所适从,它不知道你要替换什么内容。

  4. 默认 sed 软件是对模式空间(内存中的数据)操作,而 -i 选项会更改磁盘上的文件内容。

案例 1:

复制代码
sed 's#Tom#Eric#g' person.txt 

命令说明: 将需要替换的文本 "Tom" 放在第一个和第二个 # 之间,将替换后的文本 "Eric" 放在第二个和第三个 # 之间。结果为第二行的 "Tom" 替换为 "Eric"。

案例 2:指定行修改配置文件

复制代码
sed '3s#0#9#' person.txt 

命令说明: 前面学习的例子在 sed 命令 "s" 前没有指定地址范围,因此默认是对所有行进行操作。而这个案例要求只将第 3 行的 0 换成 9,这里就用到了我们前面学过的地址范围知识,在 sed 命令 "s" 前加上 3 就代表对第 3 行进行替换。

案例 3:分组替换 () 和 \1 的使用功能

sed 软件的 () 的功能可以记住正则表达式的一部分,其中,\1 为第一个记住的模式即第一个小括号中的匹配内容,\2 第二个记住的模式,即第二个小括号中的匹配内容,sed 最多可以记住 9 个。

案例 4:执行命令取出 linux 中的 ens33 的 IP 地址?

复制代码
dnf install net-tools -y
# ifconfig ens33 | sed -n '2p'
    inet 192.168.88.101 netmask 255.255.255.0 broadcast 192.168.88.255
# ifconfig ens33 | sed -n '2p' | sed -r 's#.*inet ([0-9.]+).*#\1#g'
192.168.88.101 

正则解析:

  • .* 匹配所有,但是受到 inet 影响,只能匹配 inet 前面内容。

  • inet ([0-9.]+),这个小括号中的内容,用于匹配 IP(只有数字和点号的情况), 放入 1 号分组。

  • IP 后面内容统一用 .* 匹配。

  • \1 引用刚才匹配的 IP 地址,然后替换整个前面一行内容,最后就剩 1 个 IP。

命令说明:

这道题是需要把 ifconfig ens33 执行结果的第 2 行的 IP 地址取出来,上面答案的思路是用 IP 地址来替换第 2 行的内容。

案例 5:

复制代码
echo "I am eric teacher." | sed -r 's#^.*am ([a-z]+) tea.*#\1#g'
eric 

9. sed 查询操作

这个功能也是非常得有用,比如我们想查看文件中的某些行,以前最常用的是 cat 或 more 或 less 命令等,但这些命令有些缺点,就是不能查看指定的行。而我们用了很久的 sed 命令就有了这个功能了。而且我们前面也说过使用 sed 比其他命令 vim 等读取速度更快!

这里我们需要用到 1 个 sed 命令:

  • "p":输出指定内容,但默认会输出 2 次匹配的结果,因此使用 -n 选项取消默认输出,记忆方法:p 的全拼是 print,意思是打印。

案例 1:

复制代码
sed '2p' person.txt  

命令说明: 选项 -n 取消默认输出,只输出匹配的文本,大家只需要记住使用命令 p 必用选项 -n

复制代码
sed -n '2p' person.txt

案例 2:

复制代码
sed -n '2,3p' person.txt

命令说明: 查看文件的第 2 行到 3 行,使用地址范围 2,3。取行就用 sed,最简单。

案例 3:

复制代码
sed -n '1~2p' person.txt  

注意: 起始值~步长。

命令说明: 打印文件的 1,3,5 行。~ 代表步长。

案例 4:

复制代码
sed -n 'p' person.txt

命令说明: 不指定地址范围,默认打印全部内容。

案例 5:

复制代码
sed -n '/CTO/p' person.txt

命令说明: 打印含 CTO 的行。

案例 6:

复制代码
sed -n '/CTO/,/CFO/p' person.txt

命令说明: 打印含 CTO 的行到含 CFO 的行。

案例 7:

复制代码
sed -i.bak 's#Tom#Peter#g' person.txt 

命令行说明:

-i 参数的后边加上 .bak(. 任意字符),sed 会对文件进行先备份后修改。

特殊符号 = 获取行号(了解)

案例 8:

复制代码
sed '=' person.txt 

命令说明: 使用特殊符号 = 就可以获取文件的行号,这是特殊用法,记住即可。从上面的命令结果我们也发现了一个不好的地方:行号和行不在一行。

案例 9:

复制代码
sed '1,3=' person.txt 

命令说明: 打印 1,2,3 行的行号,同时打印输出文件中的内容。

案例 10:取不连续的行

复制代码
sed -n '1p;3p;5p' person.txt
小结:
  • sed 如果想对行进行打印输出,通常使用 (p) 命令,取消默认输出,使用选项(-n)。

  • sed 功能非常强大:支持(增删改查操作)。


四、Shell 三剑客之 awk 指令

作用: awk 可以完成复杂的数据分析,如日志分析等等。

1. awk 概述

awk 不仅仅是 Linux 系统中的一个命令,更可以理解为它是一种编程语言,可以用来处理数据和生成报告(excel)。处理的数据可以是一个或多个文件,可以是来自标准输入,也可以通过管道获取标准输入,awk 可以在命令行上直接编辑命令进行操作,也可以编写成 awk 程序来进行更为复杂的运用。本章主要讲解 awk 命令的运用。

2. awk 格式

awk 指令是由模式,动作,或者模式和动作的组合组成。

  • 模式 既 pattern, 可以类似理解成 sed 的模式匹配,可以由表达式组成,也可以是两个正斜杠之间的正则表达式。比如 NR==1,这就是模式,可以把他理解为一个条件。

  • 动作 即 action,是由在大括号里面的一条或多条语句组成,语句之间使用分号隔开。

awk 处理的内容可以来自标准输入(<),一个或多个文本文件或管道。

options 既参数,使用最多的参数为 -F(Field,用于指定字段与字段之间的分隔符,列与列之间的分隔符,默认为空格)。

pattern 既模式,也可以理解为条件,也叫找谁,你找谁?高矮,胖瘦,男女?都是条件,既模式。

action 既动作,可以理解为干啥,找到人之后你要做什么。

注意: awk -F"分隔符" '模式和动作' file

模式和动作的详细介绍我们放在后面部分,现在大家先对 awk 结构有一个了解。

案例 1:基本模式与动作

复制代码
awk -F":" 'NR>=2 && NR<=6{print NR, $1}' /etc/passwd 
2 bin
3 daemon
4 adm
5 lp
6 sync

命令说明:

  • -F 指定分隔符为冒号,相当于以 : 为菜刀,进行字段的切割。

  • NR>=2 && NR<=6:这部分表示模式,是一个条件,表示取第 2 行到第 6 行。

  • {print NR, $1}:这部分表示动作,表示要输出 NR 行号和 $1 第一列。

  • NR = Number(数字、号码) + Record(记录、行) = 行号。

案例 2:只有模式

复制代码
awk -F ":" 'NR>=2 && NR<=6' /etc/passwd  
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 

命令说明:

  • -F 指定分隔符为冒号。

  • NR>=2 && NR<=6 这部分是条件,表示取第 2 行到第 6 行。

  • 但是这里没有动作,这里大家需要了解如果只有条件(模式)没有动作,awk 默认输出整行。

案例 3:只有动作

复制代码
awk -F":" '{print NR, $1}' /etc/passwd
1 root
2 bin
3 daemon
4 adm
5 lp
6 sync
7 shutdown
8 halt
9 mail
10 uucp
以下省略...

命令说明:

  • -F 指定分隔符为冒号。

  • 这里没有条件,表示对每一行都处理。

  • {print NR,$1} 表示动作,显示 NR 行号与 $1 第一列。

  • 这里要理解没有条件的时候,awk 会处理每一行。

案例 4:多模式与多动作

复制代码
awk -F":" 'NR==1{print NR, $1}; NR==2{print NR,$NF}' /etc/passwd
1 root
2 /sbin/nologin

命令说明:

  • -F 指定分隔符为冒号。

  • 这里有多个条件与动作的组合。

  • NR==1 表示条件,行号(NR)等于 1 的条件满足的时候,执行 {print NR,$1} 动作,输出行号与第一列。

  • NR==2 表示条件,行号(NR)等于 2 的条件满足的时候,执行 {print NR,$NF} 动作,输出行号与最后一列($NF)。

  • NR:Number Record,行号信息。

  • NF:Number Field,注意:计数情况下,往往都代表最后一个,所以 NF 代表最后一列。

注意:

  • Pattern 和 {Action} 需要用单引号引起来,防止 Shell 作解释。

  • Pattern 是可选的。如果不指定,awk 将处理输入文件中的所有记录。如果指定一个模式,awk 则只处理匹配指定的模式的记录。

  • {Action} 为 awk 命令,可以是单个命令,也可以多个命令。整个 Action(包括里面的所有命令)都必须放在 {} 之间。

  • Action 必须被 { } 包裹,没有被 { } 包裹的就是 Pattern。

  • file 要处理的目标文件。

3. awk 执行过程

在深入了解 awk 前,我们需要知道 awk 如何处理文件的。

复制代码
mkdir /server/files/ -p
head /etc/passwd > /server/files/awkfile.txt
cat /server/files/awkfile.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/sbin:/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/email:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin 

awk 执行过程演示:

复制代码
awk 'NR>=2{print $0}' /server/files/awkfile.txt
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/email:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin 

命令说明:

  • 条件 NR>=2, 表示行号大于等于 2 时候,执行 {print $0} 显示整行。

  • awk 是通过一行一行的处理文件,这条命令中包含模式部分(条件)和动作部分(动作),awk 将处理模式(条件)指定的行。

  • $0 代表所有列,$1 代表第 1 列,$2 代表第 2 列,... $NF 代表最后 1 列。

awk 执行过程解析:

  1. awk 读入第一行内容。

  2. 判断是否符合模式中的条件 NR>=2

    • 如果匹配则执行对应的动作 {print $0}

    • 如果不匹配条件,继续读取下一行。

  3. 继续读取下一行。

  4. 重复过程 1-3,直到读取到最后一行(EOF:end of file)。

4. awk 记录(行)

记录和字段 接下来我给大家带来两个新概念记录和字段,这里为了方便大家理解可以把记录就当作行即记录 Record = 行,字段相当于列,字段 = 列。

名称 含义
record 记录,行 => NR(Number Record)行号
field 域,区域,字段,列 => NF(Number Field)列号,$NF 代表最后一列

数据集:

复制代码
cat /server/files/awkfile.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/sbin:/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:/sbin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/email:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin 

思考:

一共有多少行呢?你如何知道的?通过什么标志?

awk 对每个要处理的输入数据认为都是具有格式和结构的,而不仅仅是一堆字符串。默认情况下,== 每一行内容都是一条记录 : ==,并以 : = 换行符 = = 分隔(\n)结束。

RS$0 Proto: Recv-Q: Send-Q: Local Address: Foreign Address: State
tcp 0 0 115.29.49.213:80: 140.206.64.90:62291: TIME_WAIT
RS$0 tcp 0 0 115.29.49.213:80: 49.80.146.230:13453: FIN_WAIT2
RS$0 tcp 0 0 115.29.49.213:80: 49.80.146.230:13453: ESTABLISHED
RS$0 tcp 0 0 115.29.49.213:80: 113.104.25.50:56714: ESTABLISHED
RS$0 tcp 0 0 115.29.49.213:80: 113.104.25.50:56715: TIME_WAIT

awk 默认情况下每一行都是一个记录(record)。

  1. RS 既 record separator 输入输出数据记录分隔符,每一行是怎么没的,表示每个记录输入的时候的分隔符,既行与行之间如何分隔,默认为 \n,可以调整为其他字符。

  2. NR 既 number of record 记录(行)号,表示当前正在处理的记录(行)的号码。

  3. ORS 既 output record separator 输出记录分隔符。

awk 使用内置变量 RS 来存放输入记录分隔符,RS 表示的是输入的记录分隔符,这个值可以通过 BEGIN 模块 == 重新定义修改。

案例 1:

复制代码
awk 'BEGIN{RS="/"}{print NR,$0}' /server/files/awkfile.txt
1 root:x:0:0:root:  
2 root:  
3 bin  
4 bash  
bin:x:1:1:bin:  
5 bin:  
6 sbin  
7 nologin 
daemon:2:2:daemon:
8 sbin:
9 sbin
10 nologin
adm:3:4:adm:
11 var
12 adm:
13 sbin
14 nologin
lp:4:7:lp:
15 var
16 spool
17 lpd:
18 sbin
19 nologin
sync:5:0:sync:
20 sbin:
21 bin
22 sync
shutdown:6:0:shutdown:
23 sbin:
24 sbin
25 shutdown
halt:7:0:halt:
26 sbin:
27 sbin
28 halt
mail:8:12:mail:
29 var
30 spool
31 mail:
32 sbin
33 nologin
uucp:10:14:uucp:
34 var
35 spool
36 uucp:
37 sbin
38 nologin

命令说明:

  • BEGIN:开始模块,在 awk 开始处理数据之前执行 1 次。

  • END:结束模块,在 awk 处理所有行后执行 1 次。

  • 在每行的开始先打印输出 NR(记录号行号),并打印出每一行 $0(整行)的内容。

  • 我们设置 RS(记录分隔符)的值为 /,表示一行(记录)以 / 结束。

  • 在 awk 眼中,文件是从头到尾一段连续的字符串,恰巧中间有些 \n(回车换行符),\n 也是字符哦。

我们回顾下"行(记录)"到底是什么意思?

行(记录):默认以 \n(回车换行)结束。而这个行的结束不就是记录分隔符嘛。

所以在 awk 中,RS(记录分隔符)变量表示着行的结束符号(默认是回车换行)。

在工作中,我们可以通过修改 RS 变量的值来决定行的结束标志,最终来决定"每行"的内容。

为了方便人们理解,awk 默认就把 RS 的值设置为 \n

注意:

awk 的 BEGIN 模块,我会在后面(模式-BEGIN 模块)详细讲解,此处大家仅需要知道在 BEGIN 模块里面我们来定义一些 awk 内置变量即可。

对 $0 的认识

复制代码
awk '{print NR,NF,$0,$1,$2,...}' 
  • NR:行号,打印行号。

  • **** :代表列号,如果前面添加了 ``,则代表最后一列。

  • $0:整行内容。

  • $1:按照分割后,1 代表分割后的第 1 列。

  • $2:按照分割后,2 代表分割后的第 2 列。

由以上代码运行可知:awk 中 $0 表示整行,其实使用 0 来表示整条记录。记录分隔符存在 RS 变量中或者说每个记录以 RS 内置变量结束。

另外,awk 对每一行的记录号都有一个内置变量 NR 来保存,每处理完一条记录,NR 的值就会自动 +1。

案例 2:NR 记录行号

复制代码
awk '{print NR,$0}' /server/files/awkfile.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
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
9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

命令说明:

  • NR 既 number of record,当前记录的记录号,刚开始学也可以理解为行号。

  • $0 表示整行或者说整个记录。

企业面试题:按单词出现频率降序排序(计算文件中每个单词的重复数量)

复制代码
sed -r '1,10s#[^a-zA-Z]+# #g' /etc/passwd |head > /server/files/count.txt
cat /server/files/count.txt
root x root root bin bash
bin x bin bin sbin nologin
daemon x daemon sbin sbin nologin
adm x adm var adm sbin nologin
lp x lp var spool lpd sbin nologin
sync x sync sbin bin sync
shutdown x shutdown sbin sbin shutdown
halt x halt sbin sbinhalt
mail x mail var spool mail sbin nologin
uucp x uucp var spool uucp sbin nologin 

思路:

让所有单词排成一列,这样每个单词都是单独的一行。

  1. 设置 RS 值为空格。

  2. 将文件里面的所有空格替换为回车换行符 \n

  3. 基于 sort + uniq -c 实现词频统计。

方案一:

复制代码
awk 'BEGIN{RS="[]+"} {print $0}' count.txt | sort |uniq -c|sort -nr 

方案二:

复制代码
cat count.txt | tr " " "\n" | sort |uniq -c |sort -nr 

小结:

以后遇到词频统计,大致处理流程都是一样的:

  1. 想办法把每个单词单独放一行。

  2. sort + uniq -c

  3. 最后在升序或降序。

5. awk 记录知识小结

基本语法:

复制代码
awk -F"分隔符" '模式 + 动作' 要处理的文件
  • 分隔符,需不需要根据某个符号提取某 1 列。

  • 模式 ,匹配指定行信息 => NR,&& 逻辑与,BEGIN{RS="行与行之间分隔符"}

  • 动作 ,拿到模式匹配的结果要做什么 ⇒ {print $0 $1 $2}

  • NR 存放着每个记录的号(行号)读取新行时候会自动 +1。

  • RS 是输入数据的记录的分隔符,简单理解就是可以指定每个记录的结尾标志。

  • RS 作用 就是表示一个记录的结束。

  • 当我们修改了 RS 的值,最好配合 NR(行)来查看变化,也就是修改了 RS 的值通过 NR 查看结果,调试 awk 程序。

  • ORS 输出数据的记录的分隔符。

awk 学习技巧一则:

大象放冰箱分几步?打开冰箱,把大象放进去,关闭冰箱门。

awk 也是一样的,一步一步来,先修改了 RS,然后用 NR 调试,看看到底如何分隔的。然后通过 sort 排序,uniq -c 去重。

6. awk 字段(列)

每条记录都是由多个区域(field)组成的,默认情况下区域之间的分隔符是由空格(即空格或制表符)来分隔,并且将分隔符记录在内置变量 FS 中,每行记录的区域数保存在 awk 的内置变量 NF 中。

  • FS 既 field separator, 输入字段(列)分隔符。分隔符就是菜刀,把一行字符串切为很多个区域。

  • NF 既 number of fileds,表示一行中列(字段)的个数,可以理解为菜刀切过一行后,切成了多少份。

OFS 输出字段(列)分隔符
  1. awk 使用内置变量 FS 来记录区域分隔符的内容,FS 可以在命令行上通过 -F 参数来更改,也可以通过 BEGIN 模块来更改。

  2. 然后通过 $ 是整数,来取被切割后的区域,$1 取第一个区域,$2 取第二个区域,$NF 取最后一个区域。

案例 1:指定分隔符

复制代码
awk -F":" 'NR>=2&&NR<=5{print $1,$3}' /server/files/awkfile.txt  
bin 1  
daemon 2  
adm 3  
lp 4 

命令说明:

:(冒号)为分隔符,显示第 2 行到第 5 行之间的第一区域和第三区域。

案例 2:提取字符串中的内容

复制代码
echo "I am eric,my qq is 1234567890" >> /server/files/test.txt  
cat /server/files/test.txt  
I am eric,my qq is 1234567890 

思路:

我们用默认的想法一次使用一把刀,需要配合管道的。如何同时使用两把刀呢?看下面的结果。

复制代码
awk -F "[ ,]" '{print $3,$NF}' /server/files/test.txt
eric 1234567890

命令说明:

  • 通过命令 -F 参数指定区域分隔符。

  • [ ,] 是正则表达式里面的内容,它表示一个整体,"一个"字符,既空格或者逗号 (,),合并在一起,-F "[ ,]" 就表示以空格或者逗号 (,) 为区域分隔符。

小技巧:

在动作('{print NF}')里面的逗号,表示空格,其实动作中的逗号就是 OFS 的值,我们会在后面说明。刚开始大家把动作中的都逗号,当作空格即可。

7. awk 分隔符

复制代码
vim /server/files/awkblank.txt
inet addr:192.168.197.133 Bcast:192.168.197.255 Mask:255.255.255.0 

默认分隔符时候

复制代码
awk '{print $1}' /server/files/awkblank.txt  
inet 

指定分隔符时候

复制代码
awk -F "[ :]+" '{print $1}' /server/files/awkblank.txt
# awk -F "[ :]+" '{print $2}' /server/files/awkblank.txt
inet 

命令说明:

  • awk 默认的 FS 分隔符对于空格序列,一个空格或多个空格 tab 都认为是一样的,一个整体。

  • 正常情况下,切割后,数据都是从 $1 开始。

  • 但是如果一个文件的开头有很多连续的空格,然后才是 inet 这个字符。

  • 当我们使用默认的分隔符的时候,$1 是有内容。

  • 当我们指定其他分隔符(非空格)时候,区域会有所变化,$2 才有内容。

8. ORS 和 OFS

  • RS 是输入记录分隔符,决定 awk 如何读取或分隔每行(记录) => 输入数据时,awk 认为行与行之间分隔符。

  • ORS 表示输出记录分隔符,决定 awk 如何输出一行(记录)的,默认是回车换行(\n) => 输出数据时,awk 会在行与行之间设定 ORS,默认 \n

  • FS 是输入区域分隔符,决定 awk 读入一行后如何再分为多个区域 => 输入数据时,awk 认为列与列之间分隔符。

  • OFS 表示输出区域分隔符,决定 awk 输出每个区域的时候使用什么分隔她们 => 输出数据时,awk 会在列与列之间添加一个 OFS 符号,默认是空格。

awk 无比强大,你可以通过 RS,FS 决定 awk 如何读取数据。你也可以通过修改 ORS,OFS 的值指定 awk 如何输出数据。

9. 字段与记录小结

现在你应该会对 awk 的记录字段有所了解了,下面我们总结一下,学会给阶段性知识总结是学好运维的必备技能。

  • RS 记录分隔符,表示每行的结束标志。

  • NR 行号(记录号)。

  • FS 字段分隔符,每列的分隔标志或结束标志。

  • NF 就是每行有多少列,每个记录中字段的数量。

  • **** 符号表示取某个列(字段)`1 $NF`。

  • NF 表示记录中的区域(列)数量,$NF 取最后一个列(区域)。

  • FS(-F) 字段(列)分隔符,-F(FS)":" <==> 'BEGIN{FS=":"}'

  • RS 记录分隔符(行的结束标识)。

  • NR 行号。

  • 选好合适的刀 FS, RS, OFS, ORS。

  • 分隔符 . ==> 结束标识。

  • 记录与区域,你就对我们所谓的行与列,有了新的认识(RS,FS)。

10. awk 模式与动作进阶

awk 模式与动作

接下来就详细介绍下,awk 的模式都有几种:

  • 正则表达式作为模式。

  • 比较表达式作为模式。

  • 范围模式。

  • 特殊模式 BEGIN 和 END。

案例 1:awk 正则表达式匹配整行

复制代码
sed '/正则表达式/'
awk '/正则表达式/'
awk '$0~/正则表达式/'
复制代码
awk -F ":" '/^root/' awkfile.txt 或
awk -F ":" '$0~/^root/' awkfile.txt

命令解析:

awk 只用正则表达式的时候是默认匹配整行的即 $0~/root//root/ 是一样的。

案例 2:awk 正则表达式匹配一行中的某一列

复制代码
awk -F ":" '$5~/shutdown/' awkfile.txt 

命令解析:

  • $5 表示第五个区域(列)。

  • ~ 表示匹配(正则表达式匹配)。

  • /shutdown/ 表示匹配 shutdown 这个字符串。

  • 合在一起,$5~/shutdown/ 表示第五个区域(列)匹配正则表达式 /shutdown/,既第 5 列包含 shutdown 这个字符串,则显示这一行。

案例 3:某个区域中的开头和结尾

知道了如何使用正则表达式匹配操作符之后,我们来看看 awk 正则与 grep 和 sed 不同的地方。

awk 正则表达式:

^ 匹配一个字符串的开头
$ 匹配一个字符串的结尾

数据集

复制代码
cat >>/server/files/reg.txt<<KOF 
Zhang Dandan 41117397 :250:175
Zhao Xiaoyu 390320151 :155:90:201
Meng Feixue 80042789 :250:60:50
Wu Yujia 70271111 :250:80:75
Liu Bingbing 41117483 :250:175
Wang Yangming 3515064655 :50:95:135
Guan Tingting 1986787350 :250:168:200
Li Xinxin 918391635 :175:75:300
Liao Feifan 918391635 :250:175
KOF

说明:

  • 第一列是姓氏。

  • 第二列是名字。

  • 第一列第二列合起来就是姓名。

  • 第三列是对应的 ID 号码。

  • 最后三列是三次捐款数量。

练习题 1: 显示姓 Zhang 的人的第二次捐款金额及她的名字

复制代码
awk -F "[ :]+" '$1~/^zhang/{print $2, $(NF-1)}' reg.txt 

练习题 2: 显示 Xiaoyu 的名字和 ID 号码,并以逗号隔开

复制代码
awk -F "[ :]+" '$2~/^xiaoyu$/{print $1", "$3}' reg.txt 

练习题 3: 显示所有以 41 开头的 ID 号码的人的全名和 ID 号码

复制代码
awk -F "[ :]+" '$3~/^(41)/{print $1,$2,$3}' reg.txt 

练习题 4: 显示所有以一个 D 或 X 开头的人名全名

复制代码
awk -F "[ :]+" '$2~/^D|^X/{print $1,$2}' reg.txt 

练习题 5: 显示所有 ID 号码最后一位数字是 1 或 5 的人的全名

复制代码
awk -F "[ :]+" '$3~/1$|5$/{print $1,$2}' reg.txt 

特别说明: {print $2,$3} 这么写,代表使用默认分隔符(空格连接)。

特别说明: {print $2, $3}

案例 4:取出网卡 ens33/ens160 的 ip 地址

  • VMware16 => ens33

  • VMware17 => ens160

  • 网卡 => eth0

最简单: hostname -I

复制代码
ifconfig ens33 |sed -n '2p' |awk -F"[ ]+" '{print $3}' 或
ifconfig ens160 |sed -n '2p' |awk -F"[ ]+" '{print $3}'

案例 5:取出常用服务端口号

思路: linux 下面服务与端口信息的对应表格在 /etc/services 里面,所以这道题要处理 /etc/services 文件。

复制代码
sed -n '23,30p' /etc/services  
tcpmux 1/tcp # TCP port service multiplexer  
tcpmux 1/udp # TCP port service multiplexer  
rje 5/tcp # Remote Job Entry  
rje 5/udp # Remote Job Entry  
echo 7/tcp  
echo 7/udp  
discard 9/tcp sink null  
discard 9/udp sink null 

从 23 行开始基本上每一行第一列是服务名称,第二列的第一部分是端口号,第二列的第二部分是 tcp 或 udp 协议。

复制代码
awk -F "[ /]+" '$1~/^(ssh)$|^(http)$|^(https)$|^(mysql)$|^(ftp)$/{print $1,$2}'  
/etc/services |sort|uniq  
ftp 21  
http 80  
https 443  
mysql 3306  
ssh 22  

命令解析:

  • | 是或者的意思,正则表达式。

  • sort 是将输出结果排序。

  • uniq 是去重复但不标记重复个数。

  • uniq -c 去重复但标记重复个数。

11. BEGIN 模式与 END 模式

BEGIN 模式

BEGIN 模块再 awk 读取文件之前就执行,一般用来定义我们的内置变量(预定义变量,eg: FS,RS), 可以输出表头(类似 excel 表格名称)。

BEGIN 模式之前我们有在示例中提到,自定义变量,给内容变量赋值等,都使用过。需要注意的是 BEGIN 模式后面要接跟一个 action 操作块,包含在大括号内。awk 必须在输入文件进行任何处理前先执行 BEGIN 里的动作(action)。我们可以不要任何输入文件,就可以对 BEGIN 模块进行测试,因为 awk 需要先执行完 BEGIN 模式,才对输入文件做处理。BEGIN 模式常常被用来修改内置变量 ORS,RS,FS,OFS 等值。

案例 1:可以使用 BEGIN 设定表头信息

复制代码
awk -F: 'BEGIN{print "username","UID"}{print $1,$3}' awkfile.txt
username UID # 这就是输出的表头信息
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
uucp 10

说明:

  • 要在第一行输出一些 username 和 UID,我们应该想到 BEGIN{} 这个特殊的条件(模式),因为 BEGIN{} 在 awk 读取文件之前执行的。

  • 所以结果是 BEGIN{print "username","UID"}, 注意 print 命令里面双引号吃啥吐啥,原样输出。

  • 然后我们实现了在输出文件内容之前输出 "username" 和 "UID",下一步输出文件的第一列和第三列即 {print $1, $3}

  • 最后结果就是 BEGIN{print "username","UID"}{print $1, $3}

案例 2:awk 变量

直接定义,直接使用即可。

awk 命令不仅可以进行数据获取分析,还支持数学运算!

awk 中字母会被认为是变量,如果真的要给一个变量赋值字母(字符串),请使用 == 双引号 ==

复制代码
awk 'BEGIN{a=abcd;print a}'
awk 'BEGIN{abcd=123456;a=abcd;print a}'
123456
awk 'BEGIN{a="abcd";print a}'
abcd 
END 模式

作用: 与 BEGIN 相呼应,在 awk 处理文件结束后,会自动触发 END 模块(只会触发 1 次)。

案例 1:统计 /etc/servies 文件里的空行数量

思路:

a) 空行通过正则表达式来实现:^$

b) 统计数量:

grep 方法:

复制代码
grep "^$" /etc/services | wc -l
16
# grep -c "^$" /etc/services
16

说明:

grep 命令 -c 表示 count 计数统计包含 ^$ 的行一共有多少。

awk 方法:

复制代码
awk '/^$/{i=i+1} END{print "blank lines count:"i}' /etc/services 

提示:

使用了 awk 的技术功能,很常用。

  • 第一步:统计空行个数。

  • /^$/ 表示条件,匹配出空行,然后执行 {i++}i++ 等于 i=i+1)即:/^$/{i=i+1}

  • 我们可以通过 /^$/{i=i+1;print i} 来查看 awk 执行过程。

第二步:

复制代码
awk 编程思想:  
先处理,最后再 END 模块输出  
{print NR,$0} body 模块处理,处理完毕后  
END{print "end of file"} 输出一个结果

案例 2:awk 支持数学运算

复制代码
echo | awk '{print 1+2}'
echo | awk '{print 1+1.5}' 

案例 3:文件 count.txt, 文件内容是 1 到 100(由 seq 100 生成),请计算文件每行值加起来的结果(计算 1 + ... + 100))

思路:

文件每一行都有且只有一个数字,所以我们要让文件的每行内容相加。

回顾一下上一道题我们用的是 i++i=i+1

这里我们需要使用到第二个常用的表达式:

复制代码
i=i+$0 

对比一下,其实只是把上边的 1 换成了 $0

复制代码
awk '{i=i+$0} END{print i}' count.txt 

12. awk 数组

awk 提供了 "数组" 来存放一组相关的值。

awk 是一种编程语言,肯定也支持数组的运用,但是又不同于 c 语言的数组。数组在 awk 中被称为关联数组,因为它的下标既可以是数字也可以是字符串。下标通常被称作 key,并且与对应的数组元素的值关联。数组元素的 key 和值都存储在 awk 程序内部的一张表中,通过一定散列算法来存储,所以数组元素都不是按顺序存储的。打印出来的顺序也肯定不是按照一定的顺序,但是我们可以通过管道来对所需的数据再次操作来达到自己的效果。

复制代码
students[a]="张三"
students[b]="李四"
students[c] = "王五"

数组 索引值 = "元素值",要求:索引值在数组中是不重复的。

白话讲数组: awk 数组就和酒店一样。数组的名称就像是酒店名称,数组元素名称就像酒店房间号码,每个数组元素里面的内容就像是酒店房间里面的人。

假设我们有一个酒店:

复制代码
酒店 <=> hotel

酒店里面有几个房间 110,119,120,114 这几个房间。

  • 酒店 110 房间 <==> hotel[110]

  • 酒店 120 房间 <==> hotel[120]

  • 酒店 119 房间 <----> hotel[119]

  • 酒店 114 房间 <=> hotel[114]

酒店房间入驻客人

  • 酒店 110 房间住着 xiaoyu <=> hotel[110]="xiaoyu"

  • 酒店 119 房间住着 ruxue <==> hotel[119] = "ruxue"

  • 酒店 120 房间住着 dandan <==> hotel[120] = "dandan"

  • 酒店 114 房间住着 bingbing <==> hotel[114] = "bingbing"

案例 1:

复制代码
awk 
'BEGIN{hotel[110]="xiaoyu"; hotel[119]="ruxue"; hotel[120]="dandan"; hotel[114]="bingbing" 
;print hotel[110], hotel[119], hotel[120], hotel[114]} 
xiaoyu ruxue dandan bingbing 

案例 2:

复制代码
awk
'BEGIN{hotel[110]="xiaoyu";hotel[119]="ruxue";hotel[120]="dandan";hotel[114]="bingbing" ;for(i in hotel) print i,hotel[i]}'
110 xiaoyu
120 dandan
114 bingbing
119 ruxue

案例 3:统计域名访问次数

复制代码
www.itheima.com 3 
post.itheima.com 2 
mp3.ittheima.com 1 
# cat file
http://www.itheima.com/index.htm
http://www.itheima.com/1.htm
http://post.itheima.com/index.htm
http://mp3.itheima.com/index.htm
http://www.itheima.com/3.htm
http://post.itheima.com/2.htm 

思路:

  1. 以斜线为刀取出第二列(域名)。

  2. 创建一个数组。

  3. 把第二列(域名)作为数组的下标。

  4. 通过类似于 i++ 的形式进行计数。

  5. 统计后把结果输出。

第一步:查看一下内容

复制代码
awk -F "[\/]+" '{print $2}' file 

第二步:计数

复制代码
awk -F "[\/]+" '{i++;print $2,i}' file

命令说明: i++: i 最开始是空的,当 awk 读取一行,i 自身 +1。

第三步:用数组替换 i

复制代码
awk -F "[\/]+" '{h[$2]++;print $2,h["www.itheima.com"]} ' file 

命令说明:

  1. 将 i 替换成 h[$2]; 相当于我创建了一个数组 h[],然后用 $2 作为我的房间号。但是目前房间里是没有东西的。也就是说 h[$2] = 1 h["www.itheima.com"] and h["post.itheima.com"] and h["mp3.itheima.com"] 但是具体房间里是没有东西的也就是空。

  2. h[$2]++. 就等于 i++:也就是说我开始给房间里加东西;当出现同样的东西,我就 ++

  3. print h["www.itheima.com"]: 意思就是说我开始要输出了。我要输出的是房间号为 "<www.itheima.com>" 里面的内容。这里面的内容最早是空的,随着 awk 读取每一行一旦出现房间号为 "<www.itheima.com>" 的房间时,我就给房间里的内容进行 ++

  4. 综上,输出的结果中,每次出现 <www.itheima.com> 时,h["www.itheima.com"] 就会 ++。因此最后的输出数字是 3。

第四步:输出最终计数结果

复制代码
awk -F "[\/]+" '{h[$2]++} END{for(i in h) print i,h[i]}' file 
复制代码
h[www.itheima.com] = 3
h[post.itheima.com] = 2
h[mp3.itheima.com] = 1

文件内容:

复制代码
http://www.itheima.com/index.html
http://www.itheima.com/1.html
http://post.itheima.com/index.html
http://mp3.itheima.com/index.html
http://www.itheima.com/3.html
http://post.itheima.com/2.html

针对以上文件,一行一行读取

  • 读取第 1 行,发现与 <www.itheima.com> 这个索引对应的数组元素一致,进行 +1。

  • 读取第 2 行,发现与 <www.itheima.com> 这个索引对应的数组元素一致,进行 +1。

相关推荐
吴声子夜歌6 小时前
Shell编程实例——内务及管理任务(一)
linux·运维·shell
吴声子夜歌7 小时前
Shell编程实例——高级脚本编程(一)
linux·运维·网络·shell
吴声子夜歌12 小时前
Shell编程实例——bash的配置与自定义(二)
linux·运维·shell
吴声子夜歌1 天前
Shell编程实例——与解析相关的任务(二)
linux·运维·shell
吴声子夜歌1 天前
Shell编程实例——脚本编程的附加特性
linux·运维·shell
云计算练习生2 天前
什么是内核?操作系统内核到底管哪些事
linux·windows·操作系统·内核·shell
AlfredZhao2 天前
sed 一条命令批量替换 SQL 文件路径
sed
吴声子夜歌4 天前
Shell编程实例——bash入门
linux·运维·shell
Swizard5 天前
Linux 日志神器 journalctl 完全指南:彻底替代传统日志查看方式
shell
吴声子夜歌5 天前
Shell编程——数组
linux·运维·shell