⭐《Linux实战技能100讲》个人笔记 - 5. 文本操作篇

码农天地 -
⭐《Linux实战技能100讲》个人笔记 - 5. 文本操作篇

[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 的模式空间, 其基本工作方式

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

sed 的空间示意图:

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

Tip

使用 ; 可以替换多个 -e模式空间 pattern spaces 替换命令
替换

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

正常模式

使用n命令后

使用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命令后

图来源: 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

参考文档:

The GNU Awk User's Guide

网上很多教程瞎JB写, 建议以官方文档? 为准

精通awk系列

非常不错的系列教程! :+1:

https://awk.readthedocs.io/en...

不完整.

awk 和 sed 的区别awk 更像是脚本语言awk 用于"比较规范"的文本处理, 用于统计数量并调整顺序, 输出指定字段.使用 sed 将不规范的文本处理为"比较规范"的文本awk 脚本的流程控制BEGIN{} 输入数据前例程, 可选{} 主输入循环END{} 所有文件读取完成例程

个人理解的执行顺序

执行开始块(可选) BEGIN{}

若存在主体块 {} 或结束块 END{}`, 则打开文件并读入数据

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

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

若存在<寻址>/pattern/, 则会依次匹配, 通过则对该记录执行 {}读完所有记录后, 执行结束块(可选) 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" : 一次性读取所有数据, 但有些特殊文件包含了空字符 \0RS="^$" : 真正的一次性读取所有数据, 因此 ^$ 匹配的是空文件RS="\n+" : 按行读取, 但忽略空行

ORS (output row separator) 输出记录分隔符(默认是 \n)

awk 'BEGIN {OFS=":"}'
CONVFMT 表示数据转换为字符串的格式, 默认值是 %.6gOFMT 表示数值输出的格式, 默认值是 %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] 值是命令名本身, 即 awkARGV[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] 值是命令名本身, 即 awkARGV[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

会进行字段划分,设置变量 $0NF$NRT,不会修改变量 NRFNR

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"
特别申明:本文内容来源网络,版权归原作者所有,如有侵权请立即与我们联系(cy198701067573@163.com),我们将及时处理。

Tags 标签

加个好友,技术交流

1628738909466805.jpg