其他文章:
- [linux 常用命令杂记](/archives/linux)
- [Linux命令之——wc](/archives/linuxwc)
- [Linux命令之——sort、uniq](/archives/linuxsortuniq)
---
## awk 命令
> awk 名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。
awk 是一种很棒的语言,最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk 抽取信息后,才能进行其他文本操作。
完整的 awk 脚本通常用来格式化文本文件中的信息。
通常,awk 是以文件的**一行为处理单位**的。awk 每接收文件的一行,然后执行相应的命令,来处理文本。
基本用法:
```bash
# 行匹配语句 awk '' 只能用单引号
awk '{[pattern] action}' {filenames}
```
尽管操作可能会很复杂,但语法总是这样。
- ``pattern``:表示 AWK 在数据中查找的内容(正则表达式,用斜杠括起来)。
- ``action``:是在找到匹配内容时所执行的一系列命令。
- ``{}``:花括号 不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。
==awk 会对输入文件中的每一行都执行一次脚本代码。==
**简单分隔**
```bash
# 打印每一行
awk '{ print $0 }' /etc/passwd
# 以 : 分隔,打印每行第一列
awk -F: '{ print $1 }' /etc/passwd
awk -F":" '{ print $1 }' /etc/passwd
# 以 : 分隔,打印每行第一列第三列
awk -F":" '{ print $1 $3 }' /etc/passwd
# 以 : 分隔,打印每行第一列(空格)第三列
awk -F":" '{ print $1 " " $3 }' /etc/passwd
# 以 : 分隔,打印每行第一列第三列(拼接字符串)
awk -F":" '{ print "username: " $1 "\t\tuid:" $3 }'
/etc/passwd
# 以 : 分隔,打印每行第一列第三列,格式化输出(printf 不换行,故末尾加个\n)
awk -F":" '{printf "username: %-20s \t uid: %-5s\n",$1,$3}' /etc/passwd
```
``-F``参数:指定分隔符,可指定一个或多个,是一个字符串或者是一个正则表达式。
``print``后面做字符串的拼接。
**只查看文件第5~10行的内容**
```bash
# NR 就是行号,从 1 开始
awk '{if(NR>=5 && NR<=10) print $0}' /etc/passwd
```
**过滤字符串**
```bash
echo "I am Sherlock,my lastname is Holmes" > awk_01.txt
# 过滤出 名 和 姓,此处 -F 后指定了分隔符 正则表达式
awk -F '[ ,]+' '{print $3, $7}' awk_01.txt
```
### BEGIN 和 END 模块
有时候可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况, awk 允许您定义一个 BEGIN 块,awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
awk 还提供了 END 块,在处理了输入文件中的所有行之后执行这个块。通常, END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
**统计/etc/passwd的账户人数**
```bash
## count是自定义变量,之前的action{}里都是只有一个print
## 其实print只是一个语句,而action{}可以有多个语句,以;号隔开。
## count,虽然默认是0,但是妥当的做法还是初始化为0:
awk 'BEGIN {count=0;print "[start] user count is ",count} {count=count+1;print $0} END{print "[end] user count is ",count}' passwd
```
**统计某个文件夹下的文件占用的字节数**
```bash
# 表示文件大小的正是第5列,所以是 $5
ll | awk 'BEGIN {size=0;} {size=size+$5;} END{print "[end]size is ",size}'
# 结果以M为单位显示
ll |awk 'BEGIN{size=0;} {size=size+$5;} END{print "[end]size is ",size/1024/1024,"M"}'
```
### awk 运算符
|运算符 |描述|
|-------|----|
|= += -= *= /= %= ^= **= |赋值|
|?: |C条件表达式|
| \|\| |逻辑或|
|&& |逻辑与|
|~ 和 !~ |匹配正则表达式和不匹配正则表达式|
|< <= > >= != == |关系运算符|
|空格 |连接|
|+ - |加,减|
|* / % |乘,除与求余|
|+ - ! |一元加,减和逻辑非|
|^ *** |求幂|
|++ -- |增加或减少,作为前缀或后缀|
|$ |字段引用|
|in |数组成员|
**赋值运算符**
a+5; 等价于 a=a+5; 其他同类。
```bash
awk 'BEGIN{a=5;a+=5;print a}'
```
**逻辑运算符**
```bash
# 判断表达式 a>2&&b>1为真还是为假,后面的表达式同理,输出:0 1
awk 'BEGIN{a=1;b=2;print (a>2&&b>1,a=1||b>1)}'
```
**正则运算符**
```bash
# 输出:ok
awk 'BEGIN{a="100testaa";if(a~/100/) {print "ok"}}'
# 输出:ok
echo|awk 'BEGIN{a="100testaaa"}a~/test/{print "ok"}'
```
**算术运算符**
算术运算符操作时,操作数自动转为数值,所有非数值都变为0。
```bash
# 输出:0, 2
awk 'BEGIN{a="b";print a++,++a}'
# 输出:20, 22
awk 'BEGIN{a="20b4";print a++,++a}'
```
**关系运算法**
``> <``可以作为字符串比较,也可以用作数值比较,关键看操作数如果是字符串就会转换为字符串比较。
两个都为数字 才转为数值比较。
字符串比较:按照ascii码顺序比较。
```bash
# 无输出
awk 'BEGIN{a="11";if(a>=9){print "ok"}}'
# 输出:ok
awk 'BEGIN{a=11;if(a>=9){print "ok"}}'
# 输出:ok
awk 'BEGIN{a;if(a>=b){print "ok"}}'
# 过滤第4列大于1,并且第1列等于第5列的行
awk -F: '$4>1 && $1==$5 {print $1,$3,$4,$6}' /etc/passwd
```
**三目运算符**
```bash
# 输出:ok
awk 'BEGIN{a="b";print a=="b"?"ok":"err"}'
# 输出:err
awk 'BEGIN{a="b";print a=="c"?"ok":"err"}'
```
### 常用 awk 内置变量
|变量| 描述|
|----|------|
|$n| 当前记录的第n个字段,字段间由FS分隔|
|$0| 完整的输入记录|
|ARGC| 命令行参数的数目|
|ARGIND| 命令行中当前文件的位置(从0开始算)|
|ARGV| 包含命令行参数的数组|
|CONVFMT| 数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组|
|ERRNO| 最后一个系统错误的描述|
|FIELDWIDTHS| 字段宽度列表(用空格键分隔)|
|FILENAME| 当前文件名|
|FNR| 各文件分别计数的行号|
|FS| 字段分隔符(默认是任何空格)|
|IGNORECASE| 如果为真,则进行忽略大小写的匹配|
|NF| 一条记录的字段的数目|
|NR| 已经读出的记录数,就是行号,从1开始|
|OFMT| 数字的输出格式(默认值是%.6g)|
|OFS| 输出记录分隔符(输出换行符),输出时用指定的符号代替换行符|
|ORS| 输出记录分隔符(默认值是一个换行符)|
|RLENGTH| 由match函数所匹配的字符串的长度|
|RS| 记录分隔符(默认是一个换行符)|
|RSTART| 由match函数所匹配的字符串的第一个位置|
|SUBSEP| 数组下标分隔符(默认值是/034)|
**内置变量概览**
```bash
awk -F":" 'BEGIN{printf "%10s %8s %8s %6s %10s %4s %10s %10s %10s\n","FILENAME","ARGC","FNR","FS","NF","NR","OFS","ORS","RS";printf "文件名 参数个数 分计行号 分隔符 字段数目 行号 输出分隔符 输出分隔符 记录分隔符 \n";printf "------------------------------------------------------------------------------------------\n"} {printf "%5s %8s %8s %6s %8s %4s %10s %10s %10s\n",FILENAME,ARGC,FNR,FS,NF,NR,OFS,ORS,RS}' /etc/passwd
```
**字段分隔符 FS**
``FS="\t+"``一个或多个 Tab 分隔
```bash
awk 'BEGIN{FS="\t+"}{print $1,$2,$3}' tab.txt
```
``FS="[[:space:]+]"``一个或多个空白空格(**默认的**)
```bash
echo 'we are studing awk now!' > space.txt
awk -F [[:space:]+] '{print $1,$2,$3,$4,$5}' space.txt
awk -F [[:space:]+] '{print $1,$2}' space.txt
```
``FS="[" ":]+"``以一个或多个空格或 : 分隔
```bash
awk -F [" ":]+ '{print $1,$2,$3}' /etc/passwd
```
**字段数量 NF**
```bash
awk -F ":" 'NF==7{print $0}' /etc/passwd
```
**记录数量 NR**
获取网卡 ip 地址(取第2行)
```bash
ifconfig eth0|awk -F [" ":]+ 'NR==2{print $3}'
```
**RS 记录分隔符变量**
将 FS 设置成``"\n"``告诉 awk 每个字段都占据一行。
通过将 RS 设置成 ``""``,还会告诉 awk 每个地址记录都由空白行分隔。
```bash
echo 'Jimmy the Weasel
100 Pleasant Drive
San Francisco,CA 123456
Big Tony
200 Incognito Ave.
Suburbia,WA 64890' > recode.txt
echo '#!/bin/awk
BEGIN {
FS="\n"
RS=""
}
{
print $1 " | " $2 " | " $3
}' > awk.txt
```
```bash
# Jimmy the Weasel,100 Pleasant Drive,San Francisco,CA 123456
# Big Tony,200 Incognito Ave.,Suburbia,WA 64890
awk -f awk.txt recode.txt
```
**OFS 输出字段分隔符**
```bash
awk 'BEGIN{FS=":"}{print $1 "," $2 "," $3}' /etc/passwd
awk 'BEGIN{FS=":";OFS="#"}{print $1,$2,$3}' /etc/passwd
```
**ORS 输出记录分隔符**
```bash
echo '#!/bin/awk
BEGIN {
FS="\n"
RS=""
ORS="\n\n"
}
{
print $1 "|" $2 "|" $3
}' > awk.txt
awk -f awk.txt recode.txt
```
### awk 正则
**规则表达式**
``awk '/REG/{action}' file``
``/REG/``为正则表达式,可以将``$0``中满足条件的记录送入到``action``进行处理。
```bash
# 匹配所有包含root的行
awk '/root/{print $0}' /etc/passwd
# 以冒号作为分隔符,匹配第5个字段是 root 的行
awk -F: '$5~/root/{print $0}' /etc/passwd
# 获取网卡 ip 地址(取第2行)
ifconfig eth0|awk 'BEGIN{FS="[[:space:]:]+"} NR==2{print $3}'
```
**列字符串匹配**
```bash
# 输出第二列包含 "th",并打印第二列与第四列
$ awk '$2 ~ /th/ {print $2,$4}' log.txt
```
> ``~`` 表示模式开始。``//`` 中是模式。
**忽略大小写**
```bash
awk 'BEGIN{IGNORECASE=1} /this/' log.txt
```
**模式取反**
匹配不以``th``开头的行
```bash
awk '$2 !~ /th/ {print $2,$4}' log.txt
# 或者
awk '!/th/ {print $2,$4}' log.txt
```
**布尔表达式**
``awk '布尔表达式{action}' file``
仅当对前面的布尔表达式求值为真时, awk 才执行代码块。
```bash
# 以冒号作为分隔符,第1个字段(第一列)是 root 的行
awk -F: '$1=="root"{print $0}' /etc/passwd
# 以冒号作为分隔符,第1、5个字段(第一、五列)是 root 的行
awk -F: '($1=="root")&&($5=="root") {print $0}' /etc/passwd
```
## 其他用法
- [awk 的 if、循环和数组](https://www.runoob.com/w3cnote/awk-if-loop.html)
- [AWK 数组](https://www.runoob.com/w3cnote/awk-arrays.html)
- [AWK 用户自定义函数](https://www.runoob.com/w3cnote/awk-user-defined-functions.html)
## 一些实例
**计算文件大小**
```bash
ls -l *.txt | awk '{sum+=$6} END {print sum}'
```
**从文件中找出长度大于80的行**
```bash
awk 'length>80' log.txt
```
**打印九九乘法表**
```bash
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'
```

Linux命令之——awk