sed进阶
多行命令
sed的局限在于只能针对单行数据执行操作。在sed读取数据流时,它会基于换行符的位置将数据分成行。
如果你正在查找一个短语Linux System Administrators Group,它很可能出现在两行之中,每行各包含一部分短语。如果用普通的sed命令处理文件,就不能发现这种被分开的短语。
处理多行文本的特殊命令 | |
---|---|
N | 将数据流中的下一行加进来创建一个多行组(multiline group)处理 |
D | 删除多行组中的一行 |
P | 打印多行组中的一行 |
next命令
1.单行的next命令
n命令(小写)会让sed移动到数据流中的下一行文本。
现在你有一个数据文件。
目标是删除首行空白之后的空白行,而留下最后一行之前的空白行
sed '/^$/d' data1.txt
这么做会删掉两个空白行
sed '/header/{n ; d}' data1.txt
先找到含有head的一行,在移动到它的下一行(第一个空白行)在用d命令删除空白行
2.合并文本行
N命令(大写)会将下一个文本行添加到模式空间已有的文本后。这样就能将数据流的两个文本行合并到同一个模式空间中。文本行仍然是用换行符分割的,但sed会将两行文本当做一行来处理。
现在有一个数据文件。
要把第二行和第三行合并为同一行。sed先找到含有first的一行(第二行),N命令将下一行(第三行)合并到这一行。然后用替换命令s将换行符替换成空格。
sed '/first/{N;s/\n/ /}' data2.txt
现在有一个数据文件。
在文件中查找Sysem Administrator 然后将它替换成Desktop User
sed 'N;s/System.Administrator/Desktop User/' data3.txt
用N命令将第一行和下一行合并起来。点号匹配空格和换行符这两种情况
但这样会把两行合并为一行。解决方法是在sed中脚本中用两个替换命令:一个用来匹配多行,另一个用啦匹配单行
sed 'N
s/System\nAdministrator/Desktop\nUser/
s/System Adiministrator/Desktop User/' data3.txt
3.多行删除命令
d命令用来删除模式空间中的当前行。
删除命令会将含有分开短语的两行都删掉。
sed 'N;/System\nAdministrator/d' data4.txt
D删除命令只会删除模式空间中的第一行,文本的第二行被 N 命令加到了模式空间,但仍然完好。
sed 'N;/System\nAdiministrator/D' data4.txt
这里有一个数据文本
删除数据流中出现的第一行前的空白行。先查找空白行,再用N命令将下以文本行添加到模式空间。如果新的模式空间内容含有单词header。则D命令会删除该模式空间的第一行。
sed '/^$/{N;/header/D}' data5.txt
4.多行打印命令
P命令(大写)只打印多行模式空间中的第一行。
sed -n 'N;/System\nAdministrator/P' data3.txt
保持空间
模式空间(pattern space)是一块活跃的缓冲区,在执行sed命令时里面保存了要检查的文本。
保持空间(hold space),在处理模式空间中某些行时可以用来临时保存一些行。
command | description |
---|---|
h | 将模式空间复制到保持空间 |
H | 将模式空间附加到保持空间 |
g | 将保持空间复制到模式空间 |
G | 将保持空间附加到模式空间 |
x | 交换模式空间和附加空间的内容 |
这里有一个文件
sed -n '/first/{h;p;n;p;g;p}' data2.txt
sed先用正则表达式过滤出含有first的行;当含有first的行出现时,h命令将该行放到保持空间中;
p命令打印模式空间第一行;n命令提取数据流中的第一行并用p命令打印出来;g命令将保持空间的内容放回模式空间中;在用p命令打印模式空间当前内容
排除命令
感叹号命令(!)用来排除命令,也就是会让原本会起作用的命令不起作用。
将一个文件出除了包含单词header那一行都打印出来
sed -n '/header/!p' data.txt
sed无法处理数据流中的最后一行解决方法:美元符表示数据流中的最后一行文本,所以当sed编辑器到了最后一行时,它没有执行 N 命令,但它对所有其他行都执行了这个命令。使用这种方法,你可以反转数据流中文本行的顺序。要实现这个效果(先显示最后一行,最后显
示第一行)
sed '$!N' data.txt
改变流
(1)分支
基于地址、地址模式或地址区间排除一整块命令、
[address]b [label]
option | description |
---|---|
address | 决定哪些行的数据会触发分支命令 |
label | 定义要跳转的位置;如果没有这个参数会跳转到结尾 |
这里有一个文本数据
分支命令在数据流中的第2行和第3行处跳过两个替代命令
sed '{2,3b;s/This is/Is this/;s/line/test ?/}' data2.txt
要是不想直接跳到脚本的结尾,可以定义一个Label;Label以冒号开始,最多可以7个字符长度:label2
如果文本文件出现了first,程序跳转到标签jump1的脚本行;除了含有first的行都进行第一次替换;
:jump1 跳回匹配行 进行第二次替换
sed '{/first/b jump1;s/This is the/Np jump on/
:jump1
s/This is the/Jump here on/}' data2.txt
测试
测试(test)命令(t)可以改变sed脚本执行流程
格式:[address] t [label]
这里有一个文本数据
第一个替换命令会查找模式文本 first 。如果匹配了行中的模式,它就会替换文本,而且测试命
令会跳过后面的替换命令。如果第一个替换命令未能匹配模式,第二个替换命令就会被执行。
sed '{
s/first/matched/ #进行第一次替换的 跳过后面的替换命令
t
s/This is the/No match on/}' data2.txt #第一次跳过的行进行该替换
模式替换
想在行中匹配的单词两边上放上引号
echo "The cat sleeps in his hat" | sed 's/cat/"cat"/'
如果想在模式用点符号匹配多个单词,这么做就会出错。
echo "The cat sleeps in his hat" | sed 's/.at/".at"/g'
&符号
&符号可以用来代表替换命令的匹配的模式。
echo "The cat sleeps in his hat" | sed 's/.at/"&"/g'
替换单独的单词
sed用圆括号来定义替换模式中的子模式。可以在替换模式中使用的在特殊字符来引用每个子模式。替换字符由反斜线和数字(\n)组成。数字表明子模式的位置。
当在替换命令中使用圆括号时,必须用转义字符将它们标示为分组字符而不是普通的圆括号。
echo "The System Administrator manual " | sed 's/\(System\) Administrator/\1 User/'
sed实用工具
(1)加倍行间距
向一个文本文件的行间插入空白行:G命令
sed 'G' data2.txt
不想要在最后一行插入空白行
sed '$!G' data2.txt
(2)对可能含有空白行的文件加倍行间距
有一个文本文件已经有一些空白行
sed '{/^$/d;$!G}' data6.txt
(3)给文件中的行编号
用等号显示数据流中行的行号
sed '=' data2.txt
sed '=' data2.txt | sed 'N;s/\n/ /'
(4)打印末尾行
sed -n '$p' data
(5)删除行
删除连续的空白行
sed '/./,/^$/!d' data.txt
删除开头的空白行
sed '/./,$!d' data.txt
gawk进阶
使用变量:内建变量
variable | description |
---|---|
FIELDWIDTHS | 由空格分割的一段数字,定义每个数字字段确切的宽度 |
FS | Field separator;输入字段分割符 |
RS | Record separator;输入记录分割符 |
OFS | output Field separator;输出字段分割符 |
ORS | output Record separator;输出记录分割符 |
这里有一个数据文件
gawk 'BEGIN{FS=","}{print $1,$2,$3}' data1.txt
gawk 'BEGIN{FS=",";OFS="-"}{print $1,$2,$3}' data1.txt
FIELDWIDTH变量可以设置来匹配数据在记录中的位置
这里有一个文件
gawk 'BEGIN{FIELDWIDTH="3 5 2 5"}{print $1,$2,$3,$4}' data1b
FIELDWIDTH变量定义了四个字端,gawk依次来解析数据记录。每个记录中的数字串会根据已定义好的字段长度来分割。
变量RS和ORS等定义了gawk如何处理数据流中的字段。默认情况下,gawk将 RS 和 ORS 设为换行符。默认的 RS 值表明,输入数据流中的每行新文本就是一条新纪录。
有时在数据流中会碰到占据多行的字段。
gawk默认会将每行当做一条单独的记录来读(RS),将记录中的空格当做字段的分隔符(FS);解决方法是将FS设为换行符,RS设为空白行
更多的gawk的内建变量
varIable | description |
---|---|
ARGV | 包含命令行参数的数组 |
ARGC | 当前命令行参数的个数 |
ARGIND | 当前文件在ARGV中的位置 |
CONVFMT | convert forma;数字的转换格式 |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
ERRNO | 当读取或关闭输入文件发生错误的系统错误号 |
FILENAME | 用作gawk输入数据的数据文件的文件名 |
FNR | 当前数据文件的数据行数 |
IGNORECASE | |
NF | 数据文件中的字符总数 |
NR | 已处理的输入记录数 |
OFMT | output format;数字的输出格式 |
RLENGTH | 由match函数匹配的子字符串的长度 |
RSTART | 由natch函数所匹配的子字符串的起始位置 |
ARGC表明命令行上有两个参数。分别是gawk命令和data1参数
ARGV数组从索引0开始。
gawk 'BEGIN{print ARGC,ARGV[1]}' data1
2 data1
ENVRION数组索引是shell环境变量名,值是shell环境变量的值。
gawk'BEGIN{print ENVIRON["HOME"];print ENVIRON["PATH"]}'
/home/rich
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
NF可以告诉你记录中有多少个数据字段,$NF可以在不知道具体位置的情况下制定记录中的最后一个数据字段。
gawk 'BEGIN{FS=":";OFS=":"}{print $1,$NF}' /etc/passwd
FNR变量含有当前数据文件已处理过的记录数,NR变量则含有已处理过得当前记录数。
gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' data1 data1
gawk 'BEGIN{FS=","}{print $1,"FNR="FNR,"NR="NR}' data1 data1
FNR变量的值在gawk处理第二个数据文件时会被重置重新计数。
NR变量的值在处理第二个数据文件时继续计数。
如果只使用一个数据文件作为输入, FNR 和 NR 的值是相同的;如果使用多个数据文件作为输入, FNR 的值会在处理每个数据文件时被重置,而 NR 的值则会继续计数直到处理完所有的数据文件。
自定义变量
(1)在脚本中给变量赋值
gawk变量可以保存数值或文本值
gawk 'BEGIN{testing="This is a test";print testing}'
变量可以改变
gawk 'BEGIN{name="kiko";print name;name="keiji";print name}'
赋值语句还可以包含数学算式来处理数字值
gawk 'BEGIN{x=4;x=x*2+3;print x}'
(2)在命令行上给变量赋值
#这里有一个脚本script1
BEGIN{FS=","}
{print $n}
#gawk运行指定脚本
打印文件第二列数据
gawk -f script1 n=2 data1
打印文件第三列数据
gawk -f script1 n=3 data1
使用命令行参数来定义变量值会有一个问题。在你设置变量后,这个值在代码的BEGIN部分不可用。
#这里有一个脚本script2
BEGIN{print "The starting value is",n;FS=","}
{print $n}
#用gawk指定运行脚本
gawk -f script2 n=3 data1
gawk可以用-v来设定变量,-v命令行参数必须放在脚本代码之前
gawk -v n=3 -f script2 data1
处理数组
gawk中的关联数组,它的索引值可以是任意文本字符串。
(1)定义数组变量
格式 var[index]=element
#定义数组
capital["Illinois"]="Springfield"
capital["Indiana"] = "Indianapolis"
capital["Ohio"] = "Columbus"
#引用数组
gawk 'BEGIN{capital["Illinois"]="Springfield";print capital["Illinois"]}'
gawk 'BEGIN{var[1]=34;var[2]=3;total=var[1]+var[2];print total}'
(2)遍历数组变量
#gawk中遍历数组的方法
for (var in array)
{
statements
}
#一个例子:
gawk 'BEGIN{
var["a"]=1
var["g"]=2
var["m"]=3
var["u"]=4
for (test in var)
{
print "Index:",test,"- value",var[test]
}}'
(3)删除数组变量
delete array[index]
使用模式
(1)正则表达式
匹配了数据字段中含有字符串11的记录
gawk 'BEGIN{FS=","}/11/{print $1}' data1
(2)匹配操作符
~
matching operator
第一个数据字段以文本data开头
$1 ~ /^data/
过滤出第二个数据字段以data2开头的
gawk 'BEGIN{FS=","} $2~/^data2/{print $0}' data1
在/etc/passwd第一个数据字段中查找文本 rich 。如果在记录中找到了这个模式,它会打印该记录的第一个和最后一个数据字段值。
gawk -F: '$1 ~ /rich/{print $1,$NF}’ /etc/passwd
用!排除正则表达式的匹配
$1 !~ /expression/
awk程序脚本会打印/etc/passwd文件中与用户ID rich 不匹配的用户ID和登录shell。
gawk -F: '$1 !~/rich/{print $1,$NF}' /etc/passwd
(3)数学表达式
匹配模式中使用数学表达式
显示所有属于root用户组(组ID=0)的系统用户
gawk -F: '$4 == 0{print $!}' /etc/passwd
数学比较表达式 |
---|
x == y |
x <= y |
x < y |
x >= y |
x > y |
对文本数据使用表达式,要注意与正则表达式不同,表达式必须完全匹配
结构化命令
(1)if语句
格式:
if (condition)
statement1
可以将它放在一行上,像这样:if (condition) statement1
#有一个文件data4
cat data4
10
5
13
50
34
gawk '{if ($1 > 20) print $1}' data4
如果要在if语句中执行多条语句,就必须用花括号将它们括起来。
gawk '{
if ($1 > 20)
{
x=$1*2
print x
}
}' data4
# if-then-else
gawk '{
if ($1 > 20)
{
x=$1 * 2
print x
} else
{
x=$1 / 2
print x
}}' data4
if-then-else单行命令 if (condition) statement;else statement
gawk '{if ($1 > 2) print $1 * 2;else print $1 /2}'
(2)while语句
格式:
while (condition)
{
statement
}
计算一个文件每行值的平均值
gawk '{
total=0
i=1
while (i < 4)
{
total += $i
i++
}
avg=total/3
print "average:",avg
}' data5
(3)do-while语句
格式:
do
{
statements
}while (condition)
读取每条记录的数据字段并将它们加在一起,直到累加结果达到150。
gawk '{
total=0
i=1
do
{
total += $i
i++
}while (total < 150)
print total }' data5
(4)for语句
格式:for (variable assignment;condition;iteration process)
gawk '{
total=0
for (i=1;i<4;i++)
{
total += $i
}
avg= total / 3
print "average=",avg
}' data5
格式化打印
格式:printf "format string",var1,var2...
控制字母 | 描述 |
---|---|
c | 显示ASCII字符 |
d | 显示一个整数值 |
i | 同上 |
e | 用科学计数法显示一个数 |
f | 显示一个浮点值 |
g | 用科学计数法或者浮点数显示 |
o | 显示一个八进制值 |
s | 显示一个文本字符串 |
x | 显示一个十六进制值 |
X | 显示一个十六进制值,但用大写字母A-F |
gawk 'BEGIN{
x=10 * 100
printf "The anser is :%e\n",x
}'
The answer is: 1.000000e+03