linux命令行(1) awk和正则表达式其实并没有什么卵用

awk 的应用场景

  • 如果仅仅是做一些文本的处理;
  • 本着"能不写代码就不写代码"的原则;
  • 更不想安装 python;
  • 其实用 linux 自带的文本处理工具 awk 就足够了。
  • 本文纯属娱乐,不是教学,可以用来测测您的正则表达式和 awk 掌握的如何
  • 本文 linux 环境:CentOS 7,不同的 linux 环境 awk 版本可能不一致,功能也不同。
  • 本文用到的 regex 规范为 ERE(Extended Regular Expression),不是 PRE(Perl Regular Expression),不同规范的 RE 细微语法可能不同。
  • (因为第二个例子相对复杂一点,后文附录了简要解释。)

例(一) 补全 netstat 输出

netstat -nltp 的输出中,PID/Program name 列显示的信息不全

[root@szma02 vol-auto]# netstat -nltp
Active Internet connections (only servers)
Local Address     State       PID/Program name
0.0.0.0:44013     LISTEN      -
0.0.0.0:33551     LISTEN
0.0.0.0:111       LISTEN
0.0.0.0:10000     LISTEN
0.0.0.0:8883      LISTEN
0.0.0.0:22        LISTEN
127.0.0.1:11000   LISTEN
0.0.0.0:8889      LISTEN
127.0.0.1:25      LISTEN
127.0.0.1:6010    LISTEN
0.0.0.0:1082      LISTEN      3896/ssh

使用 netstat -nltp | full 补全后的输出:

[root@szma02 vol-auto]# netstat -nltp | full
Active Internet connections (only servers)
Local Address     State       PID/Program name
0.0.0.0:44013     LISTEN      -
0.0.0.0:33551     LISTEN      3579 
0.0.0.0:111       LISTEN      1 temd --switched-root --system --deserialize 2
0.0.0.0:10000     LISTEN      3789 ibexec/webmin/miniserv.ponf
0.0.0.0:8883      LISTEN      3757 s /usr/sbin/nginx
0.0.0.0:22        LISTEN      3459 
127.0.0.1:11000   LISTEN      3787 virtual-server/lookup-do
0.0.0.0:8889      LISTEN      3757 s /usr/sbin/nginx
127.0.0.1:25      LISTEN      3531 connections
127.0.0.1:6010    LISTEN      17473 
0.0.0.0:1082      LISTEN      3896 :1082 vol-auto@10.100.69.36

脚本:

function full()
{
  awk '
  !found_pid_line {print}
  found_pid_line {
    prefix=substr($0,1,pid_column_byte_offset-1);
    pid_column=substr($0,pid_column_byte_offset);
    split(pid_column,result,"/");
    pid=result[1];
    pid_first_char=substr(pid,1,1);
    if (pid_first_char!="-") {
      "ps hp " pid | getline;
      close("ps hp " pid);
      full_path=gensub(/^\s*(\S+\s+){4}/,"",1);
      print prefix pid,full_path;
    }
    else
      print $0
  }
  /PID\/Program/{
    for(i=1;i<=NF;i++) {
      if($i=="PID/Program") {
        found_pid_line=1;
        pid_column_byte_offset=match($0,"PID/Program name");
      }
    }
  }
  ' /dev/stdin
}

例(二) 美化 log 输出

原始 log 只有"扁平"的一行,看着不舒服,把它转换成"立体"的多行:

原始 log: 2019-08-13 15:33:38.549151 INFO starting... 2019-08-13 15:33:40.441249 NOTICE OnRspQueryOptionPosition: { UserID: 109, ClientID: xxx, AccountType: 2, FundAccount: 32200287, ExchID: 100, SecurityType: 9, SecuritySubType: 0, SecurityCode: xxx, PositionDirection: 9, InitOvnQty: 0, CurQty: 0, AvailQty: 0, CurMargin: 0, AvgPx: 0 } 找到其中的 NOTICE 字样所在的行,并把单行转换成多行: 2019-08-13 15:33:38.549151 INFO starting... 2019-08-13 15:33:40.441249 NOTICE OnRspQueryOptionPosition: { UserID: 109, ClientID: xxx, AccountType: 2, FundAccount: xxx, ExchID: 100, SecurityType: 9, SecuritySubType: 0, SecurityCode: 10001839, PositionDirection: 9, InitOvnQty: 0, CurQty: 0, AvailQty: 0, CurMargin: 0, AvgPx: 0 }

脚本:

