1

[TOC]

正则表达式与文本搜索

元字符

  • . 匹配任意单个字符(单行模式下不匹配换行符)
  • * 匹配前一个字符任意次
  • [] 匹配范围内任意一个字符
  • ^ 匹配开头
  • $ 匹配结尾
  • \ 转义后面的特殊字符

扩展元字符

  • + 先前的项可以匹配一次或多次。
  • ? 先前的项是可选的,最多匹配一次。
  • | 匹配前面或后面的正则表达式, "或"
  • () 分组

重复

一个正则表达式后面可以跟随多种重复操作符之一。
{n}    先前的项将匹配恰好 n 次。
{n,}   先前的项可以匹配 n 或更多次。
{n,m}  先前的项将匹配至少 n 词,但是不会超过 m 次


find 命令

递归地在目录中查找文件

find [路径...] 表达式

表达式
    查找范围
    -maxdepth <level>    # 目录递归最大层数
    -mindepth <level>    # ?

    按文件名查找
    -name "模式"        # 完整匹配基本的文件名, 使用"通配符"匹配基本的文件名 
    -regex "模式"        # 完整匹配整个路径(并非单单匹配文件名), 使用"正则"匹配基本的文件名
    -iregex "模式"        # 完整匹配整个路径(并非单单匹配文件名) , 使用"正则"匹配基本的文件名, 但不区分大小写
    
    -regextype <reg_type>        # 改变正则表达式语法, 可选: emacs (this is the default),  posix-awk,  posix-basic,  posix-egrep  and  posix-extended
    
    按文件类型查找
    -type 类型        # b 块设备, c 字符设备, d 目录, p 命名管道, f 普通文件, l 符号链接, s 套接字
    
    按时间查找
    # 数字参数
    # +n        在这之前的
    # -n        在这之后的
    # n            正好处于该时刻
    -daystart        # 从当日0点为基准, 而不是当前时刻, 影响下述几种时间类型.
 
    -atime <n>        # Access, 最后访问时间, 单位为天(但实际是以当前时间为基准)
    -ctime <n>        # Change, 文件i节点修改时间, 单位为天
    -mtime <n>        # Modify, 文件内容修改时间, 单位为天
    
    -amin <n>        # 类似 atime, 但单位是分钟
    -ctim <n>        # 类似 ctime, 但单位是分钟
    -mmin <n>        # 类似 mtime, 但单位是分钟
    
    按大小
    -size <n>        # 默认单位是块(512字节), 支持 k(KB), M(MB), G(GB), 可以用 + - 或无符号, 意义同上面的按时间查找.
    
    按归属
    -user <user>    # 按属主
    -uid <uid>        # 按属主的id
    
    动作
    -exec 操作 \;        # 执行时无需确认, {} 作为转义字符会被替换成查找的文件
    -ok 操作 \;        # 类似 -exec, 但是每次操作都会提示确认
    
    运算符(按优先级从高到低排序)
    ()                        # 强制优先
    ! <表达式>                  # 逻辑非, 对<表达式>结果取反, 即不匹配后面条件, 比如 ! -name 表示不匹配指定文件名
    -not <表达式>                  # 逻辑非, 同 ! <表达式>
    <表达式1> <表达式2>        # 逻辑与(默认), 如果前一个<表达式>执行结果为false, 则不会执行后续<表达式>
    <表达式1> -a <表达式2>    # 逻辑与, 同上
    <表达式1> -and <表达式2>    # 逻辑与, 同上
    <表达式1> -o <表达式2>    # 逻辑或
    <表达式1> -or <表达式2>    # 逻辑或
    <表达式1> , <表达式2>        # 前一个表达式的结果不影响后一个表达式的执行
    
cat 仅会修改 access 时间

touch 会同时修改 access, modify, change

chmod 仅会修改 change 时间

注意:

  • 不同参数的前后顺序很重要, 比如 -daystart 需要写在 -atime 等之前, 否则对其不生效.

示例

# 仅删除昨天的日志
find /path/to/log -daystart -mtime 1 -exec rm -v {} \;

grep 命令

文本内容过滤(查找)

查找文本中具有关键字的一行

说明
    若未提供查找的文件名或是 - 则默认是标准输入

语法
    grep [选项] 模式 文件...
    grep [选项] (-e 模式 | -f 包含模式的文件) 文件...
    
选项
    模式
    -G, --basic-regexp            # 使用基本正则(默认)
    -E, --extended-regexp        # 使用扩展正则
    -e 模式, --regexp=模式         # 当模式以 - 开头时, 应使用这种方式
    
    -v, --invert-match            # 反向匹配, 只选择不匹配的行    
    -i, --ignore-case            # 忽略大小写    

    -R, -r, --recursive            # 递归地读每一目录下的所有文件。这样做和 -d recurse 选项等价。
    
    显示内容
    -A <n>, --after-context=<n>        # 打印匹配行的后续 n 行
    -B <n>, --before-context=<n>    # 打印匹配行的前面 n 行
    -o, --only-matching                # 只显示匹配的部分(默认是显示匹配行的所有内容)
    -n, --line-number                # 同时显示行号

    修改显示类型, 不进行通常输出
    -c                                # 打印匹配到多少行
    -l, --files-with-matches        # 打印匹配的文件名
    -L, --files-without-match        # 打印不匹配的文件名
    
    
        
    

注意:

  • 在输入选项时尽量使用引号包围, 避免Shell去解释, 比如 grep \. 实际上模式是 . 也就是匹配任意字符. 而 grep "\."grep '\.' 才是匹配模式 \.

