awk命令学习

前言

awk命令的介绍
AWK是一种 逐行处理 文本文件的语言,是一个强大的文本分析工具,可以实现精确搜索并输出。之所以叫AWK是因为其取了三位创始人Alfred Aho,Peter Weinberger, 和 Brian Kernighan的Family Name的首字符

awk的语法格式:

awk的基本语法;

awk    [options]     'pattern     {action}'          file
awk      选项        `模式/条件        指令`          被处理文档

也可以通过管道符连接从上一条命令的标准输入中读取数据进行二次处理,或者从重定向中读取数据

stdin  | awk    [options]     'pattern     {action}'
前置指令 | awk      选项        '条件/模式       指令'

awk的处理数据来源可以分为从文件中(标准语法)中获取,或者从标准输入中读取,从标准输入中读取数据可以分为以下几种:

  • 1.直接从命令行读取:当 AWK 程序从命令行运行时,它可以直接从终端读取输入。用户在命令行执行 AWK 脚本后,可以手动输入数据,AWK 会将这些数据作为输入处理。
    awk '{print $0}' 
    # 用户可以在执行后输入数据,Ctrl+D (Linux/macOS) 或 Ctrl+Z (Windows) 结束输入
    
  • 2.通过管道读取输入:
    echo "Hello, AWK" | awk '{print $0}'
    
  • 3.通过重定向操作符"<"将文件内容重定向到AWK的标准输入
    awk '{print $0}'< input.txt
    
  • 4.使用Here Document(嵌入文档符)"<<"为AWK提供输入
    awk '{print $1}' << EOF
    V  C
    WO JO
    50 JO
    EOF
    
  • 5.使用Here string "<<<"符将字符串直接传递给AWK的标准输入
    awk '{print $2}' <<< "1 2 3"`
    2
    
  • 6.从特殊文件读取,如" /dev/stdin"
    #`<<<`重定向符效果一定意义上等价于从标准输入文件(/dev/stdin)中读取数据,这两者都是属于标准输入
    awk '{print $2}' <"/dev/stdin" 
    1 2 3
    2
    #但是后面这个命令会一直在命令行等待输入不会结束,还可以继续输入并执行,直到按下ctrl+c
    

除了单一的用法,上面的用法也可以结合使用
如,结合输入和重定向awk '{print $1}' < file.txt > output.txt,

核心概念

记录(Record)

  • 默认情况下,awk通常以换行符作为记录的分割符,因此一个记录通常是文件中的一行,也可以使用内置变量RS修改记录的分割符。

字段(Filed)

  • 默认情况下,awk使用空格或制表符作为字段分割符,将每个记录分割成多个字段(列),也可以通过内置变量FS修改字段分割符,或直接通过选项-F进行修改,字段分别用$1、$2、$3...等来表示第一列、第二列、第三列,用$0来表示所有字段,即整个记录(文本)。

awk的自有变量

在awk中包含一些自有变量,可以作为筛选条件或执行指令的一部分,在学习awk具体用法之前,了解这些自有变量非常有必要。

$1 - $n

  • $1 - $n代表当前一行的第1列到第n列,由字段分隔符(FS)决定,$0表示整个记录

NR

  • NR:Number of Records,记录数,表示记录的数量,通常是行号
    示例:
    #打印user文件的行号(记录数量)
    [root@j tmp]# awk '{print NR}' user 
    1
    2
    #打印user文件的行号和文本打印出来
    [root@j tmp]# awk '{print NR,$0}' user 
    1 root: x:0:0:root:/root:/bin/bash
    2 bin: x:1:1:bin:/bin:/sbin/nologin
    #行号为2的行,打印行号和该行的所有字段
    [root@j tmp]# awk 'NR==2{print NR,$0}' user 
    2 bin: x:1:1:bin:/bin:/sbin/nologin
    

NF

  • NF:Number of Fields,字段数,表示当前记录中字段的数量,注意这里不能理解为列号,之前看到一些误解说NF是列号是并不准确的,在筛选条件(pattern)中,NR==2可以表示第二行,但是NF==2只能表示字段总数为2的行,因为awk是逐行处理的,要想表示第二列可以用$2表示
    示例:
    [root@j tmp]# cat abd.txt 
    abd abc bbc
    ack def
    #这里表示打印字段数为2的行,并不是打印第二列
    [root@j tmp]# awk ' NF==2{print }' abd.txt 
    ack def
    #打印每一行的最后一个字段
    awk '{print $NF}' abd.txt 
    bbc
    def
    #这条命令的执行逻辑如下:
    #当执行该命令时,awk 会逐行读取输入文件 abd.txt 中的内容。
    #对于每一行,awk 会将其按照空格分隔成多个字段,并将每个字段存储为 $1, $2, $3, ...$NF,$NF表示最后一个字段。
    #然后,awk 执行动作 '{print $NF}',即打印每一行的最后一个字段。
    

FS

  • FS:字段分隔符,默认是空格或制表符,一般通过-F选项指定即可,也可以通过设置FS变量指定
    如:BEGIN { FS = ":" }
    打印/etc/passwd文件的第一列
    示例:
    awk 'BEGIN { FS = ":" }{print $1}' /etc/passwd
    root
    bin
    ...
    等同于
     awk -F'{print $1}' /etc/passwd
    root
    bin
    ...
    #(因为这个文件的所有字段都是以冒号分隔开的,而不是空格,所以想要打印出第一个字段需要指定字段分隔符)
    

OFS

  • OFS:输出字段分割符,默认是空格
    示例:
    #假设有一个文本文件 file.txt,其中包含以下内容:
    apple,red,fruit
    banana,yellow,fruit
    tomato,red,vegetable
    #这个文件的每一行都包含用逗号分隔的字段。**如果您想使用 AWK 更改字段分隔符,并打印出这些行**,可以这样做:
    awk 'BEGIN { FS = ","; OFS = " - " } { print $1, $2, $3 }' file.txt
    #这个命令的解释:BEGIN { FS = ","; OFS = " - " }:在 AWK 开始处理任何输入行之前,设置字段分隔符 FS 为逗号(,),并将输出字段分隔符 OFS 设置为 " - "。{ print $1, $2, $3 }:对于每一行输入,打印第一个字段、第二个字段和第三个字段,字段之间用 OFS 指定的分隔符 " - " 分隔。
    执行后的输出将会是:
    apple - red - fruit
    banana - yellow - fruit
    tomato - red - vegetable
    

RS

  • RS:(输入)记录分割符
    示例:
    #更改文件分行(记录)的标准,如:
    cat hello.txt 
    hello,are you ok?
    hello,how are you?
    yes, I’m fine
    awk 'BEGIN{RS=","}{print $1}' hello.txt 
    hello
    are
    how
    I’m
    #这条命令将输入换行符由换行符更改为了逗号","于是awk处理文件时,awk眼中的每一行就变为了
    hello,
    are you ok?hello,
    how are you?yes, 
    I’m fine
    #所以打印出的第一列就变为了上面的结果
    

ORS

  • ORS:输出记录分隔符
    示例:
    将输出记录符(默认是换行符)修改为逗号或其他符号,可以将输出的内容显示在一行
    awk 'BEGIN{ORS=","}{print $1}' abc.txt 
    hello,welcome,[root@j tmp]#
    

其他自有变量

除了上述的变量,awk还有一些其他的自有变量,不同的awk版本可能也有差异,具体可以man查看手册查询,这里附一些基本含义

参数含义
ARGC命令行参数的数目
ARGIND命令行中当前文件的位置 (从0开始算)
ARGV包含命令行参数的数组
CONVFMT数字转换格式 (默认值为%.6g)
ENVIRON环境变量关联数组
ERRNO最后一个系统错误的描述

options 选项:

-F

-F: 定义字段(列)分隔符,不写默认以空格作为分隔符,亦可以指定其他的分隔符,如:

#指定以冒号为分隔符打印/etc/passwd文件中的用户(第一列/第一个字段)
awk -F : '{print $1}'  /etc/passwd 
root
bin
......

-f

-f:从文件中读取awk脚本,这个选项对于复杂的或需要重复使用的awk脚本非常有用

awk  -f  script.awk   file.txt

示例
这个脚本会打印被处理文档的第一列
cat script.awk
{
print $1
}
awk -f script.awk input_file

-v var=value

-v var=value
在程序开始执行前设置变量 var 的值为 value。这对于在 awk 程序中传递外部变量非常有用

awk -v var=value '{print var}' file.txt

其他选项

不同的 awk 实现(比如 gawk、nawk、mawk 等)可能会支持额外的选项,具体取决于版本和系统。要获取特定 awk 实现的完整选项列表,可查阅相应的手册页(man awk 或 man gawk 等)。

参数含义
-m[fr] val(对某些 awk 版本)为内存限制设置最大值。-mr 设置最大记录大小,-mf 设置最大字段数
- -标记选项列表的结束。在这之后的所有参数都被视为文件名,即使它们以破折号开头
-W option用于提供特定于实现的选项。这些选项依赖于你使用的 awk 的版本。例如,GNU awk(gawk)支持如 -W version(显示版本信息)等选项

pattern 条件/模式:

模式匹配:只处理包含特定文本的行
如果匹配了这个模式或条件,awk 会执行大括号中的 操作{action}。
在awk的模式匹配中,大部分都会使用到正则表达式,正则表达式包含很多用法,在 AWK 中,被斜杠" /…/" 包围的任何表达式都被视为正则表达式,这里主要介绍常用的用法,更多详细用法可以参考awk的官方文档
匹配大致包括以下几种:

字符匹配

匹配特定单个字符(/./)

~包含,!~不包含(可以针对特定的列或全文查找)
示例:

#查找文本中包含字符a的行(全文查找)
awk '/a/{print}' file.txt 
#awk默认的action就是print,所以这里也可以简写为 
awk '/a/' file.txt `等价于`awk '$0~/a/' file.txt 
#查找文本中第二列包含字符a的行
awk '$2~/a/' file.txt 
匹配任意单个字符(.)

示例:

匹配包含以 'a' 开始,后面紧跟任意字符,再紧跟 'b' 的行
awk '/a.b/' file.txt 
匹配特定的字符集([ ])

[ ]用于匹配括号内的任意一个字符
示例:

#匹配包含任何一个元音字母(a, e, i, o, u)的行
awk '/[aeiou]/' file.txt 
排除特定字符集([^ ])

[^ ]用于匹配不在方括号范围内的任意一个字符
示例:

#查找文件 file.txt 中包含除数字以外的任何字符的行,并将这些行输出。换句话说,它会匹配包含至少一个非数字字符的行。
awk '/[^ 0-9]/' file.txt
匹配任意一个数字([0-9])

[0-9]表示任意一个数字,等价于\d ,但有些版本的awk不支持\d
示例:

#匹配包含至少一个数字的行。
awk '/[0-9]/' file.txt`
匹配一个任意非数字字符([^ 0-9]或\D)