function pretty()
{
  awk '
  {
    if ($0 ~ /[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6} NOTICE /) {
      head = gensub(/([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6} NOTICE )(.*)/, "\\1", "g");
      line = gensub(/([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{6} NOTICE )(.*)/, "\\2", "g");
      print head;
      command=("echo \"" line "\" | grep -Eo -e \"[a-zA-Z_][a-zA-Z_0-9 <>=-]*(: *([^][{},]*) *,? *|, *)?| *\\{ *| *\\} *,? *| *\\[ *| *\\] *,? *|[0-9. ]*,? *\"");
      offset = 4;
      while ((command|getline) > 0) {
        if ($0 ~ /^[]}]/)
          offset -= 4;
        printf "%*c%s\n", offset, " ", $0;
        if ($0 ~ /^[[{]/)
          offset += 4;
      }
      close(command);
    }
    else {
      print;
    }
  }
  ' /dev/stdin
}

$0 ~ /regex/ 表示判断整行 $0 是否和正则表达式 regex 匹配。

在 awk 中,推荐使用 gensub 进行正则表达式的匹配和替换

  • 语法:gensub(regexp, replacement, how [, target])
  • target 是原始字符串,如果省略则表示 $0;
  • regexp 表示要匹配怎样的正则表达式,支持 group 语义,即用 () 表示一个 group
  • replacement 表示要替换成什么样子,支持 group 语义,即用 \n 表示匹配到的第 n 个 group
  • how 可以是一个数字 N,表示仅替换第 N 个匹配;
  • how 也可以是字母 "g",表示替换所有的匹配。(因为一个较长的字符串 target 中可能有多处匹配 regexp)
  • 注意字母 "g" 要加引号,如果不加引号就表示 variable g,这方面 awk 和 C 语言有一点类似。

转义字符和特殊字符

  • 因为 awk 的 string literal 中,\ 表示一个转义,类似 C 语言中的 \ 转义。因此如果要表达 group 字符串 "\1",需要写成 "\\1" 的形式。
  • 在 line 的两边一定要有一对双引号,来防止 line 中含有 shell 特殊字符,例如 <, >, | 这些字符。
  • 因为这对双引号是要输入给 sh 的,因此需要用反斜杠 \ 来转义
  • \\{, \\}, \\[, \\} 这四个就相当于 \{, \}, \[, \],因为转义符号 \ 本身是要传递给 sh 执行的,因此需要为 awk 这一层进行一次转义。

在 awk 中使用 grep

  • 怎样在 awk 中使用 grep?可以用 awk 的 pipe 命令
  • grep -o 是表示一旦匹配 pattern 就打印出来,因此作用是把一行根据符号 []{}, 分成多行
  • grep -e 后面跟的 script 也需要被包围在双引号中,表示这些字符都是要作为 -e 的参数的,否则里面如果包含特殊符号例如 <, >, | 就无法正常工作了。

写在 group [] 中的内容:

  • 如果 ] 本身是 group 的一个待匹配字符,则建议写在开头,即 []x],表示匹配符号 ] 或者字母 x
  • 如果 - 是 group 的一个待匹配字符,则建议写在最后,即 [x-],表示匹配字母 x 或者符号 -
  • 写在 group 中的 [, {, } 这几个特殊字符不需要转义
  • 以上,对于反向匹配 group [^] 也是如此。

在 awk 中执行命令

  • command=(xxx) 加括号是为了消除歧义。例如 "echo " "date" | getline 可以被理解为 ("echo " "date") | getline,也可以被理解为 "echo " ("date" | getline)。同样 (command|getline) > 0 中的括号也是为了消除歧义
  • command | getline 是 awk 的 pipe 命令,command 只执行一次,输出结果被逐行赋值给 $0,当 pipe 中的内容被全部读取后,循环 while ((command|getline) > 0) 退出。
  • 在执行完 command 后,一定要记得 close。每次从 stdin 读取一个 line 后,都会创建一个 pipe,消耗一个系统的文件描述符,如果 stdin 的内容有很多行,如果每次不 close,则很快会达到系统文件描述符的创建上限,导致脚本执行失败。

awk 中的打印

  • printf("%*.*d", n, m, x) 类似 C 语言中的 dynamic width and prec capability,即表示 printf("%n.md", x)
  • print; 不加任何参数,即表示 print $0; 打印整行。并且在 awk 中,单行命令最后的分号 ; 也可以省略。

原文发布于微信公众号 - Toddler的笔记(gh_5708a01db935)

原文发表时间:2019-08-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券