很高兴和你相遇
这里正在记录我的所思所学
订阅免费邮件通讯接收最新内容
首页 归档 想法 通讯 播客 工具 简历 关于

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/, 正则表达式斜线中的空格是有意义的。

  1. /regexpr/
    当当前输入行包含一段能够被 regexpr 匹配的子字符串时,该模式被匹配。
  2. expression ~ /regexpr/
    如果 expression 的字符串值包含一段能够被 regexpr 匹配的子字符时,该模式被匹配。
  3. expression !~ /regexpr/
    如果 expression 的字符串值不包含能够被 regexpr 匹配的子字符串,该模式被匹配。
awk '$4 ~ /Asia/' awk2.txt
awk '$4 !~ /Asia/' awk2.txt
  1. 正则表达式的元字符包括:
    \ ^ $ . [ ] | ( ) * + ?
  2. 基本的正则表达式包括下面几种:
    一个不是元字符的字符,例如 A,这个正则表达式匹配的就是它本身。
    一个匹配特殊符号的转义字符:\t 匹配一个制表符。
    一个被引用的元字符例如、*, 按字面意义匹配元字符。
    ^ 匹配一行的开始。
    $ 匹配一行的结束。
    . 匹配任意一个字符。
    字符类:[ABC] 匹配字符 A, B 或 C。
    字符类可包含缩写形式:[A-Za-z] 匹配单个字母。
    互补的字符类:[^0-9] 匹配任意一个不是数字的字符。
  3. 运算符:
    选择: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 输出时后面可以括号括住需要的内容,也可以不括住,但是当参数有关系运算符的时候必须使用括号才可以。默认的printprint $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
# 按照染色体分割文件

本文作者:思考问题的熊

版权声明:本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。

如果你对这篇文章感兴趣,欢迎通过邮箱或者微信订阅我的 「熊言熊语」会员通讯,我将第一时间与你分享肿瘤生物医药领域最新行业研究进展和我的所思所学所想点此链接即可进行免费订阅。


· 分享链接 https://kaopubear.top/blog/2018-06-03-awk2/