前言

本文介绍的sed/awk是高级文本流处理工具,如果对这两个工具很熟悉,可以快速实现一些复杂的需求。两个工具比较而言,Sed的优势是文本流的增删替换, Awk的优势在于分列信息抽取。要高效的使用它们,还需要熟悉正则表达式,正则表达式虽然是相通的,但是这两个工具的写法却不尽相同。

Sed

基本概念

sed options 'cond cmd/arg1/arg2/flags' filename

一般采用/作为分界符,分界符可以用其它字符,例如要操作的字符串包含/,那么可以用:作为分界符来避免引入转义字符。

-E
扩展正则表达式
-n
只输出匹配的行
-e
脚本表达法,可以执行多条命令
-f file
指定脚本名字
-i
就地处理,不产生副本

常用条件写法:

line                                    # 匹配指定行
$                                       # 匹配最后一行
/regexp/                                # 正则表达式匹配
line1,line2                             # [line1, line2]
line,+N                                 # [line, line+N]
line,/regexp/                           # [line, first-match-regexp]

常用命令,命令可以使用!cmd形式,表示对未匹配行操作:

=
打印匹配行号
a\text
将文本插入匹配行之后
i\text
将文本插入匹配行之前
c\text
将匹配行替换为制定文本
d
删除匹配行
p
打印匹配行
s/expr1/expr2/
将匹配行expr1替换为expr2
y/expr1/expr2/
将匹配行expr1中字符对应替换为expr2中字符
标志 含义
g 作用于全局
I 忽略大小写
p 打印
w fname 输出到文件

可以用如下的方式来写sed脚本,如果要指定选项,必须将选项放到-f之前:

#!/bin/sed -nf
s/a/A/g
s/e/E/g

文本替换

在sed中,特殊符号&用于引用匹配字符串,在下面例子中要注意贪婪算法。第二行实际上在行首匹配了一个空字符。

echo "123 abc" | sed -E 's/[0-9]*/&+&+/'
echo "abc 123" | sed -E 's/[0-9]*/&+&+/'
echo "abc 123" | sed -E 's/[0-9]+/&+&+/'
123+123+ abc
++abc 123
abc 123+123+

分组表达式是非常重要的特性,每个小括号创建一个分组,用\1\2等引用分组,最多可引用9个分组。

echo hello123 | sed -E 's/([a-z]*).*/\1/'
echo hello world | sed -E 's/([a-z]+) ([a-z]+)/\2 \1/'
echo hello hello world | sed -E 's/([a-z]+) \1/\1/'
hello
world hello
hello world

如果只想修改第n次出现,可以用如下表达式:

echo 1b 2b 3b 4b | sed -E 's/b/?/3'     # 只修改第3次出现
echo 1b 2b 3b 4b | sed -E 's/b/?/3g'    # 修改第3次之后的所有出现
1b 2b 3? 4b
1b 2b 3? 4?

实用操作

# 大写转小写
sed 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' fname

sed -i 's/\r//' fname                   # 将windows换行符转换为unix换行符
sed -i 's/^[ \t]*//g' fname             # 删除行首空白
sed -i 's/[ \t]*$//g' fname             # 删除行尾空白

ls -lF | sed -nE '/^total\ |.*\/$/ !p'  # 只显示文件,不显示目录

删除C++注释:

#!/bin/bash

fname=$1
function del_cpp_comment()
{
    # del //... in line begin
    sed -i '/^[ \t]*\/\// d' $fname

    # del //... in line end
    sed -i "s/[ \t]*\/\/[^\"]*$//" $fname

    # del single line only have /*...*/
    sed -i '/^[ \t]*\/\*.*\*\/[ \t]*$/ d' $fname

    # del single line /*...*/
    sed -i 's/[ \t]*\/\*.*\*\///' $fname

    # del multi line /*...*/
    sed -i '/^[ \t]*\/\*/,/.*\*\// d' $fname
}
del_cpp_comment

Awk

基本概念

