awk入门与进阶part2—模式动作输出与输入
模式
BEGIN END
当 awk 从输入读取数据之前,首先执行 BEGIN 的语句;当所有输入数据读取完毕,最后执行 END 的语句。BEGIN 与 END 提供控制初始化与结尾的方式。FS 指定输入行分隔符;OFS 指定输出行分隔符。
任意一个表达式都可以作为任意一个运算符的操作数。如果一个表达式是数值形式而运算符要求字符串值,数值会自动转换成字符串;当运算符要求一个数值时字符串会自动转换成数值。
在一个关系比较中,如果两个操作数都是数值,关系比较将会按照数值比较进行;否则的话,数值操作数会被转换成字符串,再将操作数按字符串的形式进行比较。
符号 | 意义 |
---|---|
< | 大于 |
<= | 大于等于 |
== | 等于 |
!= | 不等于 |
>= | 大于等于 |
> | 大于 |
~ | 匹配 |
!~ | 不匹配 |
两个字符串的比较以字符为单位进行,一个字符串小于另一个,指的是比另一个字符串更先出现。
cat awk2.txt
USSR 8649 275 Asia
Canada 3852 25 North_America
China 3705 1032 Asia
USA 3615 237 North_America
Brazil 3286 134 South_America
India 1267 746 Asia
#提取A和B开头的行
awk '$1 >= "c"' awk2.txt
#如果都是字符串则比较哪一个字符首先出现
awk '$1 > $4' awk2.txt
字符串与正则表达式
用斜括号包围,可以将一个正则表达式切换为一个模式。/test/, 正则表达式斜线中的空格是有意义的。
- /regexpr/
当当前输入行包含一段能够被 regexpr 匹配的子字符串时,该模式被匹配。 - expression ~ /regexpr/
如果 expression 的字符串值包含一段能够被 regexpr 匹配的子字符时,该模式被匹配。 - expression !~ /regexpr/
如果 expression 的字符串值不包含能够被 regexpr 匹配的子字符串,该模式被匹配。
awk '$4 ~ /Asia/' awk2.txt
awk '$4 !~ /Asia/' awk2.txt
- 正则表达式的元字符包括:
\ ^ $ . [ ] | ( ) * + ? - 基本的正则表达式包括下面几种:
一个不是元字符的字符,例如A,这个正则表达式匹配的就是它本身。
一个匹配特殊符号的转义字符:\t 匹配一个制表符。
一个被引用的元字符例如*, 按字面意义匹配元字符。
^ 匹配一行的开始。
$ 匹配一行的结束。
. 匹配任意一个字符。
字符类: [ABC] 匹配字符A, B 或C。
字符类可包含缩写形式: [A-Za-z] 匹配单个字母。
互补的字符类: [^0-9] 匹配任意一个不是数字的字符。 - 运算符:
选择: A|B 匹配A 或 B。
拼接: AB 匹配紧跟B 的A。
闭包: A* 匹配0个或多个A。
正闭包: A+ 匹配一个或多个A。
零或一: A? 匹配空字符串或A。
括号: 被(r) 匹配的字符串,与r所匹配的字符串相同。
# 匹配开始和结尾
^c 匹配以字符c 开始的字符串;
c$ 匹配以字符c 结束的字符串;
^c$ 匹配只含有单个字符c 的字符串;
^.$ 匹配有且仅有一个字符的字符串;
^...$ 匹配有且仅有3 个字符的字符串;
... 匹配任意3 个字符;
\.$ 匹配以句点结束的字符串.
# 使用字符类进行匹配,使用连字符表示一个范围,无连字符为匹配其中任意一个字符。^ 表示互补。
^[ABC] 匹配以A, B, 或C 开始的字符串;
^[^ABC] 匹配以任意一个字符(除了A, B, 或C) 开始的字符串;
[^ABC] 匹配任意一个字符, 除了A, B, 或C;
^[^a-z]$ 匹配任意一个有且仅有一个字符的字符串, 且该字符不能是小写字母.
# 符号*, + 与 ? 是一元运算符, 用来指定正则表达式的重复次数.
B* 匹配空字符串, 或B, BB, 等等.
AB*C 匹配AC, 或ABC, ABBC, 等等.
AB+C 匹配ABC, 或ABBC, ABBBC, 等等.
AB?C 匹配AC 或ABC
[A-Z]+ 匹配由一个或多个大写字母组成的字符串.
(AB)+C 匹配ABC, ABABC, ABABABC, 等等.
任意一个被一对斜杠包围的正则表达式都可以作为匹配运算符的右操作数,$2 !~ /^[0-9]+$/
打印第2个字段不全是数字的行。
复合模式
逻辑运算符通过 ||(OR), &&(AND), !(NOT) 进行组合.
表达式 | 匹配 |
---|---|
c | 非元字符c |
\c | 转义序列或字面意义上的c |
^ | 字符串的开始 |
$ | 字符串的结束 |
. | 任意一个字符 |
[c1c2...] | 任意一个在c1c2... 中的字符。 |
[^c1c2...] | 任意一个不在c1c2... 中的字符。 |
[c1-c2...] | 任意一个在范围内的字符, 范围由c1 开始, 由c2 结束。 |
[^c1-c2...] | 任意一个不在范围内的字符, 范围由c1 开始, 由c2 结束。 |
r1|r2 | 任意一个被r1 或r2 匹配的字符串。 |
(r1)(r2) | 任意一个字串xy, 其中r1 匹配x, 而r2 匹配y; 如果当中不含有选择运算符,那么括号是可以省略的 |
(r)* | 零个或连续多个能被r 匹配的字符串。 |
(r)+ | 一个或连续多个能被r 匹配的字符串。 |
(r)? | 零个或一个能被r 匹配的字符串。 在这里括号可以省略。 |
(r) | 任意一个能被r 匹配的字符串 |
awk '$4 == "Asia" || $4 == "North_America"' awk2.txt
awk '$4 ~ /^(Asia|North_America)$/' awk2.txt #选择运算符
范围模式
一个范围模式由两个被逗号分开的模式组成,如pat1,pat2
一个范围模式匹配多个输入行,这些输入行从匹配pat1 的行开始,到匹配pat2 的行结束,且包括这两行; pat2 可以与pat1 匹配到同一行,这时候模式的范围大小就退化到了一行。
awk 'FNR == 1, FNR == 5 { print FILENAME ": " $0 }' awk2.txt
# FNR 目前读入的行数,FILENAME 当前输入文件名
动作
在一个模式–动作语句中,模式决定动作什么时候执行。有时候动作会非常简单如一条单独的打印或赋值语句。有些时候动作有可能是多条语句,语句之间用换行符或分号分开。
动作语句包括
- expression, 包括常量,变量,赋值,函数调用等等。
- print expression-list
- printf(format, expression-list)
- if (expression) statements
- if (expression) statements else statements
- while (expression) statements
- for (expression; expression; expression) statements
- for (expression in array) statements
- do statements while (expression)
- break
- continue
表达式
常量:包括字符串和数值。
变量:变量类型无需事先声明,内建变量都是大写,一个变量对应一个值。未初始化的变量值是空字符串与0。
内建变量:ARG 命令行参数的个数;ARGV 命令行参数数组;FNR 当前输入文件记录个数;FS 输入行字段分隔符;NF 当前记录字段个数;NR 所读的记录数量(行数);OFS 输出字段分隔符;RS 输入行记录分隔符。
字段变量:$1, $2, $(NF-1)
算术运算符:% 取余数,^ 指数运算符,比较运算符;逻辑运算符 && 或 || 与。
条件表达式,一个条件表达式具有形式:expr1 ? expr2 : expr3
。首先expr1 求值。如果值为真,也就是值非零或非空,那么整个条件表达式的值就会是expr2 的值; 否则,如果expr1 的值为假,那么条件表达式的值就会是expr3。expr2 与expr3 只有其中一个会被求值。
赋值运算符:共有六种方式。+=,-=,*=,/=,%=,以及^=。它们的意义都是类似的: v op = e 等价于v = v op e,但是v 只被求值一次。赋值表达式:pop = pop + $3
可以用+= 写成更加紧凑的形式: pop += $3
。
内建算术函数
函数 | 返回值 |
---|---|
atan2(y,x) | y/x 的反正切值 |
cos(x) | x 的余弦值, x 以弧度为单位 |
exp(x) | x 的指数函数 |
int(x) | x 的整数部分; 当x 大于0 时, 向0 取整 |
log(x) | x 的自然对数(以e 为底) |
rand() | 返回一个随机数r, 0<r<1 |
sin(x) | x 的正弦值, x 以弧度为单位. |
sqrt(x) | x 的方根 |
srand(x) | x 是rand() 的新的随机数种子 |
返回1-n的一个随机数randint = int(n * rand()) + 1
第二个字段有且仅有数字:awk 'BEGIN { digits = "^[0-9]+$" }$2 ~ digits' awk2.txt
内建字符串函数
函数 | 描述 |
---|---|
gsub(r,s) | 将$0 中所有r 替换为s,返回替换发生的次数。 |
gsub(r,s,t ) | 将字符串t 中所有出现的r 替换为s,返回替换发生的次数 |
index(s,t) | 返回字符串t 在s 中第一次出现的位置,如果t 没有出现的话,返回0。 |
length(s) | 返回s 包含的字符个数 |
match(s,r) | 测试s 是否包含能被r 匹配的子串,返回子串的起始位置或0; 设置RSTART 与RLENGTH |
split(s,a) | 用FS 将s 分割到数组a 中,返回字段的个数 |
split(s,a,fs) | 用fs 分割s 到数组a 中,返回字段的个数 |
sprintf(fmt,expr-list) | 根据格式字符串fmt 返回格式化后的expr-list |
sub(r,s) | 将0的最左最长的,能被r 匹配的子字符串替换为s,返回替换发生的次数。 |
sub(r,s,t) | 把t的最左最长的,能被r 匹配的子字符串替换为s,返回替换发生的次数。 |
substr(s,p) | 返回s 中从位置p 开始的后缀。 |
substr(s,p,n) | 返回s 中从位置p 开始的,长度为n 的子字符串。 |
awk '{gsub(/USA/,"united states");print}' awk2.txt
awk '{ $1 = substr($1, 1, 3); print $0 }' awk2.txt
流程控制语句
Awk 提供花括号用于语句组合,if-else 用于判断,while,for,do 语句用于循环。 一条单独的语句总可以被替换为一个被花括号包围起来的语句列表,列表中的语句用换行符或分号分开,换行符可以出现在任何左花括号之后,也可以出现在任何右花括号之前。
if (expression)
statements1
else
statements2
{ for (i = 1; i <= NF; i++)
print $i
}
do
statements
while (expression)
do 循环执行 statements 一次, 只要 expression 为真, 就重复执行statements。do 循环与while , for 循环相比它的条件测试在循环体的底部, 所以循环体至少会执行一次。
有两种语句可以影响循环的运行: break 会导致控制流马上从包围着它的循环内退出,循环可以是while,for,或do。continue 导致下一次迭代开始;它使得执行流马上进入while 与 do 的测试表达式, 或 for 的expression.
关联数组
Awk 提供了一维数组,用于存放字符串与数值。数组与数组元素都不需要事先声明,也不需要说明数组中有多少个元素。就像变量一样,当被提及时,数组元素就会被创建,数组元素的默认初始值为0 或空字符串""。Awk 的数组与大多数其他语言最大的不同点在于数组元素的下标是字符串。
cat awk2.txt
USSR 8649 275 Asia
Canada 3852 25 North_America
China 3705 1032 Asia
USA 3615 237 North_America
Brazil 3286 134 South_America
India 1267 746 Asia
awk '/Asia/ { pop["Asia"] += $3 }; /North_America/ { pop["NA"] += $3 } END { print "Asian population is", pop["Asia"], "million.";print "European population is",pop["NA"], "million."}' awk2.txt
#Asian population is 2053 million.
#European population is 262 million.
这里数组下标是字符串,且数量会累积到数组pop[""]中,如果我们需要统计每个地方的人口总和,或者统计多倍体中每个 subgenome 的总和,这种聚合问题使用关联数组非常方便,其数组下标可以是任意表达式。对于上面的内容,我们可以把$4
作为下标,统计$3
。
awk '{ pop[$4] += $3 }END{ for (name in pop) print name,pop[name]}' awk2.txt
#North_America 262
#Asia 2053
#South_America 134
输入
getline 函数
awk本质上是一个逐行处理过程,类似for循环,直到整个文件的每一行都被执行完毕。
getline 抓取下一个记录,按照通常的方式把记录分割成一个个的字段。它会设置NF,NR,和FNR; 如果存在一个记录,返回1,若遇到文件末尾,返回0,发生错误时返回-1 (例如打开文件失败).
表达式 getline x 会读取下一条记录到变量 x 中,并递增NR 与FNR,不会对记录进行分割,所以不会设置NF。
表达式 | 被设置的变量 |
---|---|
getline | $0, NF, NR, FNR |
getline var | var, NR, FNR |
getline <file | $0, NF |
getline var <file | var |
cmd | getline | $0, NF |
cmd | getlinevar | var |
# 只打印奇数
seq 10 | awk '{getline; print $0}'
# 只打印偶数
seq 10 | awk '{print $0; getline}'
# 奇数偶数行交换
seq 10 | awk '{getline tmp; print tmp; print $0}'
同样的利用第一列信息判断,将第一列相同的第二列内容合并也可以使用getline函数。
cat awk3.txt
a qw
a we
a wet
b wer
b klj
b piu
c eie
c tmp
c ike
awk 'BEGIN{getline;a=$1;printf ("%s\t%s",$1,$2)}{if(a==$1){printf "//"$2}else{printf "\n%s\t%s",$1,$2;a=$1}}END{printf "\n"}' awk3.txt
a qw//we//wet
b wer//klj//piu
c eie//tmp//ike
输出
print(f)
使用 print 输出时后面可以括号括住需要的内容,也可以不括住,但是当参数有关系运算符的时候必须使用括号才可以。默认的print
是print $0
。打印空白行可以使用print ""
awk 的默认输出字段分隔符(OFS)是空格,默认的输出记录分隔符(ORS)是换行符。
printf 用于产生格式化的输出,书写形式:
printf format,expression1,expression2,..., expressionn
printf(format,expression1,expression2, ..., expressionn)
参数format 是一个变量,字符串值含有字面文本与格式说明符,字面文本会按照文本的字面值输出,格式说明符规定了参数列表中的表达式将被如何格式化地输出。每一个格式说明符都以% 开始,以转换字符结束,可能含有下面三种修饰符:
- - 表达式在域内左对齐
- width 为了达到规定的宽度必要时填充空格
- .prec 字符串最大宽度或十进制数的小数部分的位数
字符 | 打印 |
---|---|
c | ASCII 字符 |
d | 十进制整数 |
e | [-]d.ddddddE[+-]dd |
f | [-]ddd.dddddd |
g | 按照e 或f 进行转换, 选择较短的 |
s | 字符串 |
echo 5.55 |awk '{printf "%d\n",$1}'
#5
echo 5.55 |awk '{printf "%5d\n",$1}'
# 5
echo 0.5555555555 |awk '{printf "%.3e\n",$1}'
#5.556e-01
echo 0.5555555555 |awk '{printf "%.3f\n",$1}'
#0.556
echo 0.5555555555 |awk '{printf "%.3g\n",$1}'
#0.556
echo 0.5555555555 |awk '{printf "%-10g\n",$1}'
#0.555556
echo 0.5555555555 |awk '{printf "%10g\n",$1}'
# 0.555556
输出到文件
重定向运算符> 与>> 用于将输出重定向到文件,而不是原来的标准输出。
awk '{ print($1, $3) > ($3 > 100 ? "big.txt" : "small.txt") }'
# 第三列大于100输入到一个文件,小于100输入到另外一个
awk '{ print >> $1 }' tmp.gtf
# 按照染色体分割文件