示例:

匹配包含至少一个非数字字符的行
awk '/[^0-9]/' file.txt 
匹配特定字符重复次数

{n} 表示前一个字符恰好重复 n 次。
示例:

#匹配包含连续三个 'a' 的行(如 "aaa")
awk '/a{3}/' file.txt 

锚点

^:匹配行的开始。$:匹配行的结束。
awk语法中的大部分正则都要写在“/ /”双斜线范围内
示例:

#匹配以数字开头的行
awk '/^[0-9]/'  file.txt
#匹配以非数字字符结尾的行
awk '/[^0-9]$/' file.txt

数量限定符

*: 匹配前面的元素零次或多次
+: 匹配前面的元素一次或多次(至少一次)(在某些 AWK 版本中可能需要通过\+ 来使用)
?: 匹配前面的元素零次或一次(最多一次)(在某些 AWK 版本中可能需要通过 \? 来使用)
{n}:匹配前面的元素刚好n次
{n,}:匹配前面的元素n次及以上
{n,m}: 匹配前面的元素n到m次
示例:

#查找文件中包含任意个a的行
awk '/a*/' file.txt
#查找文件包含一个或多个a的行
awk '/a+/' file.txt`等价于 `awk '/a{1,}/' file.txt
#查找文件包含零个或一个a的行
awk '/a?/' file.txt
#查找文件包含2个a的行
awk '/a{2}/' file.txt
#查找文件包含2个到6个a的行
awk '/a{2,6}/' file.txt
#查找包含两个a的行:
awk '/a{2}/' file.txt
#查找"只"包含两个a的行:
awk '/^([^a]*a[^a]*){2}$/' file.txt
#查询包含2个a的文件,3个,6个都是符合查询条件的,因为都包含了2个a,如果想查找只包含两个a的行
#正则表达式 `^([^a]*a[^a]*){2}$ `的含义是:在一行中,只有两个 ‘a’,并且 ‘a’ 前后可以有0个或多个非 ‘a’ 的字符

分组和引用

分组( ) 将几个项目组成一个单元,这些单元可以通过* 、?、 {n} 、 {n,} 、{n,m}等量词作用于整个组,分组的用法有很多,如上文中查询只包含两个a的行 ^([^a]*a[^a]*){2}$ ,或者是结合awk的函数使用,如:

echo "apple 123" | awk '{match($0, /(a[a-z]+) ([0-9]+)/, arr); print arr[1],  "hello"arr[2]}'
#这里会返回apple,hello123,在这个例子中,使用括号对正则表达式进行分组,分别匹配了单词和数字部分。然后我们使用arr[1]和arr[2]来引用匹配到的两个部分,
#match函数解释:
#Awk的match函数用于在字符串中查找匹配指定模式的子串。match函数的基本语法如下:
`match(string, regexp, array)`
#其中:- `string` 是要进行匹配的字符串。- `regexp` 是要匹配的正则表达式。- `array` 是一个可选参数,用于存储匹配结果的数组
#如果匹配成功,match函数会返回匹配的起始位置,如果没有匹配成功,则返回0。同时,如果提供了数组参数,匹配到的子串会存储在数组中。
#这样就可以通过match函数来匹配字符串并提取需要的部分。

引用\1 、\2等引用之前在正则表达式中匹配的分组内容(某些 AWK 实现可能不支持或行为不同)
在 AWK 中,引用之前在正则表达式中匹配的分组内容(如 \1、\2 等)通常是不直接支持的,这种语法在 sed 或 Perl 中常用于后向引用。
然而,在 AWK 的文本处理中,通常不用这种方式引用正则表达式中的分组。
不过,如果目的是在处理文本时使用某种形式的分组或捕获,可以通过其他方式实现。比如使用 gawkGNU AWK)的 gensub() 函数,这个函数提供了一种方式来进行替换并引用匹配的子表达式。gensub() 是 gawk 的扩展,不是所有 AWK 版本都支持它。
下面是一个使用 gensub() 函数的例子,它展示了如何替换文本并引用分组:

