sed、awk命令速查
awk与sed、grep一样都是为了加工数据流而做成的文本加工过滤器命令。awk会事先把输入的数据根据字段单位进行分割。在没有指定分割单位的情况下,以输入数据中的空格或Tab为分隔符。与sed相比,它以更接近编程语言的文法进行处理,还包括了通过正则表达式进行的字符串操作、简单的数学函数功能等。
sed的基本形式
sed 启动选项
'地址
命令
/查找字符串/替换字符串/标志
' 输入.txt > 输出.txt
启动选项
-e '单行脚本'; -f 脚本文件; -g 整个文章作为对象(global)
-i 将结果覆盖到源输入文件中,可以指定后缀名生成备份文件,如-i.bak
地址
举例 | 含义 |
---|---|
(空,未指定) | 所有行 |
2 | 第2行 |
11,$ | 11~最后一行 |
4,10! | 4~10行以外的行 |
/^[0-9]/ |
所有以数字开头的行 |
2,/END$/ |
从第2行开始到以END结尾的行 |
命令
可以使用;分割组合使用多个命令,按顺序执行。
s
字符串替换;y
字符替换;d
删除;
p
直接输出;w
文件输出;n
数据输入
双直引号与单直引号
为了防止其中的字符作为shell的特殊字符处理。
sed命令操作
s命令替换字符串
cat /etc/passwd | sed "s!/bin/bash$!/bin/zsh!g"
表示将行末以/bin/bash结尾的行中的/bin/bash替换为/bin/zsh其中感叹号!为分隔符,也可以用/、\、#、甚至任意的英文字母,只要不与要替换的字符串发生混淆即可。标志g表示将整个文档作为替换范围,而不是仅替换第一次出现的位置。
sed -e 's!//!#!' hello.c #将//替换为#
sed -e 's/var/+&+/g' a.txt #将var替换为+var+
sed -e 's/.*/result: &/gw output.txt' a.txt
&表示对前边待替换字符串中正则表达式匹配的字符串的引用。
w 指定输出文件名,将替换后的结果写到文件。
y命令替换字符
sed -e 'y/abc/xyz' a.txt #同时进行a->x, b->y, c->z的替换
d命令删除行
sed -e '1,5d' a.txt #删除前5行后输出剩余内容
sed -e 'd' a.txt #删除全文,输出为空,输入文件并不改变
p命令输出
sed -n -e '2,$p' a.txt
sed -n -e '/^aaa/,/eee$/p' a.txt
#以aaa开头的行到以eee结尾的行
脚本文件中命令的组合
对于一个地址可以执行{多个命令}
2,6{
命令1
命令2
....
}
脚本文件结构-标签、分支、循环
: 标签
命令1,2,3...
/模式/b 标签
命令x
示例-删除所有换行符合并字符串
:loop #循环执行的开始位置
N #字符串合并
$!b loop #不是最后一行时跳转
s/\n//g #删除换行符
awk
基本用法
命令基本用法:awk [-F域分隔符] '{pattern + action}' {filenames}
默认的域分隔符是空格,可以指定'\t','\n'等分隔符。其中 pattern 表示 awk 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。
通常,awk 是以文件的一行为处理单位的。awk 每接收文件的一行,然后执行相应的命令,来处理文本。
待处理的文件可以是多个,后面有处理多个文件的例子。
支持awk '/正则表达式/' testfile 筛选匹配的行,支持sub,match,split等正则表达式函数。
正则表达式匹配查找(match使用)
$ awk 'BEGIN{info="this is a test2010test!";print match(info,/[0-9]+/)?"ok":"no found";}'
ok
模式与操作
模式
模式可以是以下任意一种:
-
正则表达式:使用通配符的扩展集
-
关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试
-
模式匹配表达式:用运算符
~
(匹配)和~!
不匹配 -
BEGIN 语句块, pattern语句块, END语句块
操作
操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于{大刮号内},主要部分是:变量或数组赋值、输出命令、内置函数、控制流语句。
awk执行过程
-
第一步: 执行
BEGIN { commands } pattern
语句块中的语句BEGIN语句块:在awk开始从输入输出流中读取行之前执行,在BEGIN语句块中执行如变量初始化,打印输出表头等操作。
-
第二步:从文件或标准输入中读取一行,然后执行
pattern{ commands }
语句块。它逐行扫描文件,从第一行到最后一行重复这个过程,直到全部文件都被读取完毕。pattern语句块:pattern语句块中的通用命令是最重要的部分,它也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行。
{ }
类似一个循环体,会对文件中的每一行进行迭代,通常将变量初始化语句放在BEGIN语句块中,将打印结果等语句放在END语句块中。 -
第三步:当读至输入流末尾时,执行
END { command }
语句块END语句块:在awk从输入流中读取完所有的行之后即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。
awk内置变量
变量 | 解释 |
---|---|
$n |
当前记录的第n个字段,n为1表示第一个字段 |
$0 |
执行过程中当前行的文本内容 |
ARGC | 命令行参数的数目 |
ARGV | 包含命令行参数的数组 |
ARGIND | 命令行中当前参数的位置(从0开始算) |
CONVFMT | 数字转换格式(默认值为%.6g) |
ENVIRON | 环境变量关联数组 |
ERRNO | 最后一个系统错误的描述 |
FIELDWIDTHS | 字段宽度列表(用空格键分隔) |
FILENAME | 当前输入文件的名字 |
FS | 字段分隔符(默认是任何空格) |
FNR | 当前行在正在处理的文件中的行号 |
NR | 在执行过程中对应于当前的行号(已处理的所有文件的记录数) |
NF | 表示字段数,在执行过程中对应于当前的字段数 |
OFMT | 数字的输出格式(默认值是%.6g) |
OFS | 输出字段分隔符(默认值是一个空格) |
ORS | 输出记录分隔符(默认值是一个换行符) |
RS | 记录分隔符(默认是一个换行符) |
RSTART | 由match函数所匹配的字符串的第一个位置 |
RLENGTH | 由match函数所匹配的字符串的长度 |
SUBSEP | 数组下标分隔符(默认值是34) |
IGNORECASE | 如果为真,则进行忽略大小写的匹配 |
print $NF
打印一行中最后一个字段; $(NF-1)
代表倒数第二个字段。
awk内置函数
awk
提供了一些内置函数,方便对原始数据的处理。如函数toupper($1)
用于将字符串$1转为大写。
length()
:返回字符串长度。substr()
:返回子字符串。
s2 = substr(s1, startIdx, [len]) 表示是从第startIdx个字符开始截取, 如果有len参数,则最多取len个。sin()
:正弦。cos()
:余弦。sqrt()
:平方根。rand()
:随机数。match(), sub(), gsub()
: 正则匹配、替换。print, printf
: 输出split()
: 分隔字符串到数组
awk运算符
-
算术运算:(+,-,*,/,&,!,……,++,--)
所有用作算术运算符进行操作时,操作数自动转为数值,所有非数值都变为0。 -
赋值运算:(=, +=, -=,*=,/=,%=,……=,**=)
-
逻辑运算符: (||, &&)
-
关系运算符:(<, <=, >,>=,!=, ==)
-
正则运算符:(~,~!)(匹配正则表达式,与不匹配正则表达式)
格式化输出
可以使用printf优雅地输出:awk 'BEGIN { printf "Sr No\tName\tSub\tMarks\n" }'
和C语言类似,可以格式化输出,比如%-5d
print和printf会将后边的参数拼接起来进行输出,因此如果要输出tab分割的两列,可以采用这种方式:print $1 "\t" $2
,注意不能是单引号'\t'
.
多路输出
根据不同条件,将内容输出到不同文件中。awk小脚本示例:
#!/bin/awk -f
{
if(NR==FNR)
{a[$0]++}
else
{if($1 in a)
print $0 >> "in_set.txt"
else
print $0 >> "out_set.txt"
}
}
当然,直接单行的脚本中也可以用>
和>>
进行输出定向,示例如下:
awk -F'\t' 'NR==FNR{cat[$1]=1}NR>FNR{if($1 in cat){print $0 > "in_set.txt"} else{print $0 > "out_set.txt"}}' set_file.txt data.txt
正则匹配
正则表达式match匹配查找:match(var, pattern)
$ awk 'BEGIN{info="this is a test2010test!";print match(info,/[0-9]+/)?"ok":"no found";}'
ok
正则匹配替换:gsub(pattern, target, var) 表示将变量var中模式pattern匹配的文本替换为target,
例子:
sub(/^['\''|\#| |,|\.|:|!|\?|\\|+|\-|&|\$|\^|@|`|~|=|%|\||\*|\/|"]+/,"",$j); // 去除前缀符号串
sub(/['\''|\#| |,|\.|:|!|\?|\\|+|\-|&|\$|\^|@|`|~|=|%|\||\*|\/|"]+$/,"",$j); // 去除末尾符号串
gsub(/"|'\''/,"",$j); // 去除所有的单双引号(英文)
gsub("‘","'",$j);
gsub("’",v,$j);
gsub(/\000|\001|\002,"",$text); // 去除特殊字符
sub与gsub的区别?
sub匹配第一次出现的符合模式的字符串,相当于 sed 's//' 。
gsub匹配所有的符合模式的字符串,相当于 sed 's//g' 。
判断文本中是否含一个模式:
if ($text ~ /^http:/) // 是否以http:开头
if ($text ~ "我是谁"); // 是否包含文本"我是谁"
示例:awk 'BEGIN{a="100testa";if(a ~ /^100*/){print "ok";}}'
判断两个字符串是否是包含关系:
if(str ~ pat) print "pat in str";
不包含则用 if((str ~ pat) == 0) print "pat not in str"; 注意里边的圆括号,去掉后结果不相同。
如果要匹配文本内容中的$
符号,则需要两次转义:\\$
,例如:
if ($1 ~ "^\\$")
表示第一列是否是以$
开始。
数组
在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。
-
分割字符串到数组
split("a:b:c", array, ":")
-
获取数组长度
awk 'BEGIN{
info="it is a test";
lens=split(info,tA," "); #使用split函数获取数组长度
print length(tA),lens; #使用length函数获取数组长度(版本有要求)
}'
说明: 版本够高的awk当中,支持直接得到数组长度的方法length(),如果awk的版本过低,则不支持。另外,如果传给length的变量是一个字符串,那么length返回的则字符串的长度。
-
判断键值是否存在
if ( key in array)
-
删除键值
delete array[key]
-
清空数组
split("", array)
-
二维,多维数组
awk的多维数组在本质上是一维数组,更确切一点,awk在存储上并不支持多维数组。awk提供了逻辑上模拟二维数组的访问方式。例如,
array[2,4]=1
这样的访问是允许的。awk使用一个特殊的字符串SUBSEP
作为分割字段。 类似一维数组的成员测试,多维数组可以使用if ( (i,j) in array)
这样的语法,但是下标必须放置在圆括号中。类似一维数组的循环访问,多维数组使用for ( item in array )
这样的语法遍历数组。与一维数组不同的是,多维数组必须使用split()
函数来访问单独的下标分量。
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=9;j++){
tarr[i,j]=i*j;
print i,"*",j,"=",tarr[i,j];
}
}
}'
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=9;j++){
tarr[i,j]=i*j; } }
for(m in tarr){
split(m,tarr2,SUBSEP);
print tarr2[1],"*",tarr2[2],"=",tarr[m]; } }'
-
排序
awk里边的数组实质是一种dict(key-value对),遍历数组的value采用for(i in a)是最有效的,也是我们最喜欢用的;但问题是打印出来不是排序的。
gawk里边有asort和asorti函数可以用,而原始的awk则没有。
cnt=asort(a [,b]) 是对数组a的值进行排序,并且会丢掉原先键值;返回数组元素的个数,排序后的值放在b中。
cnt=asorti(a,b) 是对数组的index(键)进行排序;返回数组元素的个数,排序后的值放在b中。当以arr[k]的形式访问awk数组中不存在的key对应的值时不会报错,awk会自动创建对应的key,值为0或空字符串,视上下文而定。因此在访问不存在的key时最好判断一下。
OFS列输出分隔符
下面的两个语句输出内容不同
awk 'BEGIN{OFS="|";}{print $0}' test1
111 222
333 444
555 666
awk 'BEGIN{OFS="|";}{NF=NF;print $0}' test1
111|222
333|444
555|666
OFS是否生效,在The AWK Programming Language中描述为
when \$0 is changed by assignment or substitution, \$1, \$2, etc., and NF will be recomputed; likewise, when one of \$1, \$2, etc., is changed, \$0 is reconstructed using OFS to separate fields.
当\$0被重建或者打印逗号分隔的值时会采用OFS。
echo '1 2 3' | awk -v OFS=',' '{print $1, $2, $3}'
1,2,3
参考
- https://www.cnblogs.com/quincyhu/p/5884390.html
- http://blog.51yip.com/shell/1151.html
- https://stackoverflow.com/questions/55877410/understanding-how-ofs-works-in-awk
示例1-awk脚本
可以编写awk脚本,然后传给awk进行解释执行:awk -f awk-script-file input-file(s)
awk统计各个登录shell的用户数:
$ awk '
BEGIN{ FS=":" } #修改字段分隔符为:
{ shells[$NF]++;} #NF保存的是输入行的字段数,$NF可以访问最后一个字段的值
END{
for(sh_i in shells)
print sh_i ": " shells[sh_i];#输出类似/bin/bash: 3
}' /etc/passwd
示例2-日志解析
-
输出特定行
sed -e '100,200p' 文件名 -
统计行数
awk '{count++} END {print count}' 文件名 -
统计目录下文件的总大小
ls -l | awk '{size+=$5} END {print size}' -
在控制台中高亮显示一些信息
sed -e 's/\(Failed password\)/\x1b[1;36;44m\1\x1b[0m/
' /var/log/secure
其中\x1b
是ESC
这一字符的16进制表示,1;36;44表示文字风格;文字颜色;背景色
,用[x;y;zm表示这个组合的绑定,\x1b[0m表示文字修饰结束。中间的\1
引用前面匹配的\(内容\)
可选高亮设置值 文字风格 0 重置风格 1 粗体 4 下划线 5 点线 7 颜色反转 8 隐藏 值(文字颜色/背景色) 文字颜色/背景色 30/40 黑 31/41 红 32/42 绿 33/43 黄 34/44 蓝 35/45 红紫 36/46 蓝绿 37/47 白 -
显示出现频率较高的top 10个字符串,如访问源IP
awk '
{print $1}
' access_log | sort | uniq -c | sort -nr | head -10注意uniq的去重统计是按照相邻行计算的,只走一遍,所以需要在uniq -c之前先进行sort按字符顺序进行排序。
-
条件筛选-根据URL请求计算疑似非法访问的日志数量
awk '
$9 !~ /200|304/{print $9,$7}
' access_log |sort| uniq -c | sort -nr$9 !~ /模式/
表示筛选掉$9中满足该模式的字符串
示例3-同时处理两个文件
awk 'pattern' file1 file2
在awk里,NR和FNR的含义相近,唯一的区别就是作用范围,NR是所有读取的行信息计数,而FNR是正在读取文件的行信息技术,FNR在文件切换时会从0重新开始计数,所以在awk语句中:
- NR==FNR在判断是不是在读file1;
- NR>FNR则判断是不是在读file2.
awk求两个文件的交集(有相同字段的行)
awk -F'\t' 'NR==FNR{cat[$1]=1}NR>FNR{if($1 in cat){print $0}else{}}' file1 file2
代码的含义是首先处理file1时NR==FNR
,执行cat[$1]=1
,表示创建一个cat词典,当处理file2时,判断file2中的行是否在cat字典中,并进行print $0
(一整行)
awk按照奇数行和偶数行进行处理
使用NR或FNR进行判断,awk '{if (NR%2==1) print $0}' file
输出第一列和第7列相同内容的行
awk -F'\t' '{ if($1==$7){print $0} }' in.txt > out.txt
输出第一列和第7列除去空格后相同内容的行
需要对两列使用gsub函数去除空格然后比较。注意两点:
- 空格分半角' '和全角' ',两者不等同。
print ori
不要写成print $ori
, 后者等同于print $0
, 由于gsub改变了$1
和$7
的值,所以$0
的结果是被去除了空格。
awk -F'\t' '{ ori=$0; gsub(/ /, "", $1);gsub(/ /, "", $7);if($1==$7){print ori} }' ./in
管道-命令结合
tail -n +2 file | awk '{print $1*$2}'
#表示输出file中从第二行开始到结束的行,跳过首行的描述信息或注释等
tr -d ',-' < file | sed 's/^.../&-'
#将file中的逗号与连字符删掉后在每行前三个字符与后边字符之间加上连字符
echo {1..10} | tr ' ' '\n' | awk '{print $1*2}' | xargs
#xargs将多行的数据转成了单行,输出:2 4 6 8 10 12 14 16 18 20
执行单个awk脚本与用管道进行多个awk串联的比较
使用管道将多个命令串起来将整个任务分解开来无疑更容易理解处理思路,并且利用多CPU并行工作还可提高效率。
如下操作,对数字进行替换,两种方式的执行时间比较:
time seq 1 10000000 | sed -e 's/1/one/g' -e 's/0/zero/g' > /dev/null
V.S.
time seq 1 10000000 | sed 's/1/one/g' | sed 's/0/zero/g' > /dev/null
gawk进行正则表达式字符串提取
gawk 是 AWK 的 GNU 版本。gawk可能没有预装在系统中,需要额外安装。
用法:gawk 'match($0, pattern, ary) {print ary[1]}'
例子:
echo "abcdef" | gawk 'match($0, /b(.*)e/, a) {print a[1]}'
其中,a[1]表示括号中匹配的结果.
将a[1]替换成ary['${2:-'0'}']
可以匹配整个字符串
输出: cd.
可以写成一个shell函数,方便调用:
function regex { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'0'}']}'; }
使用:echo "abcdef" | regex 'b.*f'