Linux文本处理工具
前言
本文介绍的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 $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