echo "This is a test" | gawk '{ print gensub(/(is) (a)/, "\\1 not \\2", "g") }'
#这个命令会输出:
This is not a test
#在这个例子中,gensub() 的第一个参数是正则表达式,其中 (is) (a) 分别是两个分组;第二个参数是替换字符串,其中 \\1 和 \\2 分别引用第一和第二个括号内匹配的文本;第三个参数 "g" 表示全局替换,即替换所有匹配。
#gensub函数语法:
`gensub(regexp, replacement, how, target)`
#regexp 是要匹配的正则表达式。
#replacement 是用来替换匹配到的部分的字符串。
#how 是一个可选的字符串参数,用来指定匹配的方式,比如"g"表示全局匹配,“1”表示替换第一列,“2”表示替换第二列等等。
#target 是要进行操作的字符串,如果不提供target参数,那么函数会默认使用当前正在处理的文本行作为目标字符串进行匹配和替换

注意,gensub() 函数中引用分组的语法是通过双反斜杠(\)来实现的,这与 sed 或 Perl 中的单反斜杠(\)有所不同。这是因为在字符串中单个反斜杠用于转义,而在 AWK 的字符串中,要表示字面量的反斜杠需要使用两个反斜杠。

特殊字符类(部分 AWK 不支持)

\s:匹配任意空白字符,包括空格、制表符等。
\S:匹配任意非空白字符。
\d:匹配任意数字,等价于[0-9]
\D:匹配任意非数字字符,等价于[^0-9]
\w:匹配任意单词字符(字母、数字或下划线)。
\W:匹配任意非单词字符。

字段匹配&&数值比较

通过比较特定的字段(列)与某个值或模式(条件),来选择对应的记录 (行)
比较使用到的符号:== :等于 != :不等于 > :大于 >=:大于等于 < :小于 <=:小于等于
示例:
字段匹配(一般字段匹配都是用==!=符号)

#匹配第一个字段正好是 "apple" 的记录
awk '$1 == "apple" { print $0 }' file.txt

数值比较

#匹配第二个字段值大于 10 的记录
awk '$2 > 10 { print $0 }' file.txt
#匹配行号为2的记录
awk 'NR==2' file.txt

:如果awk 在处理比较操作时,如果有一个操作数是字符串,那么它会把所有的操作数都当作字符串来处理。在这种情况下,awk 使用的是字符串比较,而不是数值比较。
如:

cat a.txt 
123 666
abc hhh
awk '$1>123' a.txt 
abc hhh
#在字符串比较中,“abc” 被认为大于 “123”,因为字符串的比较是基于 ASCII 值的,而字符 ‘a’ 的 ASCII 值大于字符 
#‘1’,在 awk 中进行字符串比较时,会从左到右,逐个字符地进行比较。上述例子中,awk 会首先比较 ‘a’ 和 ‘1’。因为 ‘a’ 
#的 ASCII 值(97)大于 ‘1’ 的 ASCII 值(49),所以 awk 就会立即判断 ‘abc’ 大于 ‘123’,而不会继续比较 ‘bc’ 和 ‘23’。
#这就像在字典中查找单词一样,我们会首先比较单词的第一个字母,如果第一个字母相同,我们才会比较第二个字母,以此类推。在
#这个例子中,因为第一个字符 ‘a’ 已经大于 ‘1’,所以 ‘abc’ 就被判断为大于 ‘123’,无需进一步比较
#在ASCII表中,数字的ASCII值范围是48(代表"0")到57(代表"9")。这个范围小于字母的ASCII值范围,即65(代表"A")到122(代表"z")

范围模式

awk允许指定一个范围模式,格式为'pattern1,pattern2'其中pattern1是范围的开始,pattern2是范围的结束,如:

awk  '/start/,/end/' file.txt
#这将打印从包含”start“的行到end的行之间的所有行,包括这两行

除了上面这种模式,利用逻辑运算符&&||来组合两个条件也可以达到类似的效果,如:

awk '$1>12 && $2<36' file.txt
#这个命令会打印出 file.txt 中第一个字段大于 12 并且第二个字段小于 36 的所有行

BEGIN和END块

awk的BEGINEND块也叫做awk的“处理时机”,属于awk特殊的模式(pattern),用于匹配输入流的开始和结束,BEGIN块在读取任何输入行之前执行,END块在读取任何输入行之后执行。
BEGIN任务执行一次,在读取文档之前执行;
逐行任务执行n次,读取文档时执行;
END任务执行一次,读取文档之后执行。
示例:

head -3 /etc/passwd > user
#在读取文件之前打印一次行号,读取文档时打印一次行号,读取完成后再打印一次行号
awk -F: 'BEGIN{print NR}{print NR}END{print NR}' user
0
1
2
3
3
#利用awk的处理时机,生成一个查询表
awk -F: 'BEGIN{print "User\tUID\tHome"}{print $1"\t"$3"\t"$6}END{print "总计"NR"行"}' user
User	UID	 Home
root	 0	 /root
bin	     1	 /bin
daemon	 2	 /sbin
总计3行
#\t是制表符

复合条件

可以使用逻辑操作符&&(逻辑与)||(逻辑或)!(逻辑非)来组合条件,示例

