awk 笔记

Table of Contents

1. awk 匹配模式

语法:

匹配 {动作}
{
    BEGIN {print "hello world"}
}

# 整行匹配
{
    /root/ {print $0}
}

1.1. 正则

1.1.1. 提取正则匹配的结果

echo abc123 | awk 'match($1, /[0-9]+/, a){print a[0]}

2. 变量定义

2.1. 内置变量

FNR,当前文件的所在行。例,打印某一样的数据:

awk -F: 'FNR == 3{print}' /etc/passwd

NR:记录当前共读了多少行。

FNR 和 NR 在多文件情况下能区别出来:

awk '{print FNR}' /etc/passwd /etc/hosts
awk '{print NR}' /etc/passwd /etc/hosts

NF,当前行的列数。例,找出少于 4 列的行:

{print NF}

例,打印最后一列:

{print $NF}

例,行列互换,member.txt 内容如下:

name lx
blog www.shellcodes.org
github http://github.com/1u4nx

将它转为如下格式:

name blog github
lx www.shellcodes.org http://github.com/1u4nx

实现代码如下:

{
    for (i=1; i<=NF; i++) {
        data[i, NR] = $i
    }
}

END {
    for (i=1; i<=NF; i++) {
        for(n=1; n<=NR; n++) {
            printf data[i, n] " "
        }
        print ""
    }
}

2.2. 和 shell 交互

例,正则使用 shell 中的变量

for i in {00..05}
do
    awk '$2~/2016-04-07<SP>'$i'/' web.log
done

3. 列操作

3.1. 列引用

for (i=0; i<3; i++) {
    print $(i) # 引用第 i 列的数据
}

3.2. 排除某列

例,打印数据,并排除第一列:

{
    $1=null;
    print
}

3.3. 以列值为文件名,将相同列值的行输出到同一文件

比如某个单日志文件中记录列多个域名的日志,需要将域名输出到各自的文件中:

# 假设第 5 列是域名
awk '{print >> $5}' website.log

4. 数组

遍历数组方法 1:

awk -F: '{users[FNR]=$1};END{len=length(users);for (i=0;i<=len;i++) {print users[i]}}' /etc/passwd

遍历数组方法 2:

awk -F: '{users[$1]=1};END{for (user in users) {print user}}' /etc/passwd

例,有一个日志文件,还有一个 IP 列表文件,需要从日志中按 IP 列表导出相应的日志:

BEGIN {
    while (getline < "IP列表.txt") {
        ips[$0]=1;
    }

    while (getline < "日志文件") {
        split($0, ft, " ");
        if(ips[ft[6]]) {
            print $0;
        }
    }
}

去重:

awk '!x[$0]++' 文件名

5. 函数

gsub,正则替换:

# 例,手机号打码

echo 'user=lu4nx&phone=13800138000' | awk 'gsub(/phone=[0-9]{11}/, "phone=***")'

6. 输出重定向

重定向输出

基本语法:

print DATA > file
print DATA >> file              # 追加

例,一个 Web 日志文件包含了多个子域名的请求记录,按主域将其重定向到不同文件中:

awk '{if($6~/abc.com/){print >> "abc.log"} else { print >> "cde.log"}}' web.log

管道

基本语法:

print DATA | command

例 1,调用外部脚本查询 Web 日志 IP 列的地理位置:

awk '{print $1 | "python3 query_ip.py"}' web.log

例 2,更复杂一点,将外部命令输出的结果存储到变量中:

awk '{cmd="python query_ip.py "$1; cmd | getline result; close(cmd);print $1,result}' web.log

双向通信

使用“|&”与外部命令建立双向通信的连接,这样就能将外部命令的输出存储到变量中。可以将上面的例子 2 改为双向通信:

awk '{
    cmd="python query_ip.py "$1;
    print $1 |& cmd;            # 通过双向管道将参数传递到外部程序
    close(cmd, "to");           # 关闭单项管道
    cmd |& getline result;      # 用 getline 将外部输出结果保存到变量 result 中
    print $1,result;
    close(cmd)
}'