在基本正则表达式中,元字符 ?, +, {, |, (, 和 ) 丧失了它们的特殊意义;作为替代,使用加反斜杠的 (backslash) 版本 ?, +, {, |, (, 和 ) 。

cut 行切割

在文件的每一行提取片段

cut 选项 [FILE]...

选项
    -d, --delimiter <分隔>        # 以指定分隔符来切割, 分隔符必须是单个字符
    -f, --fields <list>            # 输出指定位置的字段, 用逗号分隔多个位置, 位置从1开始

uniq 连续重复行处理

删除排序文件中的"连续"重复行
    默认从标准输入读取, 输出到标准输出

uniq 选项 [INPUT [OUTPUT]]

选项
    -c, --count        # 在首列显示重复数量
    -d, --repeated    # 仅显示重复的行

sort 排序

对文本文件的行排序

sort 选项 [FILE]...

选项
    字段类型
    -n            # 按照数值排序, 默认包含 -b
    -k            # 
    
    -r            # 逆向排序
    
    -b            # 忽略开头的空格

seq 产生数字序列

产生数字序列

语法
    seq [OPTION]... LAST
    seq [OPTION]... FIRST LAST
    seq [OPTION]... FIRST INCREMENT LAST

tac 倒序显示

tac [选项] <文件=STDIN>

行编辑器

非交互式, 基于行操作的模式编辑.

sed 行编辑器

sed 是单行文本编辑器, 非交互式.

sed 的模式空间, 其基本工作方式

  1. 将文件以行为单位读取到内存(称作模式空间)
  2. 使用sed的每个脚本依次对该行进行操作
  3. 打印模式空间的内容并清空
  4. 读取下一行, 重复执行上述步骤

sed 的空间示意图:

image-20200302180855354

  • 模式空间的内容默认会输出, 并清空
  • 保持空间的默认内容是 \n

Tip

  • 使用 ; 可以替换多个 -e

模式空间 pattern space

s 替换命令
替换

sed [选项] '[<寻址>]s<分隔符><old><分隔符><new><分隔符>[<标志位>]' [文件...]        # 简单示例: sed 's/old/new/'

参数
    old        # 支持正则表达式
    分隔符      # 可以采用 / 也可以采用其他来避免与正则匹配内容冲突, 比如 ~ @ 等

寻址(默认是所有行)    
    !            # 对寻址取反, eg. "2,4!d" 表示不删除2~4行
    <n>            # 只替换第<n>行
    <n1>,<n2>    # 区间: 从<n1>到<n2>这几行        eg. /12\/Apr\/2020/,/13\/Apr\/2020/      正则同样可以使用这种区间寻址
    1,<n>        # 替换从开始到第<n>行
    <n>,$        # 替换从第<n>到结束的这些行
    /正则/       # 仅替换符合此正则匹配到的行
                # eg.   sed '/正则/s/old/new/' 
                # eg.  sed '/正则/,$s/old/new/'    (正则可以和行号混用)表示从匹配到的正则那行开始到结束都替换 
                # 寻址可以匹配多条命令, 注意大括号. eg.   /regular/{s/old/new/;s/old/new/}    
                # 比如nginx日志需要筛选出 14号这天的: sed -n '/14\/Apr\/2020/,/15\/Apr\/2020/ p' xx.log
 
标志位(默认是只替换第1个匹配项)
    /g        # 替换所有匹配项(默认只替换第1个匹配项)
    /<n>    # <n>是一个数字, 表示每行只替换第<n>个匹配项
    /p        # 打印模式空间的内容(即匹配的行), 通常会和 -n 一起使用, 如果不和 -n 一起使用, 会导致匹配的行多输出依次.
            # eg. sed -n 's/old/new/p'                 # 此时仅打印匹配的行
    /w <file>    # 将模式空间的内容(即匹配到的行)写入到指定文件


选项
    -r, --regexp-extended            # 使用扩展正则表达式, 包括圆括号分组及回调.
    -e script, --expression=script    # 指定多个执行脚本时, 每个脚本前用一个 -e. 可以使用 "分号" 简写
                                    # Eg. -e 's/old1/new1/' -e 's/old2/new2/'
    -f script-file, --file=script-file    # 加载脚本文件
    -i[<后缀>], --in-place[=<后缀>]  # 修改原始文件, 当指定后缀时则会生成对应的备份文件. 也可以直接输出重定向输出到其他文件
    -n, --quiet, --silent            # 默认不自动打印

示例
    # 使用扩展正则表达式
    sed -r 's/old/new/' [文件]...

    # 执行多个脚本
    sed -e 's/old/new/' -e 's/old/new/' [文件]...
    sed 's/old/new/;s/old/new/'                        # 使用分号隔开不同脚本

    # 将结果写回文件保存
    sed -i 's/'
    sed -i,.bak 's/'
    
    # 圆括号分组及回调
    echo "a213123t" | sed -r 's/a(\d*)/b\1/g'        # b213123t
d 删除命令
删除当前"模式空间的内容", 并放弃后面的命令, 读取新的内容并重新执行sed
    改变脚本的控制流, 读取新的输入行
    (由于模式空间的内容被删除了, 因此d后面的脚本没法执行, 会略过)
    (使用 s 替换成空内容, 但本质上这一行内容还在, 依旧会执行后续脚本, 会输出)

sed '[<寻址>]d' [文件...]

寻址
    同 s 命令

示例
    sed '1d'            # 删除第一行
    sed '/^\s*#/d'        # 删除 # 开头的行
a 追加命令
在匹配行的下一行插入内容

sed '[<寻址>]a <插入内容>'

示例
    sed '1i haha'        # 在原先第1行前面插入 haha
i 插入命令
在匹配行在上一行插入内容

sed '[<寻址>]i <插入内容>'

示例
    sed '2i haha'        # 在原先第二行前面插入 haha
c 改写命令
将匹配行替换成指定内容
    指定匹配连续几行时, 只会替换1次    # sed '2,5c <替换内容>'

sed '[<寻址>]c <替换内容>'

示例
    sed '2c hehe'        # 将第2行替换成 "hehe"
r 读文件并插入 (从文件读取改写内容)
在匹配行下面分别插入指定文件中的内容

sed '[<寻址>]r <文件名>'

示例
    sed '$r afile' bfile > cfile        # 将 afile 内容追加到 bfile 结尾并合并成新的文件 cfile
常用于合并多个文件
w 写文件 ?
?

sed '[<寻址>]w <文件名>'
p 打印

与 替换命令 s 的标志位 /p 不一样.

输出匹配的行(不禁止原始的输出)

sed [选项] '[<寻址>]p'

示例
    sed -n '<寻址>p'        # 只打印匹配的行
n 提前读入下一行, 并指向下一行
sed 'n'

示例
    cat <<EOF | sed 'n'
    1
    2
    3
    4
    5
    EOF
    
    # 输出(由于未作任何操作, 因此原样输出)
    1
    2
    3
    4
    5
    
    # ----------------------

    cat <<EOF | sed -n 'n;p'
    1
    2
    3
    4
    5
    EOF
    
    # 输出
    2
    4

正常模式

img

使用n命令后

img使用n命令后

图来源: https://blog.51cto.com/studyi...
q 退出命令
遇到匹配项, 在处理完该项后退出

sed '[<寻址>]q'

示例
    sed '2q'        # 在打印完第二行后退出
    sed '/root/q'    # 在匹配到某一行有 root 后退出

打印前N行的一个比较

  • sed 10q filename 读取前10行就退出
  • sed -n '1,10p' filename 逐行读入全部, 只显示1-10行, 更耗时.
= 打印行号
打印出当前行号(单行显示)
    不影响正常的打印

sed '='

示例
    echo 'a' | sed '='        # 打印结果, 第一行 "1", 第二行 "a"

多行模式空间 pattern space

多行模式空间改变了 sed 的基本流程, 通过使用 N, P, D 读入并处理多行.

主要应对配置文件是多行的情况, 比如 json 或 xml.

综合示例

a.txt 内容如下
1
2
3
4
5
6
7
8
9

# ------------------- 示例1 ---------------------#
sed 'N;N;s/\n/\t/g;' a.txt
1    2    3
4    5    6
7    8    9


# ---------------------示例2 打印梯形结构 -------------------#
# 关键在于利用 D 改变控制流
sed -n 'P;N;s/\n/\t/;s/^/\n/;D' a.txt
1
1    2
1    2    3
1    2    3    4
1    2    3    4    5
1    2    3    4    5    6
1    2    3    4    5    6    7
1    2    3    4    5    6    7    8
1    2    3    4    5    6    7    8    9
b.txt 内容如下
hell
o bash hel
lo bash


# -------------------- 示例 hello bash 替换成 hello sed --------------#
sed 's/^\s*//;N;s/\n//;s/hello bash/hello sed\n/;P;D;' b.txt
hello sed
hello sed
N 将下一行加入到模式空间
读取时, 将下一行加入到模式空间
    两行视为同一行, 但中间有个换行符 \n
    此时多行模式 . 可以匹配到除了末尾的其他换行符

sed 'N'

示例
    sed = <文件> | sed 'N;s/\n/\t/'        # 在每行前面加入行号

使用N命令后

img

图来源: https://blog.51cto.com/studyi...
D 删除模式空间中的第一个字符到第一个换行符
注意
    会改变控制流, 不再执行紧随的后续命令, 会再次回到脚本的初始处(但不清空模式空间)

sed 'D'
P 打印模式空间中的第一个字符到第一个换行符
注意
    仅仅是打印, 并不会删除打印的部分.

sed 'P'

保持空间 hold space

注意:

  • 保持空间在不存储东西时, 它里面的默认内容是 \n

    因此第一次一般是用 h 覆盖掉保持空间的 \n
  • 保持空间的内容只能临时存储和取出, 无法直接修改

综合示例

# 下述多个都实现了 tac 倒序显示的效果
# 思路: 每次将本轮正确的结果保存在保持空间
cat -n /etc/passwd | head -n 6 | sed -n '1!G;$!x;$p'
cat -n /etc/passwd | head -n 6 | sed -n '1!G;h;$p'
cat -n /etc/passwd | head -n 6 | sed '1!G;h;$!d'
cat -n /etc/passwd | head -n 6 | sed '1!G;$!x;$!d'
cat -n /etc/passwd | head -n 6 | sed -n '1h;1d;G;h;$p';
cat -n /etc/passwd | head -n 6 | sed -n '1h;1!G;h;$p';

sed '=;6q' /etc/passwd | sed 'N;s/\n/\t/;1!G;h;$!d'

# --------------------- 显示结果 --------------------#
6    sync:x:5:0:sync:/sbin:/bin/sync
5    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
4    adm:x:3:4:adm:/var/adm:/sbin/nologin
3    daemon:x:2:2:daemon:/sbin:/sbin/nologin
2    bin:x:1:1:bin:/bin:/sbin/nologin
1    root:x:0:0:root:/root:/bin/bash
h 和 H 将模式空间内容存放到保持空间
  • h 是覆盖
  • H 是追加

注意: 该操作不会清空模式空间(需要清空模式空间可以用 d)

g 和 G 将保持空间内容取出到模式空间
  • g 是覆盖
  • G 是追加

注意: 该操作不会清空保持空间

x 交换模式空间和保持空间内容

awk 行编辑器

awk 是一种解释型编码语言, 常用于处理文本数据.

awk 主要是对 sed 的一个补充, 常用于在sed处理完后对相应结果进行调整并输出.

awk 版本

  • awk: 初始版本
  • nawk: new awk, 是 awk 的改进增强版
  • gawk: GNU awk, 所有 GNU/Linux 发行版都包含 gawk, 且完全兼容 awk 与 nawk

    实际 centos 用的就是 gawk

参考文档:

awk 和 sed 的区别

  • awk 更像是脚本语言
  • awk 用于"比较规范"的文本处理, 用于统计数量并调整顺序, 输出指定字段.
  • 使用 sed 将不规范的文本处理为"比较规范"的文本

awk 脚本的流程控制

  1. BEGIN{} 输入数据前例程, 可选
  2. {} 主输入循环
  3. END{} 所有文件读取完成例程

个人理解的执行顺序

  1. 执行开始块(可选) BEGIN{}
  2. 若存在主体块 {} 或结束块 END{}`, 则打开文件并读入数据

    如果文件无法打开会在此时报错.

    主体块允许存在多个, 比如根据不同的匹配模式, 写多个主体块.

  3. 若存在<寻址>/pattern/, 则会依次匹配, 通过则对该记录执行 {}
  4. 读完所有记录后, 执行结束块(可选) END{}
特殊情况, 脚本只包含 BEGIN{} 时, 在执行 awk 命令后面传入参数(非文件名), 此时不会导致报错, 因为不会执行到步骤2(即尝试打开文件).

如果脚本只包含 BEGIN{命令} , 好像可以缩写成 awk '命令' ??

语法


语法
    awk [选项] 'awk脚本内容' [ -- ] [文件...]        # 其中任意一部分都是可选的
    awk [选项] -f <awk脚本文件> [ -- ] [文件...]    # 不直接在命令行中书写 awk 脚本, 而是从指定文件读取

选项
    -f <脚本文件>, --file=<脚本文件>     # 从指定脚本文件读取 awk 脚本, 而不是默认地从第一个命令行参数读取.
    -F, --field-separator <分隔符>        # 字段分隔符可以使用正则表达式, 是空格 " ", 也可以在awk脚本中用 FS="," 来修改这一行为
    -v <var>="val", --assign <var>="val"  # 直接为awk脚本中的变量赋值, 比如 -v suffix=yjx, 然后代码中直接就存在 suffix 这个变量了, 且值是 yjx


    --dump-variables[=<file="awkvars.out">]        # 将awk中的所有全局变量及其值导出到指定文件中.
                                      

awk脚本的块
    开始块
        BEGIN {}         # 特殊模式: 读取输入数据前
    主体块
        表达式 {}              # 若匹配上才执行后续的 action              
                        # eg. 
                        #        `$1 == "top" {}`
                        #        `NR >= 2 {}`
                        #        'length($0)'
        /正则/ {}           # 正则匹配, 若匹配上才执行后续的 action        
                        # eg. 
                        #        /^menu/    只处理 menu 开头的记录 
                        #        /cost/    只处理本行内容中包含 "cost" 的记录
        !/正则/ {}      # 正则不匹配
        组合模式 {}         # 一个 组合模式 通过与(&&),或(||),非(|),以及括弧来组合多个表达式
        {}                # 每读取一行数据则执行后续的 action
        模式1,模式2 {}     # 范围模式(range pattern)匹配从与 模式1 相匹配的行到与 模式2 相匹配的行(包含该行)之间的所有行,对于这些输入行,执行 语句 。
    结束块
        END {}            # 特殊模式: 读取完毕后

    
    
    
主体块的action
    print    打印(若未配置则默认是 print)
    next    对于本行的处理, 跳过后续步骤表达式
    {...}    执行 {} 中的脚本






示例.
    awk [选项] 'BEGIN{} [<条件>] {} END{}' 文件...    # 其中任意一部分都是可选的
    

                                     # BEGIN{} 输入数据前例程
                                     # {} 主输入循环, <寻址> 是应用于 {} 的
                                     # END{} 所有文件读取完成例程
                                     
    awk -f <脚本.awk>            # 从文件中加载执行的命令, 注意<寻址>要和 { 写在同一行, 不然好像没生效?
                             # 示例 xx.awk
                             # BEGIN {
                             #        ...
                             # }
                             # /过滤条件/ {
                             #         ...
                             # }
                             # END {
                             #        ...
                             # }



字段引用
    $0                # 表示记录整行
    $1 $2 ... $n     # 表示第1~第n个字段
    NF                # 标识 $0 被分割的字段数
    $NF                # 表示最后一个字段, NF 变量表示字段的数量, 比如当前共5个字段, 则 $NF 等价于 $5.

                                    
    
    
简单示例 
    awk -F ',' '{print $1,$2,$3}' filename     # 逗号分隔, 打印前3个字段
    echo "menuentry 'CentOS Linux (5.5.6) 7 (Core)' --class centos" | awk -F "'" '{print $2}'    # 提取出内核版本

修改字段或NF值的联动效应

注意以下几种操作

  • 修改 $0 会根据 FS, 重新划分字段并自动赋值给 $1, $2, ... , NF.

    $0=$0 也会触发重新划分字段的操作.

  • 修改 $1, $2, ... , 会根据 OFS 重新生成 $0, 但不会重新分割.

    即使是 $1=$1 也会触发上述操作, 当然是用 NF=NF 也是可以的.

    # 利用该特性重新生成去除行首行尾空格, 压缩中间空格的效果
    echo "   a   b   c  " | awk '{NF=NF; print $0;}'
    
    输出
    a b c
  • 赋值给不存在的字段, 会新增字段并按需使用空字符串填充中间的字段,并使用 OFS 重新计算$0
  • 增加 NF 值,将使用空字符串新增字段,并使用 OFS 重新计算 $0
  • 减少 NF值,将丢弃一定数量的尾部字段,并使用OFS重新计算$0

正则字面量

这里有个地方容易被坑到!!!

任何单独出现的 /pattern/ 都等价于 $0 ~ /pattern/, 这个在将正则表达式赋值给变量时特别容易被坑, 举例:

  • if(/pattern/) 等价于 if($0 ~ /pattern/)
  • a = /pattern/ 等价于将 $0 ~ /pattern/ 的匹配返回值(0或1)赋值给 a
  • /pattern/ ~ $1 等价于 $0 ~ /pattern/ ~ $1 ,表示用 $1 去匹配0或1
  • /pattern/ 作为参数传给函数时,传递的是 $0~/pattern/ 的结果0或1
匹配成功时返回1, 失败返回0.

举例

# 这边直接用于匹配没什么问题
awk 'BEGIN { if ("abc" ~ "^[a-z]+$") { print "match"} }'

#输出
match



awk 'BEGIN { if ("abc" ~ /^[a-z]+$/) { print "match"} }'

#输出
match


# 这边将其赋值给其他变量, 此时其实 regex = $0 ~ /^[a-z]+$/, 也就是值 0
awk 'BEGIN { regex=/^[a-z]+$/; print regex; if ("abc" ~ regex) { print "match"} }'

#输出
0



awk 'BEGIN { regex="^[a-z]+$"; print regex; if ("abc" ~ regex) { print "match"} }'

#输出
^[a-z]+$
match

在 awk 中书写正则可以用 /[0-9]+/ 也可以用

/[0-9]+/
匹配方式:"str" ~ /pattern/或"str" !~ /pattern/
匹配结果返回值为0(匹配失败)或1(匹配成功)
任何单独出现的/pattern/都等价于$0 ~ /pattern/
if(/pattern/)等价于if($0 ~ /pattern/)
坑1:a=/pattern/等价于将$0 ~ /pattern/的匹配返回值(0或1)赋值给a
坑2:/pattern/ ~ $1等价于$0 ~ /pattern/ ~ $1,表示用$1去匹配0或1
坑3:/pattern/作为参数传给函数时,传递的是$0~/pat/的结果0或1
坑4.坑5.坑6…

内置变量

awk 中可以看作是在一个独立的系统空间中, 因此也有其特殊的系统变量.

注意:

  • 字段的引用不能加 $, 这点与Shell不一样, 不然就变成获取记录中某个字段的值了.
控制 AWK 工作的预定义变量
  • FS (field separator)输入数据的分隔符, 默认值是空格

    awk -F ","
    # 等价于
    awk 'BEGIN {FS=","}'        # 在读入文件之前设置字段分隔符
  • OFS (output field separator)输出字段分隔符(默认是空格)

    awk 'BEGIN {OFS=","}'
  • FIELDWIDTHS 以指定宽度切割字段而非按照 FS
  • FPAT 以正则匹配, 将匹配到的结果作为字段, 而非按照 FS 或 FIELDWIDTHS 划分. 理解为 re_match, 而不是 re_split

    FPAT = "([^,]+)|(\"[^\"]+\")"
    
    # 上述 FPAT 用于分割以逗号分隔的 csv 文件, 若使用双引号包裹的则视为是一个字段(忽略其中的逗号).
    # 比如对于数据:       abc,"pqr,mno"
    # $1 值为 abc
    # $2 值为 "pqr,mno"
    https://stackoverflow.com/que...
  • RS (record separator)记录分割符(默认是 \n)

    该变量通常在 BEGIN 块中修改, 修改该变量可以控制 awk 每次读取的数据的范围(默认是读取一行), 读取到的记录是不包含记录分隔符的.

    • RS 设置为单个字符: 直接使用该字符来分割记录
    • RS 设置为多个字符: 视为正则(非兼容模式), 使用该正则来分割记录.
    awk 'BEGIN {RS=":"}'        # 将记录分割符设置为 :   , 这样每次遇到 : 时就视为一条记录分别处理.

特殊读取需求

    • RS="" : 按段落读取(这个特性有用)
    • RS="\0" : 一次性读取所有数据, 但有些特殊文件包含了空字符 \0
    • RS="^$" : 真正的一次性读取所有数据, 因此 ^$ 匹配的是空文件
    • RS="\n+" : 按行读取, 但忽略空行
    • ORS (output row separator) 输出记录分隔符(默认是 \n)

      awk 'BEGIN {OFS=":"}'
    • CONVFMT 表示数据转换为字符串的格式, 默认值是 %.6g
    • OFMT 表示数值输出的格式, 默认值是 %0.6g, 标识有效位(整数部分加小数部分)最多为6.
    • IGNORECASE 控制是否对大小写敏感, 当该变量设置时, 忽略大小写. 在分割记录时也受该变量影响.

      awk 'BEGIN{IGNORECASE=1} /amit/' marks.txt
    携带信息的预定义变量
    文件与行号
    • FILENAME 当前被处理的文件名

      BEGIN {} 块中, 该变量是未定义的.
    • NR (number of rows)记录的行号

      会一直累加, 就算处理多个文件, 该行号也会一直累加.

    • FNR (file number of rows)记录的行号(处理不同文件时会重置)

      当处理多个文件时, 切换到不同文件则该值会重置掉.

    • NF (number of fields)字段数量

      最后一个字段内容可以用 $NF 取出
    • ARGIND 用于处理多个文件时, 表示当前正在处理的文件的顺序(从 1 开始)

      awk 'ARGIND==1 {if(FNR>3)print FNR,$3 } ARGIND==2 {if(FNR>1)print FNR,$2} ARGIND==3 {if(FNR<3)print FNR,$NF}' s.log t.log s.log
    • RT (Record Termination) 实际记录分割符

      当 RS 设置为多个字符(正则)时, 在每条记录被读取并分割后, RT 变量会被设置为实际用于划分记录的字符.

    命令行与环境参数
    • ARGC 命令行位置参数个数("选项"是不包含在内的)
    • ARGV 命令行位置参数数组

      • ARGV[0] 值是命令名本身, 即 awk
      • ARGV[1] 是传入的第1个参数
      • 范围: ARGV[0] ~ ARGV[ARGC - 1]
    • ENVIRON 存放系统环境变量的关联数组

      awk 'BEGIN{print ENVIRON["USER"]}'        # 输出: shell 变量 "USER"
    • PROCINFO 关联数组, 保存进程相关的信息

      # 打印 awk 进程的Id
      awk 'BEGIN { print PROCINFO["pid"] }'
    • ERRNO 用于存储当 getline 重定向失败或 close 函数调用失败时的失败信息.

    表达式

    赋值操作符
    • =

      • = 的左右是可以有空格的.
      • 字符串拼接中的空格会被忽略

        Eg. var = "hello" "world"

        实际上 var 值是 "helloworld", 没有中间的空格

      • 若是拼接两个字符串变量, 则使用字符串字面量隔开即可.

        s3=s1""s2

    • ++

      支持前置递增和后置递增

    • --

      支持前置递减增和后置递减

    • +=
    • -=
    • *=
    • /=
    • %=
    • ^=
    算数操作符
    • +
    • -
    • *
    • /
    • %
    • ^
    位操作
    • AND 按位与操作
    • OR 按位或操作
    • XOR 按位异或操作
    关系操作符
    • <
    • >
    • <=
    • >=
    • ==

      注意, 判断两个值是否相等要用 ==, 而不是赋值运算符 =

    • !=
    • ~ 字符匹配正则表达式
    • !~
    布尔操作符
    • &&
    • ||
    • !

      使用 ! 时注意使用括号将相关表达式括起来, 避免写错.

    三元运算符

    condition expression ? statement1 : statement2

    匹配运算符

    除了块的条件匹配外, 还可以用于 if 判断之类的.

    • ~ 匹配指定正则表达式的, 用于主体块的条件匹配

      # 仅处理包含 hello 文本的行
      awk '$0 ~ "hello"' xx.txt
      
      
      # 注意这里正则是用 / / 包围起来, 而不是双引号
      awk 'BEGIN { if ("[abc]" ~ /\[.*\]/) { print "match";}}'
      #输出
      #match
      
      # 注意这里用双引号括起来时, 里面用了双斜杠来处理正则的 [
      awk 'BEGIN { if ("[abc]" ~ "\\[abc\\]") { print "match";}}'
      #输出
      #match
      
    • !~ 不匹配指定正则表达式的, 用于主体块的条件匹配

      # 仅处理不含 hello 文本的行
      awk '$0 !~ "hello"' marks.txt

    条件和循环

    注意:

    • 表达式结果: 0为false, 1为true

      这个与 Shell 是相反的.
    • 影响控制的语句: break, continue

    综合示例

    cat kpi.txt
    
    user1 70 72 74 76 74 72
    user2 80 82 84 82 80 78
    
    
    #-------------- 计算每行数值的总之和平均值 -----------#
    awk '{total=0; avg=0; for (i=2;i<=NF;i++) {total+=$i;} avg=total/(NF-1); print $1,total,avg;}' kpi.txt
    
    user1 438 73
    user2 486 81
    if 条件语句
    if (表达式) {
    
    } else if (表达式) {
    
    } else {
    
    }
    执行多条语句要用 {}, 只有一条语句时可忽略.
    for 循环
    for (初始值; 循环判断条件; 累加) {
    }
    while 循环
    while (表达式) {
    
    }
    do 循环
    do {
    
    } while(表达式)

    数组

    普通数组
    • awk 的数组实际上是关联数组, 可通过下标(数字实际上也是字符串)依次访问

      比如 arr[1]arr["1"] 实际上是对同一个 key 操作
    • 支持多维数组

      gawk(可以认为是 awk 的加强版, 替代版, 至少 centos 的 awk 实际就是 gawk) 支持真正意义上的多维数组.

      awk 还有一种更"原始"的使用一维数组模拟多维数组的, 但在 gawk 中已经没必要了.

    # 定义
    ## 下标可以是数字(视为字符串)或字符串
    数组名[下标] = 值
    
    
    # 遍历
    for (变量 in 数组名) {
        数组名[变量]            # 获取对应数组值
    }
    
    
    # 删除数组
    delete 数组
    # 删除数组元素
    delete 数组[下标]
    
    
    # 判断数组中是否存在指定"键"
    if (key in array)
    # 判断数组中是否不存在指定"键", 注意这里额外加了一个括号, 不能省略了
    if (!(key in array))
    命令行参数数组
    • ARGC 命令行位置参数个数
    • ARGV 命令行位置参数数组

      • ARGV[0] 值是命令名本身, 即 awk
      • ARGV[1] 是传入的第1个参数
      • 范围: ARGV[0] ~ ARGV[ARGC - 1]

    示例

    cat argv.awk
    
    内容
        BEGIN {
            for (i=0; i<ARGC; i++) {
                print ARGV[i];
            }
            print ARGC;
        }
    
    
    # --------------------------------#
    awk -f argv.awk  afile 11 22 33
    
    输出
        awk        # ARGV[0]
        afile    # ARGV[1]
        11        # ARGV[2]
        22        # ARGV[3]
        33        # ARGV[4]
        5        # ARGC
    此处不会报错是因为awk脚本中只包含 BEGIN {} 部分, 因此不会将参数视为文件名并尝试打开.
    数组函数
    • length(数组) 获取数组长度
    • asort(数组a[, 数组b, ...]) 对数组a的值进行排序,并且会丢掉原先键值(重新生成数字递增的 key 来替代), 并将结果赋予数组 b (若未传, 则直接修改数组 a).
    • arorti(数组a[, 数组b, ...]) 对数组a的键进行排序, 并将结果赋予数组 b (若未传, 则直接修改数组 a).

    函数

    算术函数
    • sin()
    • cos()
    • atan2(y,x)
    • exp(x) 返回自然数 e 的 x 次方
    • sqrt() 平方根
    • log(x) 计算 x 的自然对数
    • int() 转换为整数(忽略小数部分)
    • rand() 伪随机数, 范围 [0,1), 默认使用 srand(1) 初始化随机种子.

      若不使用 srand() 会发现每次获取的所谓随机数都是一样的.

      srand(); print rand();

    • srand([seed]) 重置随机种子, 默认种子采用当前时间的 epoch 值(秒级别)
    位操作函数
    • compl(num) ` 按位求补
    • lshift(num, offset) 左移N位
    • rshift(num, offset) 右移N位
    字符串函数

    awk 中涉及字符索引的函数, 索引位都是从 1 开始.

    注意, 不同 awk 版本, 函数参数个数是有可能不一样的.

    • sprintf(format, expr1, ...) 返回格式化后的字符串

      示例: a = sprintf("%10s\n", "abc")

    • length(s) 返回字符串/数组的长度
    • strtonum(str) 将字符串转换为十进制数值

      如果 str 以0开头,则将其识别为8进制

      如果 str 以0x或0X开头,则将其识别为16进制

    • tolower(str) 转换为小写
    • toupper(str) 转换为大写
    • 查找

      • index(str,substr) 在目标字符串中查找子串的位置, 若返回 0 则表示不存在.
      • match(string, regexp, array) 字符串正则匹配, 将匹配结果保存在 arr 数组中.

        变量 RLENGTH 表示 match 函数匹配的字符串长度.

        变量 RSTART 表示 match 函数匹配的字符串的第一个字符的位置.

        awk 'BEGIN { if (match("One Two Three", "re")) { print RLENGTH } }'        # 输出 2
        
        awk 'BEGIN { if (match("One Two Three", "Thre")) { print RSTART } }'    # 输出 9
        cat test
        # this is wang,not wan
        # that is chen,not che
        # this is chen,and wang,not wan che
        
        awk '{match($0, /.+is([^,]+).+not(.+)/, a); print a[1],a[2]}' test
        # wang  wan
        # chen  che
        # chen  wan che
    • 替换

      • gsub(regx,sub [,targe=$0]) 全局替换, 会直接修改原始字符串, 返回替换成功的次数.

        如果 target 使用 $0, $... 等, 那么替换成功后会使用 OFS 重新计算 $0

        这边 sub 不支持反向引用, 只能使用 & 来引用匹配成功的部分

      • sub(regx,sub [,targe=$0]) 只替换第一个匹配的, 会直接修改原始字符串, 返回替换成功的次数.
      • gensub(regx, sub [, how [, target]]) 不修改原字符串, 而是返回替换后的字符串. 可以完全替代 gsubsub

        how: 指定替换第几个匹配, 比如 1 表示只替换第一个匹配, gG 表示全局替换

        这是 gawk 提供的函数, 其中 sub 支持使用 \N 引用分组匹配, 或 &, \0 来标识匹配的整个结果.

        awk 'BEGIN {
            a = "111 222"
            b = gensub(/(.+) (.+)/, "\\2 \\1, \\0, &", "g", a)
            print b
        }'
        
        
        # 输出
        222 111, 111 222, 111 222
    • 截取

      • substr(str,pos,num=剩余所有) 从指定位置开始截取一个子串
    • 分割

      • split(str, arr [, 正则分隔符=FS]) 字符串分割为数组, 并将其保存到第2个参数中, 函数返回值是分割的数
      • patsplit(str, arr[, 正则分隔符=FPAT]) 使用正则捕获匹配的字符串, 并将其保存到第2个参数中.

    若不清楚的, 可以在 man awk 中搜索相应关键字

    时间函数
    • systime 返回当前时间戳(秒级)
    • mktime("YYYY MM DD HH mm SS [DST]") 根据给定的字符串格式, 返回其对应的时间戳

      # 格式: 年 月 日 时 分 秒
      awk 'BEGIN{print mktime("2020 10 10 17 51 59")}'
    • strftime([format [, timestamp[, utc-flag]]]) 将时间戳(默认是当前时间)转换为字符串表示

      awk 'BEGIN {print strftime("Time = %Y-%m-%d %H:%M:%S")}'
      
      输出
      
      Time = 2020-10-12 17:53:37
      SN描述
      %a星期缩写(Mon-Sun)。
      %A星期全称(Monday-Sunday)。
      %b月份缩写(Jan)。
      %B月份全称(January)。
      %c本地日期与时间。
      %C年份中的世纪部分,其值为年份整除100。
      %d十进制日期(01-31)
      %D等价于 %m/%d/%y.
      %e日期,如果只有一位数字则用空格补齐
      %F等价于 %Y-%m-%d,这也是 ISO 8601 标准日期格式。
      %gISO8610 标准周所在的年份模除 100(00-99)。比如,1993 年 1 月 1 日属于 1992 年的第 53 周。所以,虽然它是 1993 年第 1 天,但是其 ISO8601 标准周所在年份却是 1992。同样,尽管 1973 年 12 月 31 日属于 1973 年但是它却属于 1994 年的第一周。所以 1973 年 12 月 31 日的 ISO8610 标准周所在的年是 1974 而不是 1973。
      %GISO 标准周所在年份的全称。
      %h等价于 %b.
      %H用十进制表示的 24 小时格式的小时(00-23)
      %I用十进制表示的 12 小时格式的小时(00-12)
      %j一年中的第几天(001-366)
      %m月份(01-12)
      %M分钟数(00-59)
      %n换行符 (ASCII LF)
      %p十二进制表示法(AM/PM)
      %r十二进制表示法的时间(等价于 %I:%M:%S %p)。
      %R等价于 %H:%M。
      %S时间的秒数值(00-60)
      %t制表符 (tab)
      %T等价于 %H:%M:%S。
      %u以数字表示的星期(1-7),1 表示星期一。
      %U一年中的第几个星期(第一个星期天作为第一周的开始),00-53
      %V一年中的第几个星期(第一个星期一作为第一周的开始),01-53。
      %w以数字表示的星期(0-6),0表示星期日 。
      %W十进制表示的一年中的第几个星期(第一个星期一作为第一周的开始),00-53。
      %x本地日期表示
      %X本地时间表示
      %y年份模除 100。
      %Y十进制表示的完整年份。
      %z时区,表示格式为+HHMM(例如,格式要求生成的 RFC 822或者 RFC 1036 时间头)
      %Z时区名称或缩写,如果时区待定则无输出。
    其他函数
    • getline

      请参照下方的 "getline" 部分.

    • close(xxx [, from|to])

      关闭文件、shell进程, 可以仅关闭某一端.

      close(xxx, "to") 表示关闭该管道的写入端, close(xxx, "from") 表示关闭该管道的输出端.

      注意!!!! awk 中任何文件都只会在第一次使用时打开, 之后都不会再重新打开(而是从上次的读取位置继续). 因此只有在关闭之后, 再次使用时才会重新打开.

    • next 跳过对当前记录的后续处理.

      会回到 awk 循环的头部, 读取下一行.

    • nextfile 停止处理当前文件, 从下一个文件开始处理.
    • return xx 函数返回值
    • system("shell命令") 执行 shell 命令, 并返回退出的状态值, 0表示成功.
    • flush([output-expr]) 刷新打开文件或管道的缓冲区

      如果没有提供 output-expr,fflush 将刷新标准输出。若 output-epxr 是空字符串 (""),fflush 将刷新所有打开的文件和管道。

    • close(expr) 关闭文件句柄 ???

      awk 'BEGIN {
          cmd = "tr [a-z] [A-Z]"
          print "hello, world !!!" |& cmd        # "&|" 表示双向管道通信
          close(cmd, "to")                    # 关闭其中一个方向的管道, 另一个是 "from" 方向
          cmd |& getline out                    # 使用 getline 函数将输出存储到 out 变量中
          print out;
          close(cmd);                            # 关闭管道
      }'
      
      输出
      
      HELLO, WORLD !!!
    • exit <code=0> 终止脚本
    自定义函数
    function 函数名(参数) {
        awk 语句
        return awk变量
    }

    注意

    • 自定义函数的书写不能再 BEGIN{}, {}, END{} 的里层

    getline

    getline 函数用于读取一行数据.

    根据不同情况, 返回值不一样.

    • 若读取到数据, 返回 1.
    • 若遇到 EOF, 返回 0.
    • 发生错误, 返回负数. 如-1表示文件无法打开,-2表示IO操作需要重试(retry)。在遇到错误的同时,还会设置 ERRNO 变量来描述错误.
    awk '{print $0; getline; print $0}' marks.txt 
    
    # 建议使用 getline 时判断一下是否读取成功
    awk 'BEGIN {getline; if ((getline) > 0) {...}}'
    从当前文件读取
    • 使用 getline 不带参数时, 表示从当前正在处理的文件中立即读取下一条记录并保存到 $0, 同时进行字段分割(分别存到 $1, $2,...), 同时会设置 NF, RT, NR, FNR, 然后继续执行后续代码.
    • 执行 getline <变量名> 时, 会将读取结果保存到对应变量中, 而不会更新 $0, 也不会更新 NF, $1, $2, ..., 此时仅仅会更新 RT, NR, FNR.
    从其他文件读取
    • 执行 getline < "filename" 表示从指定文件读取一条记录并保存到 $0 中(同时进行字段分割), 及 NF. 至于 NR, FNR 则不会更新.

      每次读取记录后会自动记录读取的位置.

      配合 whilegetline 的返回值可以遍历完文件.

      注意 getline < abcgetline < "abc" 是两码事, 一个是从 abc 变量指向的文件读取, 一个是读取 abc 文件
    • 执行 getline 变量名 < "filename" 表示从指定文件读取一条记录并保存到指定变量中.
    从 shell 命令输出结果中读取
    • cmd | getline:从Shell命令 cmd 的输出结果中读取一条记录保存到 $0

      会进行字段划分,设置变量 $0 NF $N RT,不会修改变量 NR FNR

    • cmd | getline var:从Shell命令 cmd 的输出结果中读取数据保存到 var

      除了 varRT,其它变量都不会设置

    如果要再次执行 cmd 并读取其输出数据,则需要close关闭该命令, 示例:。

    # 若屏蔽下方的 close 函数, 则再次读取该 cmd 时为空. 因此需要关闭先.
    
    awk 'BEGIN {cmd = "seq 1 5"; \
    while((cmd | getline) > 0) {print}; \
    close(cmd); \
    while((cmd | getline) > 0) {print}; \
    }'

    可以方便地使用 shell 给 awk 中变量赋值

    awk 'BEGIN {get_date = "date +\"%F %T\"";  \
    get_date | getline cur_date; \
    print cur_date; \
    close(get_date);
    }'
    
    输出
    2020-10-13 19:14:17
    将数据传给 shell 处理完(coprocess), 再读取

    这里要利用 coprocess, 也就是 |& 操作符.

    awk 可以利用 |& 将一些不好处理的数据传给 shell 来处理后, 再从 shell 中读取结果, 继续在 awk 中处理.

    使用 shell 的排序功能

    # sort 命令会等待数据写入完毕后(即 EOF 标记)才开始排序, 因此实际是在执行 close(CMD, "to") 时才开始执行.
    awk 'BEGIN {
    CMD = "sort -k2n";
    print "6 66" |& CMD;
    print "3 33" |& CMD;
    print "7 77" |& CMD;
    close(CMD, "to");
    while ((CMD |& getline) > 0) {
        print;
    }
    close(CMD);
    }'
    
    
    输出:
    3 33
    6 66
    7 77

    使用注意:

    • awk-print |& cmd 会直接将数据写进管道, cmd 可以从管道中获取数据
    • 强烈建议在awk_print写完数据之后加上 close(cmd,"to") ,这样表示向管道中写入一个EOF标记,避免某些要求读完所有数据再执行的cmd命令被永久阻塞.

      关闭管道另一端可以使用 close(cmd, "from")

    • 如果 cmd 是按块缓冲的,则 getline 可能会陷入阻塞。这时可将 cmd 部分改写成 stdbuf -oL cmd 以强制其按行缓冲输出数据

      CMD="stdbuf -oL cmdline";awk_print |& CMD;close(CMD,"to");CMD |& getline

    高级输出

    print 输出重定向
    • print "..."
    • print "..." > 文件名 重定向输出
    • print "..." >> 文件名 重定向输出

      这玩意比使用 system 函数再调用 echo 快了好几个量级
    • print "..." | "shell 命令" awk 将创建管道, 并启动 shell 命令. print 产生的数据放入管道, shell 命令则从管道中读取数据.
    • print "..." |& "shell 命令"; "shell 命令" | getline 和上面的 | 不同之处在于, 这里是将数据交给 Coprocess, 之后 awk 还需要再从 Coprocess 取回数据.

      Coprocess 执行 shell 命令的时候, 结果是不输出到标准输出的, 而是需要从管道中自行读取.

    支持重定向到

    • 标准输入 /dev/stdin
    • 标准输出 /dev/stdout
    • 标准错误 /dev/stderr

    若 print 输出后发现后续的管道命令没有内容, 那其实是因为 awk 的输出存在缓存, 可使用 fflush() 函数刷新缓冲区.

    关于 ||& 使用上区别的示例

    # 这里用的是 |, 执行结果直接输出到标准输出
    awk 'BEGIN {
    cmd = "tr \"[a-z]\" \"[A-Z]\""
    print "hello" | cmd
    > }'
    
    
    # 这里用的是 |&, 执行结果需要手动从 Coprocess 管道读取
    awk 'BEGIN {
        cmd = "tr \"[a-z]\" \"[A-Z]\""
        print "hello" |& cmd;
        close(cmd, "to");
        cmd |& getline line;
        print line;
        close(cmd);
    }'
    
    
    输出
    HELLO
    printf 格式化
    printf(format, value1, value2, ...)
    
    参数
        format    格式  
            %c        (将ASCII转换为)字符
            %s        字符串
            %d,%i    整数    
            %e,$e    科学计数法表示
            %f,%F    浮点数
            %g,%G    浮点数, 会移除对数值无影响的 0
            %o        无符号八进制
            %u        无符号十进制
            %x,%X    无符号十六进制
            %%        百分号
        
                        
    注意: 输出时不会自动换行
    
        
    示例
        printf("total pay for %s is $%.2f\n", $1, $2 * $3)
    
        # %-8s        字符串, 8个字符宽度, 左对齐
        # %6.2f        浮点数, 6个字符宽度, 保留2位小数
        printf("%-8s %6.2f\n", $1, $2 * $3)
    参考: https://awk.readthedocs.io/en...

    若仅仅是为了格式化字符串(不输出), 可以用 sprintf 函数.

    format 支持的转义序列

    • 换行符 \n
    • 水平制表符 \t
    • 垂直制表符\v

      理解为输出光标垂直向下移动一行
    • 退格符 \b

      理解为输出光标后退一格.
    • 回车符 \r

      理解为光标会回退到当前行的开头(常用于覆盖本行)
    • 换页符 \f

      这个效果...得试一下才行

    format 的 % 的可选参数

    • 宽度

      只有当字段的宽度比要求宽度小时该标示才会有效, 默认使用空格字符填充.

      printf("%10d", 1)
      
      
      输出
               1
    • 前导零 0

      只有当字段的宽度比要求宽度小时该标示才会有效

      awk 'BEGIN {printf("%010d", 1)}'
      
      
      输出
      0000000001
    • 左对齐 -

      % 和数字之间使用 - 符号即可指定左对齐

      awk 'BEGIN {printf("%-10d", 1)}'
      
      输出
      1
    • 符号前缀 +

      使用 +, 在输出数字时, 将其正负号也输出.

      awk 'BEGIN {printf("%+10d", 1)}'
      
      输出
              +1
    • 保留进制标识 #

      awk 'BEGIN {printf("%#o    %#X\n", 10, 10)}'
      
      输出
      012    0XA

    Examples

    打印出当前的可用内核列表

    并显示序号

    awk -F "'" '/^menuentry/ {print x++,$2}' /boot/grub2/grub.cfg
    
    输出
    0 CentOS Linux (5.5.6) 7 (Core)
    1 CentOS Linux (3.10.0-1062.12.1.el7.x86_64) 7 (Core)
    2 CentOS Linux (3.10.0-957.el7.x86_64) 7 (Core)
    3 CentOS Linux (0-rescue-d64aa77b8f014365aa6557986697df9c) 7 (Core)

    统计当前tcp的各个状态及数量

    netstat -ntp | awk '/^tcp / {S[$6]++} END{for (i in S) print i,S[i];}'
    
    输出
    TIME_WAIT 108
    ESTABLISHED 154

    统计每个接口的访问时间

    time cat 20_10_*.txt | grep "request cost" | gawk -v suffix=_test -f ../stat_method.awk

    stat_method.awk

    BEGIN {
        DEBUG = 1
    
        methodRecordFile = "method_record"suffix".csv"
        methodStatsFile = "method_stats"suffix".csv"
    
        if (DEBUG) {
            methodRecordFile = "/dev/stdout"
            methodStatsFile = "/dev/stdout"
        }
    
        print methodRecordFile
        print methodStatsFile
    }
    
    {
        method=substr($10,2,length($10)-2)
        method=substr(method,1,length(method)-7)
        timeStr=$1" "$2
        timeMs=$6 + 0.01
        uid=substr($11,2,length($11)-2)
    
        msLevel=(int(timeMs/100) + 1) * 100
    
        T[method][msLevel]++
    
        if (!(method in Stats)) {
            Stats[method]["name"] = method
            Stats[method]["max"] = 0        
            Stats[method]["mean"] = 0
            Stats[method]["count"] = 0
            Stats[method]["sum"] = 0
            Stats[method]["sumX2"] = 0
            Stats[method]["min"] = 9999999999999
        }
    
        if (timeMs > Stats[method]["max"]) {
            Stats[method]["max"] = timeMs
        }
    
        if (timeMs < Stats[method]["min"]) {
            Stats[method]["min"] = timeMs
        }
    
        Stats[method]["sumX2"] += timeMs * timeMs
    
        Stats[method]["count"]++
        Stats[method]["sum"] += timeMs
    
        if (NR % 10000 == 0) {
            print "已处理 "NR" 条记录"
        }
    }
    
    END {
        print "----------- 总共处理 "NR" 条记录 -----------"
    
    
        print "method,msLevel,count" > methodRecordFile
        # print "method,msLevel,count"
        for (m in T) {        
            for (l in T[m]) {
                print m","l","T[m][l] >> methodRecordFile
                # print m","l","T[m][l]
            }
        }
    
        print "----------- done -----------"
        
        print "method,min,max,mean,sd(标准差),count,sum(秒)" > methodStatsFile
        printf("%-30s\tmin\tmax\tcount\tmean\tsd(标准差)\t,sum(秒)\n", "method")    
        for (m in Stats) {
            Stats[m]["mean"] = Stats[m]["sum"] / Stats[m]["count"]
            Stats[m]["sd"] = sqrt(Stats[m]["sumX2"] / Stats[m]["count"] - Stats[m]["mean"] * Stats[m]["mean"])
    
            # for (n in Stats[m]) {
                # print "Stats["m"]["n"]= " Stats[m][n]
            # }
    
            print m "," Stats[m]["min"] "," Stats[m]["max"] "," Stats[m]["mean"] "," Stats[m]["sd"]"," Stats[m]["count"]","Stats[m]["sum"]/1000 >> methodStatsFile
            printf("%-30s\t%.2f\t%.2f\t%.0f\t%.2f\t%d\t%.2f\n", substr(m, 0, 30), Stats[m]["min"], Stats[m]["max"],Stats[m]["mean"],Stats[m]["sd"], Stats[m]["count"], Stats[m]["sum"]/1000)        
        }
    
        print "----------- done -----------"
        print methodRecordFile
        print methodStatsFile
    } 

    格式化空白

    cat > a.txt <<'EOF'
          aaaa        bbb     ccc
       bbb     aaa ccc
    ddd       fff             eee gg hh ii jj
    EOF
    
    awk 'BEGIN{OFS=" "} {NF=NF; print}' a.txt

    输出

    aaaa bbb ccc
    bbb aaa ccc
    ddd fff eee gg hh ii jj

    打印 ini 文件中的某一段

    awk -v scope="extras" 'BEGIN {scope="["scope"]"} 
    $0 == scope {
        print;
        while ((getline) > 0) {
          if ($0 !~ /\[.*\]/) { print $0; } else { exit }
        }
    }
    ' /etc/yum.repos.d/CentOS-Base.repo

    输出如下 👇

    [extras]
    name=CentOS-$releasever - Extras
    mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
    #baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/
    gpgcheck=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
    
    #additional packages that extend functionality of existing packages

    处理字段中包含字段分割符情况(csv)

    echo 'Robbins,Arnold,"1234 A Pretty Street, NE","MyTown",MyState,12345-6789,USA' | awk 'BEGIN { FPAT="[^,]+|\"[^\"]+\"" } { print NF"    "$3}'

    输出如下 👇

    7    "1234 A Pretty Street, NE"

    嘉兴ing
    284 声望24 粉丝

    PHPer@厦门