#打印第一个字段为 "apple" 且第二个字段大于 10 的所有行
awk '$1 == "apple" && $2 > 10 { print $0 }' file.txt
#匹配第一个字段不是abc且第二个字段是666的行
awk '!($1 == "abc")&& $2==666' file.txt
附-基本正则列表
正则符号描述
^匹配行首
$匹配行尾
[ ]匹配集合中的任意单个字符
[^ ]对集合取反
.匹配任意单个字符(至少一个)
*匹配前一个字符任意次数(不允许单独使用)
(注:从这往后的正则,awk可使用扩展正则,写法更简单
\{n,m\}匹配前一个字符n到m次
\{n\}匹配前一个字符n次
{n,\}匹配前一个字符n次及以上
附-扩展正则表
正则符号描述
+最少匹配一次
最多匹配一次
{n,m}匹配n次到m次
{n}匹配n次
{n,}匹配n次及以上
()组合为整体
|或者
\b单词边界
\w匹配字母、数字、下划线
\s匹配空格、tab键

:这里只列出了一些常见的正则符号,正则表达式的语法可能会因编程语言的不同而略有差异。需要根据你使用的具体编程语言来查阅相应的文档,更多详细的符号参考可参考Shell中的正则表达式 - 阿里云开发者社区JavaScript 正则表达式,Python 正则表达式指南 .NET 正则表达式语言 - 快速参考

action

action:在数据匹配条件(模式)时要执行的一系列命令,被大括号 {} 包围

print

默认的action是{print},如果是默认操作基本可以不写(除了没有任何筛选条件只是单纯的print之外,如awk '{print}' file.txt,这种情况是要写的),除了默认的{print}指令以外,还有以下其他的action:

printf

printf:按照格式化字符串打印变量或文本,类似C语言中的printf.

awk '{printf "%-5d%-5s\n", $1, $2}' file.txt
#%-5d表示第一个字段是一个整数,字段宽度为 5,左对齐。%-5s 表示第二个字段是一个字符串,字段宽度为     5,左对齐,\n表示换行符

printf的语法格式

printf format, value1, value2, ...
#format 是一个字符串,包含了要被写入到标准输出 stdout 的文本。它可以包含嵌入的格式标签,这些标签可以 被随后的附加参数中指定的值替换,并按需求进行格式化。
#… 表示可变参数列表,用于替换格式字符串中的格式标签。
#格式标签的一般形式是 %[flags][width][.precision][length]specifier,具体解释如下:
#flags:标识符,用于控制输出的格式,如 -(左对齐)、+(输出符号)、 (空格)、#(添加前缀)、0(用零 填充)等。
#width:指定输出的最小宽度。
#.precision:对于浮点数,表示小数点后的位数;对于字符串,表示最大输出长度。
#length:长度修饰符,如 h(短整型)、l(长整型)等。
#specifier:指定输出类型的字符,如 d(十进制整数)、f(浮点数)、s(字符串)、c(字符)等。
#例如,printf(“%-5d%-5s\n”, $1, $2) 中的 %-5d 和 %-5s 就是格式标签,它们分别表示左对齐的整数和字符串, 宽度都是 5。
#需要注意的是,awk 的 printf 不会自动在输出的末尾添加换行符,如果需要换行,你需要在格式字符串中显式地添加 \n

变量赋值

可以对内置变量或用户定义变量进行赋值操作 ,如:
count:计数(自定义变量)
示例:

#统计file。txt文件的行数
awk '{count++} END {print count}' file.txt  
#等价于
awk 'END {print NR}' file.txt
#变量赋值
awk 'BEGIN { my_var = "Hello, world!" ; print my_var }'

除了自定义变量,还有一些预定义(内置)变量【FS(字段分隔符)、OFS(输出字段分隔符)、RS(记录分隔符)、ORS(输出记录分隔符)】也可以赋值

#修改输出记录分隔符为两个换行符(默认为一个换行符)
awk 'BEGIN { ORS = "\n\n" } { print $1 }' filename

NF(当前记录的字段数)可以读取这个变量,但是不能直接赋值,可以通过改变记录的内容来间接改变它的值,NR(已读的记录数)可以读取,但是不能赋值

条件语句

if,else执行条件判断和分支 ,switch执行条件判断和分支
语法规则
if…
awk '{if (条件判断) action1 }' 被处理文档,或if…else
awk '{ if (condition) { action1 } else { action2 } }' [input_file] 或if…else if…else
awk '{ if (condition1) { action1 } else if (condition2) { action2 } else if (condition3) { action3 } ... else { actionN } }' [input_file]

#打印长度大于 80 字符的行:
awk '{if (length($0) > 80) {print}}' file.txt
#等价于
awk '{if (length($0) > 80) print}' file.txt   #//if(condition){action}中,如果if语句内部action只有一个动作,那么action的大括号可以省略
#等价于
awk 'length($0) >80 {print}' file.txt   #//如果只是想基于某个单一条件来执行操作,而不需要else部分,那么你可以省略if,直接写条件(pattern)和操作(action)
#等价于
awk 'length($0) >80' file.txt     #//action仅为{print}时,可省略,因为默认的action即为{print}
#length是awk的一个内置函数,它用于返回字符串的长度(即字符数),如果没有给出参数,length将会逐行返回整个记录$0的长度,如:
cat b.txt 
b
bb
awk ' {print length()}' b.txt 
1
2

如果只是想基于某个单一条件来执行操作,而不需要else部分,那么你可以省略if,直接写条件(pattern)和操作(action)。例如:

awk '{if ($1>9) print $1}' file.txt
#等价于
awk '$1>9 {print $1}' file.txt
#注:严格意义上来讲,第一部分只包含action一部分,第二条命令包含pattern和action两部分

如果需要进行更复杂的条件判断和分支执行,例如需要使用 else 或 else if,那么就需要使用 if 关键字,如:

  #判断第一个字段的值是否大于 50,如果是,那么就打印整行内容;否则,就打印 “Value is not greater than 50”。
  awk '{if ($1 > 50) print $0; else print "Value is not greater than 50"}' file.txt
  #根据第一个字段的大小区间输出不同的内容
  awk '{if ($1 > 30) print $1; else if ($1 <= 30 && $1 >= 10) print $2; else print $3;}' file.txt

和for循环结合使用
语法规则
awk '{for(循环语句) if(判断条件)}' 被处理文档

#检查file.txt中每一行的每一个字段,如果有字段的值大于30,就将该字段的值打印出来
awk '{for(i=1;i<=NF;i++)if($i >30)print $i;} ' file.txt
#打印系统中登录root失败的信息
awk '/Failed password for root/{ip[$11]++}END{for(i in ip){print i,ip[i]}}'  
/var/log/secure*

awk中switch语句的语法规则
switch (expression) { case value1: action1 break case value2: action2 break ... default: actionN }
示例:

#示例1
##如果$1等于1,那么就会打印出"Monday",如果等于2,就会打印出"Tuesday",依此类推。如果$1不在1到7的范围内,那么就会打印出"What day?"
awk '{
    switch ($1) {
        case 1:
            print "Monday"
            break
        case 2:
            print "Tuesday"
            break
        case 3:
            print "Wednesday"
            break
        case 4:
            print "Thursday"
            break
        case 5:
            print "Friday"
            break
        case 6:
            print "Saturday"
            break
        case 7:
            print "Sunday"
            break
        default:
            print "What day?"
    }
}' date.txt
#示例2
awk '{
    switch (NR * 2 + 1) {
        case 3:
        case "11":
            print NR - 1
            break
        case /2[[:digit:]]+/:
            print NR
            break
        default:
            print NR + 1
            break
    }
}'
##写在一行的写法(需要在每段句子的结尾添加分号)
awk '{switch (NR*2 +1){case 3: case "11": print NR -1; break; case /2[[:digit:]]+/: print NR; break; default: print NR+1}}' file.txt

#命令解释将NR*2+1的计算结果作为匹配条件,
— —第一段,如果计算结果是数值3或者是字符11,则输出NR-1,实际上case"11":是永远不会触发的,因为NR*2+1的计算结果是数值,不是字符串,然后遇到break,退出这个case条件匹配;
— —然后匹配第二个条件,如果计算结果是2后面至少跟一个数字的情况,则输出NR
/2[[:digit:]]+/几乎和/2[0-9]+/含义一致,意为匹配包含一个数字2,且2后面至少跟一个数字的情况,区别是[[:digit:]]在处理多语言环境和国际化应用时,[[:digit:]]可能表现出比[0-9]更广的适用性,因为在某些语言环境中,数字字符集可能不仅仅是0到9,如阿拉伯语环境:在阿拉伯语中,数字字符与西方的0-9不同,它们有自己独特的表示方式。泰语环境:泰语同样有自己的数字系统,这些数字在Unicode中与0-9是完全不同的字符。
— —如果以上条件都不匹配,则执行default中的操作,输出NR+1(通常,default分支是放在最后的,用于捕获所有未被前面的case捕获的情况。除非你有特别的理由要让它们“贯穿”(即没有break,故意让控制流从一个case流到下一个case))

循环语句