awk [-W option] [-F value] [-v var=value] [--] 'program text' [file ...]
awk [-W option] [-F value] [-v var=value] [-f program] [--] [file ...]
-F value
指定域分割符
-v var=value
变量赋值
-f file
指定程序文件
--
指示选项结束

程序结构:

BEGIN { cmds }
/pattern/ { cmds }
END { cmds }

一般来讲表达式就是执行一些操作,分无条件操作和有条件操作,条件可以是逻辑比较、也可以是正则表达式匹配。

记录和域

一次读取一个记录,保存到域变量$0,然后分割成$0$1、…、$NF。记录类似行,域类似列。

数据类型

可以基本类型是字符串,数字在需要时会转换为字符串。

echo 24 24E | awk '{ print($1>100, $1>"100", $2>100, $2>"100") }'
0 1 1 1
内置变量 含义
ARGC 命令行参数个数
ARGV 命令行参数数组
ENVIRON 环境变量数组,用变量名索引
FILENAME 输入文件名
CONVFMT 数字转字符串格式
OFMT 数字打印格式
FNR 总行数
NR 当前行号
NF 总列数
FS 列分割符
OFS 列分隔符(用于输出)
RS 行结束符
ORS 行结束符(用于输出)
RLENGTH 匹配长度
RSTART 匹配索引

表达算式

表达式语法类似C语言,运算符优先级如下:

运算符 说明
$
++ -- 自增
^ 指数(<-)
!
+ - 单目/正负
,* / % 乘除
+ - 加减
  连接
< > <= >= = ! 关系/比较
~ !~ 匹配
in 数组成员
&&
||
? : 条件(<-)
= += -= *= /= %= ^= 赋值(<-)

语句流程

if ( expr ) statement
if ( expr ) statement else statement
while ( expr ) statement
do statement while ( expr )
for ( opt_expr ; opt_expr ; opt_expr ) statement
for ( var in array ) statement
continue
break

函数

  • 内置函数
    index(var, sub)
    输出查找的索引,没找到返回0,索引从1开始计数
    length(var)
    字符串长度
    match(var, r)
    输出第一个匹配,没找到返回0
    split(var, arr, r)
    分割为数组,如果忽略r则使用FS分割
    print
    print $0 ORS
    print expr1, expr2
    print expr1 OFS expr2 ORS
    printf(fmt, args)
    格式化输出
    sprintf(fmt, args)
    生成格式化字符串
    getline
    读取一行到$0
    getline var
    读取一行到var
    getline < file
    从文件读取一行到$0
    getline var < file
    从文件读取一行到var
    cmd | getline
    从管道读取到$0
    cmd | getline var
    从管道读取到var
    gsub(r, s, var)
    全局替换,将正则表达式r替换为s。如果没有传递var,默认使用$0。字符串s中的&表示匹配子串,需要使用\&\\输出&\
    sub(r, s, var)
    单次替换
    substr(var, i, len)
    提取子串
    tolower(var)
    小写
    toupper(var)
    大写
    atan2(y,x) …
    cos(x) sin(x) exp(x) log(x) sqrt(x) int(x) rand(x) srand(x)
  • 自定义函数
    function name( args ) { statements }
    

示例代码

BEGIN { identifier = "[_a-zA-Z][_a-zA-Z0-9]*" }
$0 ~ "^" identifier
echo  abc | awk '{ gsub(//, "X") ; print }'
XaXbXcX
{ print }
END                                     # echo

{ /micky/ {cnt++}
}
END { print "count=", cnt }             # match count

{ chars += length($0) + 1
    words += NF
}
END{ print NR, lines, words, chars }    # wc

BEGIN { FS = "[^A-Za-z]+" }
{ for(i = 1 ; i <= NF ; i++)  word[$i] += 1 }
END { delete word[""]
      for ( i in word ) { print i, word[i]; cnt++ }
      print cnt }                       # count uniq words

$1 ~ /credit|gain/ { sum += $2 }
$1 ~ /debit|loss/  { sum -= $2 }
END { print sum }                       # sum 2nd-row base on 1st-row

参考资料