for、while、do…while:执行循环操作
也可以结合break、continue使用
示例
For循环:用于执行固定次数的代码
在awk中,for循环的语法规则是awk {for (init;condtion;increment) {循环体}} 被处理文档
参数解释
init:初始化部分,初始化循环变量或执行一些初始化操作,通常在循环开始之前执行一次
condition:条件部分,用于定义循环继续执行的条件,只有当条件满足时,循环才会继续执行
increment:增量部分,用于更新循环变量的值,通常在循环之后执行
循环体:包含在大括号中的代码块,定义了每次循环迭代时要执行的操作
:若循环体只有一条语句时,可以不被{}包括,其大括号{}可以省略
示例

#打印每行的每个字段(单一语句省略循环体大括号)
awk '{for (i = 1; i <= NF; i++) print $i}' file.txt
#计算和大于50时使用break结束循环
awk 'BEGIN { sum = 0; for (i = 0; i < 20; ++i) { sum += i; if (sum > 50) break; else print "Sum =", sum } }'

除了上述用法,for循环还支持与数组结合以{for(变量名 in 数组名){action}}的形式实现对数组的遍历,AWK本身不直接支持多维数组,但可以多索引拼接的方式实现对模拟多维数组的遍历
示例

#一维数组
##统计访问本地nginx的ip与访问次数
awk '{ip[$1]++}END{for (i in ip) print i,ip[i]}' /usr/local/nginx/logs/access.log | sort -n -k 2
#二维数组
##通过索引拼接遍历二维数组
cat awk.script
BEGIN {{print "City\t\tDate\t\tTemperature"}
    temperature[strtonum("20230101")]["New York"] = 5;
    temperature[strtonum("20230102")]["New York"] = 6;
    temperature[strtonum("20230101")]["Los Angeles"] = 15;
    temperature[strtonum("20230102")]["Los Angeles"] = 16;
    for (date in temperature) {
        for (city in temperature[date]) {
            printf "%-14s%-22s%-15s\n", city, date, temperature[date][city]
        }
    }
}
awk -f awk.script
City		    Date		  Temperature
Los Angeles   20230102            16             
New York      20230102            6              
Los Angeles   20230101            15             
New York      20230101            5    

命令解释:这条命令主要使用datecity两个索引拼接,然后再使用嵌套的for循环来进行查询
strtonum() 函数用于将字符串转换为数字。这个函数可以处理两种格式的字符串:普通的十进制数字和以 0x 或 0X 开头的十六进制数字。当输入是十进制数字时,strtonum() 直接将字符串转换为对应的十进制数值;当输入是十六进制格式时,它会将字符串转换为相应的十进制数值。
在上面的例子中,strtonum函数也可以省略,只使用[“20230102”][“Los Angeles”]也是可以正常执行,因为awk的数组的键支持数字和字符串两种形式,只是使用的话可以看起来更加明确索引为数值的类型

While循环:循环用于执行一段代码,直到满足某个条件为止

#循环打印数字1到5
awk 'BEGIN {i=1; while (i<6) {print i;++i}}' file.txt

++ii++的区别
++i:前置递增,先将i增加1,然后再返回新的i的值,也就是说++i是先自增,然后返回新的值
i++:后置递增,先返回i的值,然后再将i增加1,也就是说i++是先返回当前的值,然后再自增,举个例子

#a++
[root@ecs-c883-0425167 tmp]# a=1
[root@ecs-c883-0425167 tmp]# i=$((a++))
[root@ecs-c883-0425167 tmp]# echo $a
2
[root@ecs-c883-0425167 tmp]# echo $i
1
#++a
[root@ecs-c883-0425167 tmp]# a=1
[root@ecs-c883-0425167 tmp]# i=$((++a))
[root@ecs-c883-0425167 tmp]# echo $a
2
[root@ecs-c883-0425167 tmp]# echo $i
2

Do…while循环:首先执行一次循环体中的命令,然后再检查条件是否满足,如果满足则继续执行,否则则退出循环

#打印1到5
awk 'BEGIN {i=1;do {print i;++i} while (i<=5)}'

Break语句:用于提前结束循环

#计算1到20的和,计算的和大于50的时候使用break结束
awk 'BEGIN {sum=0;for(i=0;i<20;i++){sum+=i;if (sum>50)break;else print "Sum: " sum}}'

注意:在awk中,break语句只能在循环或switch语句内部使用,如下面的命令加一个分号将break独立在for循环之外的话就会报如下的错误

awk 'BEGIN{ sum=0; for (i=0; i<20; i++)    ;    {sum+=i;if(sum>50)break;else print "Sum=" sum}}'
awk: cmd. line:1: error: `break' is not allowed outside a loop or switch
#报错原因:在for循环的结束处加上了一个分号,导致了for循环被独立出来,而大括号内的部分被视为单独的语句块。因此,break语句被放在了大括号内,而不是在for循环内部。

Continue语句:用于在循环体内部结束本次循环,从而直接进入下一次循环。

#输出1到20之间的偶数
awk 'BEGIN {for (i = 1; i <= 20; i++) {if (i % 2 == 1) continue ; else print i} }'

awk中breakcontinue的区别
break:awk会立即退出当前循环,不再处理剩下的记录和字段
continue:awk会立即停止处理当前的记录和字段,开始处理下一条记录和字段

数组操作

awk 支持一维数组,可以用来存储和访问键值对
数组相当于可以存储多个值的变量
数组名[下标]=下标对应的值,如:

awk 'BEGIN {a[1]=10;a[2]=20;print a[1],a[2]}'
10 20
#创建数组a,下标1对应的值是10,下标2对应的值是20,然后使用print打印出下表为1,2数组的值

下标的值也可以不是数字,使用字符串也可以,但是需要加引号,如

awk 'BEGIN{a["abc"]="abcabc";a["xyz"]="xyzxyz";print a["xyz"]}'   

awk数组加上for循环可以实现更加复杂的搜索
for(变量名 in 数组名){print 变量名} //这个格式可以查看数组的所有下标

cat file.txt 
1 2
2 3
5 6
#统计file.txt中每个字段(数字)出现的次数
awk '{for (i = 1; i <= NF; i++) words[$i]++} END {for (word in words) print word, words[word]}' file.txt 
5 1
6 1
1 1
2 2
3 1
#注意上面这条命令虽然能统计文件中每个元素出现的次数,但是这种方式并不保证元素出现的顺序(并不特定的按照文件中字段出现的顺序),因为在awk中,当使用for (word in words)这样的语句来遍历数组时,awk会按照它内部的哈希表结构(一种数据结构)来处理数组元素,这种结构并不关心元素插入的顺序
#如果想要按照特定的顺序排序,可以使用管道符和其他命令再进行二次处理,如:sort -nr -k 2,-n是是以数字形式进行排列,-r是降序排列,-k是指定第几列排序

字符串和数值函数

awk 提供了丰富的字符串处理函数(如 gensub、gsub、sub、split、sustr、index、match)和数学函数(如 int、sin、cos、exp
字符串替换函数
sub、gsub和gensub都是awk中用于替换字符串的函数

  • sub
    sub(regxp,replacement,target)函数会在target中逐行查找第一个匹配regxp的部分,并用replacement替换它,如果省略了target参数,sub函数会在整行$0上操作
    awk '{print sub("123","666")}'
    1 #sub和gsub函数返回的是替换的次数,这里表示替换了一次
    echo "123 123"| awk '{sub("123","666") ;print}'
    666 123
    
  • gsub
    gsub(regxp,replacement,target)函数和sub函数类似,区别是gsub函数会逐行替换掉target所有匹配regxp的部分,而不只是替换第一个,同样,如果省略target,默认会在整行$0上操作

    tagret可以是以下内容之一:
    字段名或字段变量:如$0、$1等,或者是保存字段值的变量
    表达式:可以使用一个字符串表达式,用于指定要进行替换操作的具体文本,如awk 'BEGIN { target = "Hello, World!"; sub("World", "Universe", target); print target }',这个例子中,将表达式"Hello, World!“中的第一个出现的"World"替换为"Universe”。

    echo "123 123"| awk '{print gsub("123","666")}'
    2  #替换掉`target`中所有匹配`regxp`的部分
    echo "123 123"| awk '{gsub("123","666") ;print}'
    666 666
    

注意:sub,gsub,gensub函数都不支持直接修改原文,只是修改print输出的内容,如果想要修改原来的文件,可以将修改后的结果重定向到一个新文件或者原文件进行修改(awk本身并不直接支持修改原文件的功能,它主要设计用于流处理和文本分析,与sed-i选项(就地编辑)不同 ,awk处理文本文件时通常读取输入流到标准输出,不直接修改原文件)

  • gensub
    gensub(regexp,replacement,"how",target)函数 是 gawk 的扩展,不是所有 AWK 版本都支持它,它与subgsub函数类似,也是awk替换字符串的函数,区别如下:
    区别1:它不直接修改target字符串,而是生成一个修改后的新字符串(sub和gsub是返回替换字符串的次数)(这个特性的好处是,在循环执行之后,还可以调用原始的$0内容,与此相对的是gsub就不支持了,缺点是,如果在循环中多次调用gensub()函数,每次调用都会生成一个新的字符串对象,可能会导致内存使用过量,尤其是在处理大量数据时)
    示例:
    #gsub使用示例
    echo "I love apples and apples are great" | awk '{gsub ("apples","oranges"); print $0}'
    I love oranges and oranges are great
    ##可以看到$0已经被修改
    #gensub使用示例
    echo "I love apples and apples are great" | awk '{modified=gensub ("apples","oranges","g"); print "Modified 	:"modified "\nOriginal:" $0}'
    Modified :I love oranges and oranges are great
    Original:I love apples and apples are great
    #这里可以看到$0(Original)并没有改变,而是新生成了一个字符串对象(Modified)
    
    区别2gensub支持通过选项how灵活指定如何替换,其中gG就是全部替换,一个特定的正整数字表示只替换第几次出现的匹配,如数字1就是替换第一次出现的数字匹配,如果不是以上两种情况,都是只替换第一次出现的数字,target可省略,省略默认操作$0
    区别3:gensub还支持使用括号在正则表达式中分组匹配,并在替换字符串replacement中通过&\\n(n是一个数字)引用这些匹配的分组,&是引用整个匹配文本,\\n是用来匹配第n个分组的匹配文本,这为复杂的替换提供了更大的灵活性。
    #分组和引用举例
    ##基本分组和引用
    echo "123 456 789" | awk '{print gensub(/(123)(456)(789)/,"\\3\\2\\1","g")}'
    123 456 789
    ##嵌套分组和引用
    echo "123 456 789" | awk '{print gensub(/((123) (456)) (789)/, "\\2 \\3 \\4 ", "g")}'
    123 456 789 
    #这个分组里面第一分组是 ((123) (456)),它包含了 "123 456"。
    第二分组是 (123)。
    第三分组是 (456)。
    第四个分组是(789)
    所以替换后的结果仍然是123 456 789
    #”&“符号引用整个文本举例
    echo "This is a word in a sentence." | awk '{print gensub(/(word)/,"**&**","g")}'
    This is a **word** in a sentence.
    

字符串切割/截取字符串函数
split、sustr、index、match

  • split
    split(string, array, separator):这是一个用于切割字符串的函数。它会将 string 按照 separator 切割,split() 函数没有明确的返回值,它会将分割后的子字符串存储到指定的数组array 中。

    • string:要分割的字符串
    • arrray:存储分割后字符串的数组
    • separator:用于分割字符串的分隔符
      示例:
    echo "1 2 3" | awk '{split($0, words, " "); for (i in words) {print "Word " i ": " words[i]}}'
    Word 1: 1
    Word 2: 2
    Word 3: 3
    
  • substr
    substr(string, start, length):这是一个用于获取子字符串的函数。它会从 string 中的 start 位置开始,获取长度为 length 的子字符串。

    • string:要提取子串的字符串。
    • start:子串的起始位置。
    • length: 子串的长度。
      示例:
      #对$0进行操作,从2个字符(空格)开始,截取长度为3的子字符串
      echo 1 2 3 | awk '{print substr($0,2,3)}'
      2 
      #对$0进行操作,从第三个字符开始,截取直到末尾的子字符串(length省略就是默认截取到string的末尾
      echo 1 2 3 | awk '{print substr($0,3)}'
      2 3
      #对第三个字段$3进行操作,从$3的第四个字符开始截取直到末尾的子字符串
      echo 123 556 123666 | awk '{print substr($3,4)}'
      666
      
  • index

    • index(string, substring):这是一个用于查找子字符串位置的函数。它会在 string 中查找 substring,并返回 substring 在 string 中的起始索引位置,index函数也常和substr函数结合使用
      示例:
      #打印第二个字段的起始索引位置
      echo 123 456 789 | awk '{print index($0,$2)}'
      5
      #查找数字5出现的索引位置
      echo 123 456 789 | awk '{print index($0,5)}'
      6
      #打印file.txt中第一个字段大于5的数据中第二个字段到最后的部分
      awk '$1 >= 5 {print substr($0, index($0,$2))}' file.txt
      
  • match

    • match(string, regexp [,array]):这是一个用于通过正则表达式匹配字符串的函数。当match函数在string中找到与regexp相匹配的子字符串时,它会返回该匹配子字符串在string中的起始位置(注意,awk中字符串位置的索引从1开始计数)。如果没有找到匹配项,则返回0)。
      此外,awk提供了两个特殊的数组,在调用match函数后,RSTARTRLENGTH,这两个数组会自动更新反映最近一次匹配的结果,(RSTART:最近一次匹配的子字符串在原始字符串中的起始位置RLENGTH:最近一次匹配的子字符串的长度)
      • string:要进行匹配的字符串
      • regexp:正则表达式模式
      • array:可选参数,用于存储匹配到的子字符串的数组,[ ]符号表示参数是可选的,如果省略返回正则表达式匹配的结果的起始索引位置(注意这个参数是gawk引进的,标准的awk版本并不支持,虽然现在大部分Linux默认使用的是gawk)
        示例:
        #测试RSTART和RLENGTH数组
        echo "Hello world" | awk '{
        if (match($0,/wo/))
        {print "Match found at postion",RSTART,"with length",RLENGTH}
        else
        {print "no match found"} }'
        Match found at postion 7 with length 2
        #测试数组
        cat test.txt
        this is wang, not wan
        that is chen, not che
        this is chen, and wang, not wan che
        awk '{match($0,/.+is ([^,]+).+not (.+)/,array);print array[1],array[2]}' test.txt 
        wang wan
        chen che
        chen wan che
        

除了 split 函数外,其他函数并不直接用于切割字符串,而是用于获取子字符串、查找子字符串位置或通过正则表达式匹配字符串

  • 附basshell中的字符处理
    • 字符串处理
      从左往右删,*号就在左边,删一个就是一个#,删全部就是##
      从右往左删,*号就在右边,删一个是一个%,删全部是%%
      字符串删除
      #定义变量
      a=abcdabcd   
      echo ${a#*c}   //从左往右删除到第1个c
      dabcd
      echo ${a##*c}  //从左往右删除到最后1个c
      d
      echo ${a%b*}  //从右往左删除到第1个b
      abcdab
      echo ${a%%b*}  //从右往左删除到最后1个b
      ab
      

输入输出重定向

printprintf 、getline
printprintf 结合重定向符号(>>>)重定向到文件或通过管道传递给其他命令。也可以通过getline读取外部文件内容到变量

  • 输出重定向
    主要通过>将输出重定向到文件和>>将输出追加到文件,awk本身不具备直接修改源文件的功能,但是结合重定向符号可以对原文件进行覆盖以达到修改原文件的目的
    • 示例
      #将输出重定向到文件(如果将重定向指向原文件则可以覆盖原文件)
      awk '{print $1 > "output.txt"}' filename.txt
      #将输出追加到文件
      awk '{print $1 >> "output.txt"}' filename.txt
      #结合输入输出重定向
      grep "somePattern" someFile | awk '{print $1 >> "output.txt"}'
      #这里的管道符号是一种输入重定向,它把一个命令的输出作为了另一个命令的输入
      

getline显示地从输入文件或管道读取下一行。

默认情况下,awk 在处理输入文件时会自动逐行读取文件,每读取一行,就将这行的内容赋给内置变量 $0,并根据空格或者制表符将行分割成字段,分别赋给 $1, $2, …, $NF。这个过程是自动进行的,不需要显式地告诉 awk 去读取下一行。

然而,当你在 awk 脚本中使用 getline 时,你是在显式地请求 awk 从输入中读取下一行。这意味着你正在手动控制输入的读取过程,这将影响 awk 的默认行为。

所谓“显式地请求”这个表述就意味着你在程序中直接写明了要进行某项操作,这个操作不是由程序的默认行为自动完成的,而是需要你明确指定。在awk脚本的上下文中,使用getline函数就是一个典型的例子。

  • getline
    它用于从输入流(文件,管道或标准输入)中读取数据,这个函数的灵活性在于,我们可以在awk的主代码块执行过程中,读取来自其他文件的或命令的数据,而不仅仅是处理当前正常处理的文件。getline的返回值有三种:
    • 1 :成功读取一行
    • 0 :达到文件末尾(EOF)
    • -1 : 发生错误getline
    • getline有以下几种用法
      • 1.不添加任何参数,简单的将值赋值给下一行,并将其赋值给$0,同时更新字段变量$1,$2...,$NF,如果成功读取,返回1,如果达到文件末尾,返回0,如果出现错误,返回-1

        cat a.txt 
        1
        2
        3
        awk '{print "Current line:"$0 ;return_value=getline ; print "Next line:"$0;print "Return value:"return_value}' a.txt 
        Current line:1
        Next line:2
        Return value:1
        Current line:3
        Next line:3
        Return value:0
        #按照awk逐行处理的规则,awk会先print出第一行,然后getline会显式的读取下一行(第二行)并更新$0的值,然后awk会顺序
        #读取下一行(第三行)并将其print出来,然后getline函数会尝试读取下一行,可是已经到达文件末尾,所以返回了0,且并未更
        #新$0,print出来的内容还是之前默认的$0
        
      • 2.将读取的行存入变量,可以将getline读取的行存入到一个变量中,从而不影响$0或其他字段的变量

        awk '{print "Current $0:"$0 ;return_value=getline A; print "After $0:"$0;print "Return value:"return_value;print "A:"A}' a.txt 
        Current $0:1
        After $0:1
        Return value:1
        A:2
        Current $0:3
        After $0:3
        Return value:0
        A:2
        #getline将读取的行存入到了变量A中,从而没有影响到$0的值,但是由于awk的逐行读取规则,默认读取只读取到了第一行和第三行,第二行由getline读取,由于文件只有三行,getline读取下一行的规则,所以变量A只更新了一次
        
      • 3.从文件读取,getline可以将指定文件中读取下一行,这需要将文件名作为字符串参数传递给getline,这种用法需要先用getline<"filename"的形式

        cat b.txt 
        a
        b
        c
        awk '{print $0;getline<"b.txt";print $0}' a.txt 
        1
        a
        2
        b
        3
        c
        
        #从用户输入中读取数据
        awk 'BEGIN { print "Please enter your name:"; getline name < "/dev/stdin"; print "Hello, " name "!" }'
        Please enter your name:
        friend   
        Hello, friend!
        #/dev/stdin 是一个特殊的文件路径,它代表标准输入(stdin)。在类Unix系统中,/dev/stdin、/dev/stdout 和 /dev/stderr 分别代表标准输入、标准输出和标准错误输出。这些特殊的文件路径在命令行和脚本中经常被使用,允许将文件描述符重定向到标准输入、输出和错误输出。
        #或者从字符串中读取
        awk 'BEGIN { info="Sample text"; print "Original string:", info; getline info ; print "Updated string:", info }' <<< "New input"
        Original string: Sample text
        Updated string: New input
        

        注:当使用 getline < “filename” 时,getline 会逐行读取而不是跳行,当使用 getline < “filename” 形式读取数据时,getline 实际上是以逐行的方式读取指定文件的内容。在每次调用时,它读取文件的下一行,而不是像在管道或从标准输入读取时那样跳过已经由 AWK 程序处理的行。这使得 getline < “filename” 成为从特定文件中按顺序读取每一行数据的有效方式

      • 4.从管道或者命令读取getline也可以从一个管道或者命令的输出中读取一行,这通过使用"command"|getline的形式来实现,其中command是任何可以产生输出的shell命令

        awk '{"date"|getline time; close("date");print  "Current time:"time ," Current value:" $0}' a.txt 
        Current time:Thu Mar 21 16:08:19 CST 2024  Current value:1
        Current time:Thu Mar 21 16:08:19 CST 2024  Current value:2
        Current time:Thu Mar 21 16:08:19 CST 2024  Current value:3
        #close()也是awk命令的一个函数,用于关闭getline打开的管道和命令,其语法规则为close("comand"),通过调用close()函数,可
        #以确保及时释怀系统资源,并避免不必要的资源消耗,虽然通常情况下,这些由管道打开的命令会在awk的脚本执行完毕后自动
        #关闭,然而,最佳实践是不再使用这些管道或命令时手动关闭它们,以确保及时释放系统资源,保证编程的健壮性和可靠性
        
      • 5.从标准输入和字符串中读取

        #使用getline从用户输入读取数据:
        awk 'BEGIN { print "Please enter your name:"; getline name < "/dev/stdin"; print "Hello, " name "!" }'
        #或者从字符串读取:
        awk 'BEGIN { info="Sample text"; print "Original string:", info; getline info; print "Updated string:", info }' <<< "New input"
        #等价于
        awk 'BEGIN { info="Sample text"; print "Original string:", info; getline info < "/dev/stdin"; print "Updated string:", info }' 
        Original string: Sample text
        New input
        Updated string: New input
        
      • 更复杂的数据处理逻辑

        #打印非START-END区域的行
        cat example.txt
        START
        line 1
        line 2
        line 3
        line 4
        END
        line 5
        awk '/^START$/ { while ((getline > 0) && ($0 !~ /^END$/)) {} next } { print }' example.txt
        line 5
        

        命令解释:
        这条 awk 命令是用来处理文本文件的,特别是那些包含了以START标记开始,以END标记结束的特定区块的文本文件。它的工作原理如下:
        搜索以START开始的行:
        /^START / 是一个模式匹配,用于寻找完全由字符串 S T A R T 组成的行 。 代 表行的开始, / 是一个模式匹配,用于寻找完全由字符串START组成的行。^代表行的开始, /是一个模式匹配,用于寻找完全由字符串START组成的行表行的开始,代表行的结束,因此^START$确保整行只有START这个词。
        跳过START和END之间的内容:
        当找到一个匹配START的行后,{ while ((getline > 0) && ($0 !~ /^END$/)) {} next } 这部分代码被执行。
        while ((getline > 0) && ($0 !~ /^END$/)) {}:这是一个循环,使用getline函数读取下一行,并检查两个条件:
        getline > 0 确保文件还没有结束,即还有更多行可以读取。
        $0 !~ /^END$/ 确保当前行不是由END单独组成的行。!~是不匹配的意思,因此这个条件是在查找不等于END的行。
        循环的作用是读取并跳过所有从START到END之间的行,但不包括END本身。
        {} next:空的大括号({})表示在循环中不执行任何操作。next是awk的一个控制语句,用于立即停止处理当前记录,并跳到下一条记录的处理流程的开始处。这意味着一旦执行到next,awk将不会执行为该行指定的其它任何动作,直接跳至下一行的开始进行处理。
        打印非START-END区块的行:
        { print }是awk的默认操作,用于打印当前行。因为next语句已经用于跳过所有START到END之间的行,剩下的所有行都将被打印。
        简而言之,这个awk命令的目的是从输入文件example.txt中过滤掉所有位于START和END标记之间的区块,只显示这些区块之外的内容

      • 实际运用

        #ip -s link是一个用于查看网络接口统计信息的命令。通过这个命令,你可以查看系统中所有网络接口(包括物理接口、虚拟接口等)的统计数据,例如接收和发送的数据包数量、错误数量、丢包数量等,但是查询到的单位是以byte为单位,可以通过awk将其单位进行转换
        ip -s link | awk '
        /RX:/ {print $0; getline; rx_bytes=$1; print rx_bytes " bytes = " rx_bytes/1024/1024 " MB";}
        /TX:/ {print $0; getline; tx_bytes=$1; print tx_bytes " bytes = " tx_bytes/1024/1024 " MB";}
      • 注意事项

        • 1.使用getline从管道读取数据时,每次调用都会生成一个新的子进程执行命令,这可能会影响性能。
        • 2.当使用getline从文件读取数据时,要确保文件能够被打开,避免发生错误。
        • 3.使用getline时要注意错误处理和返回值,以避免无限循环和数据丢失的情况。
        • 4.过度使用getline可能会使awk脚本变得难以理解和维护,特别是在复杂的数据处理场景中。

删除数组元素

使用 delete 函数删除数组中的元素,delete函数可以删除数组中的某一个特定元素delete array[index],也可以删除整个数组delete array

#删除数组中的一个元素
awk 'BEGIN {
    myArray["a"] = 1;
    myArray["b"] = 2;
    print "Original size:", length(myArray);
    delete myArray["a"];
    print "New size:", length(myArray);
}'
#删除整个数组
awk 'BEGIN {
    myArray["a"] = 1;
    myArray["b"] = 2;
    print "Before deletion:", length(myArray);
    delete myArray;
    print "After deletion:", length(myArray);
}'

注意事项
在使用 delete 删除整个数组后,如果需要,你可以立即重用同一个数组名称存储新的数据。
delete 语句是 awk 中的一个强大功能,它能够让你灵活地处理数组数据,特别是在需要动态调整数组内容时。
虽然 delete 用法相对直接,但适当的使用它可以显著提高脚本的效率和灵活性。

退出语句

exit:退出 AWK 程序的执行。可以选择性地指定退出状态码。
awk的exit语句用于立即停止执行当前规则并停止处理输出,任何剩余的输入都将被忽略,exit语句的写法如下:
exit [return code]
其中return code可选,

  • 如果向exit提供了参数,其值将会被作为exit进程的退出状态码,如果没有提供return code参数,exit会导致awk返回成功状态(0),在第一次exit语句提供了参数的情况下,如果在END规则中第二次调用exit且没有参数,awk会使用先前调用的退出值
  • 当exit语句在BEGIN规则中执行时,会立即停止处理所有内容,不会读取任何处理输入记录;如果存在END规则,作为执行exit一部分,END规则会被执行
  • 当exit语句在END规则中执行时,会立即导致程序从exit语句处停止(前面的模块不会受到影响)

示例:

#处理逻辑和返回状态码
awk 'BEGIN{print "start";exit 1}{print "loading..."}END{print "end"}'
start
end
echo $?
1
#结合if循环
awk '{ if ($1 == "结束") exit; print $0 } END { print "处理完毕" }' 文件名

注意:为了完全可移植,退出值应在0和126之间,包含两者。负值和127或更大的值可能在不同的操作系统上产生不一致的结果

综合运用

AWK 的动作部分支持几乎任何类型的编程构造,允许创建复杂的文本处理逻辑。

实际使用案例

通过awk命令查询ssh连接失败的ip,并显示ip在哪一列

#通过这个命令可以查询到连接失败的ip都有哪些,并且ip在日志中那一行中的哪一列或者说在哪一个字段
 awk '/Failed password/ {                      
    for(i = 1; i <= NF; i++) {
        if ($i ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/)
         {print i, $i}
    }
}' /var/log/secure*
#/^([0-9]{1,3}\.){3}[0-9]{1,3}$/表示匹配一个ip
#统计ip失败的次数,但是这个命令使用的前提是ip只出现在第11列,实际上有可能出现在其他列
awk '/Failed password for root/{ip[$11]++}END{for(i in ip){print i,ip[i]}}'  /var/log/secure*

除了上面的命令可以统计尝试登陆的ip统计,还有一个更加直接准确的命令,就是直接查询/var/log/btmp文件,这是专门记录登陆失败的ip信息的

last -f btmp | awk '{print $3}' | sort | uniq -c| sort -nr 

在这里插入图片描述

btmp” 可能指的是包含登录失败记录的文件,通常位于Linux系统中的 /var/log/btmp。这个文件记录了登录系统时的失败尝试,包括尝试使用错误的用户名或密码登录系统的记录。这些信息对于系统安全和审计非常重要。
wtmp” 文件是一个包含系统登录记录的文件,通常位于Linux系统中的 /var/log/wtmp。这个文件记录了每个用户的登录和注销时间,以及登录会话的持续时间等信息。系统管理员可以使用这些信息来跟踪用户的活动,进行安全审计以及了解系统的使用情况。

查看file.txt中包含mark的行,并打印其后5行

awk '/mark/{flag=1; count=5} flag && count-->0'file.txt
#这个命令会查找包含mark字符的行,打印出来,并打印随后4行,总共打印5行
#解释:flag是一个变量,当匹配到包含mark字符的行时,flag的值会被设定为1,count的值会被设为4,count --是一个递减操作,每次执行时count的值会递减1,count -->0表示操作会在count大于0时操作,因此这个命令中的条件表达的含义是,当flag的变量为真(即匹配到包含mark的行)且count的值大于0时,执行相应的操作。

更多实际案列待丰富

更多详细用法可以参考awk的官方文档

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值