Linux【2】-管理文件-4-文本行处理(awk)
除了使用 sed 命令,Linux 系统中还有一个功能更加强大的文本数据处理工具,就是 awk。它诞生于 20 世纪 70 年代末期,这也许是它影响了众多 Linux 用户的原因之一。 曾有人推测 awk 命令的名字来源于 awkward 这个单词。其实不然,此命令的设计者有 3 位,他们的姓分别是 Aho、Weingberger 和 Kernighan,awk 就取自这 3 为大师姓的首字母。
和 sed 命令类似,awk 命令也是逐行扫描文件(从第 1 行到最后一行),寻找含有目标文本的行,如果匹配成功,则会在该行上执行用户想要的操作;反之,则不对行做任何处理。
一、基本概念
1.1 命令格式
awk 命令的基本格式为:
[root@localhost ~]# awk [选项] '脚本命令' 文件名
此命令常用的选项以及各自的含义,如表 1 所示。
表 1 awk 命令选项以及含义
选项 | 含义 |
---|---|
-F fs | 指定以 fs 作为输入行的分隔符,awk 命令默认分隔符为空格或制表符。 |
-f file | 从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令。 |
-v var=val | 在执行处理过程之前,设置一个变量 var,并给其设备初始值为 val。 |
awk 的强大之处在于脚本命令,它由 2 部分组成,分别为匹配规则和执行命令,如下所示:
'匹配规则{执行命令}'
这里的匹配规则,和 sed 命令中的 address 部分作用相同,用来指定脚本命令可以作用到文本内容中的具体行,可以使用字符串(比如 /demo/,表示查看含有 demo 字符串的行)或者正则表达式指定。另外需要注意的是,整个脚本命令是用单引号('')括起,而其中的执行命令部分需要用大括号({})括起来。
在 awk 程序执行时,如果没有指定执行命令,则默认会把匹配的行输出;如果不指定匹配规则,则默认匹配文本中所有的行。
举个简单的例子:
[root@localhost ~]# awk '/^$/ {print "Blank line"}' test.txt
在此命令中,/^$/ 是一个正则表达式,功能是匹配文本中的空白行,同时可以看到,执行命令使用的是 print 命令,此命令经常会使用,它的作用很简单,就是将指定的文本进行输出。因此,整个命令的功能是,如果 test.txt 有 N 个空白行,那么执行此命令会输出 N 个 Blank line。
1.3 awk 使用数据字段变量
awk 的主要特性之一是其处理文本文件中数据的能力,它会自动给一行中的每个数据元素分配一个变量。
默认情况下,awk 会将如下变量分配给它在文本行中发现的数据字段:
$0 代表整个文本行;
$1 代表文本行中的第 1 个数据字段;
$2 代表文本行中的第 2 个数据字段;
$n 代表文本行中的第 n 个数据字段。
前面说过,在 awk 中,默认的字段分隔符是任意的空白字符(例如空格或制表符)。
在文本行中,每个数据字段都是通过字段分隔符划分的。awk 在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。
所以在下面的例子中,awk 程序读取文本文件,只显示第 1 个数据字段的值:
[root@localhost ~]# cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
[root@localhost ~]# awk '{print $1}' data2.txt
One
Two
Three
该程序用 $1 字段变量来表示“仅显示每行文本的第 1 个数据字段”。当然,如果你要读取采用了其他字段分隔符的文件,可以用 -F 选项手动指定。
awk 主要是处理『每一行的字段内的数据』,而默认的『字段的分隔符为 “空格键” 或 “[tab]键” 』!举例来说,我们用 last 可以将登入者的数据取出来,结果如下所示:
[root@www ~]# last -n 5 <==仅取出前五行
root pts/1 192.168.1.100 Tue Feb 10 11:21 still logged in
root pts/1 192.168.1.100 Tue Feb 10 00:46 - 02:28 (01:41)
root pts/1 192.168.1.100 Mon Feb 9 11:41 - 18:30 (06:48)
dmtsai pts/1 192.168.1.100 Mon Feb 9 11:41 - 11:41 (00:00)
root tty1 Fri Sep 5 14:09 - 14:10 (00:01)
若我想要取出账号与登入者的 IP ,且账号与 IP 之间以 [tab] 隔开,则会变成这样:
[root@www ~]# last -n 5 | awk '{print $1 "\t" $3}'
root 192.168.1.100
root 192.168.1.100
root 192.168.1.100
dmtsai 192.168.1.100
root Fri
刚刚上面五行当中,整个 awk 的处理流程是:
- 读入第一行,并将第一行的资料填入 $0, $1, $2…. 等变数当中;
- 依据 “条件类型” 的限制,判断是否需要进行后面的 “动作”;
- 做完所有的动作与条件类型;
- 若还有后续的『行』的数据,则重复上面 1~3 的步骤,直到所有的数据都读完为止。
经过这样的步骤,你会晓得, awk 是『以行为一次处理的单位』, 而『以字段为最小的处理单位』。好了,那么 awk 怎么知道我到底这个数据有几行?有几栏呢?这就需要 awk 的内建变量的帮忙啦~
变量名称 | 代表意义 |
---|---|
NF | 每一行 ($0) 拥有的字段总数 |
NR | 目前 awk 所处理的是『第几行』数据 |
FS | 目前的分隔字符,默认是空格键 |
我们继续以上面 last -n 5 的例子来做说明,如果我想要:
- 列出每一行的账号(就是 $1);
- 列出目前处理的行数(就是 awk 内的 NR 变量)
- 并且说明,该行有多少字段(就是 awk 内的 NF 变量)
则可以这样:
要注意喔,awk 后续的所有动作是以单引号『 ' 』括住的,由于单引号与双引号都必须是成对的, 所以, awk 的格式内容如果想要以 print 打印时,记得非变量的文字部分,包含上一小节 printf 提到的格式中,都需要使用双引号来定义出来喔!因为单引号已经是 awk 的指令固定用法了! 鸟哥的图示
[root@www ~]# last -n 5| awk '{print $1 "\t lines: " NR "\t columns: " NF}'
root lines: 1 columns: 10
root lines: 2 columns: 10
root lines: 3 columns: 10
dmtsai lines: 4 columns: 10
root lines: 5 columns: 9
# 注意喔,在 awk 内的 NR, NF 等变量要用大写,且不需要有钱字号 $ 啦!
指定分隔符
如:以逗号分割,打印2,3列。用-F指定一个或者多个
cat test.csv | awk -F "," '{print $2,$3}'
1.4 awk 脚本命令使用多个命令
awk 允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可,例如:
[root@localhost ~]# echo "My name is Rich" | awk '{$4="Christine"; print $0}'
My name is Christine
第一条命令会给字段变量 $4 赋值。第二条命令会打印整个数据字段。可以看到,awk 程序在输出中已经将原文本中的第四个数据字段替换成了新值。
除此之外,也可以一次一行地输入程序脚本命令,比如说:
[root@localhost ~]# awk '{
> $4="Christine"
> print $0}'
My name is Rich
My name is Christine
在你用了表示起始的单引号后,bash shell 会使用 > 来提示输入更多数据,我们可以每次在每行加一条命令,直到输入了结尾的单引号。
注意,此例中因为没有在命令行中指定文件名,awk 程序需要用户输入获得数据,因此当运行这个程序的时候,它会一直等着用户输入文本,此时如果要退出程序,只需按下 Ctrl+D 组合键即可。
awk 的指令间隔:
- 所有 awk 的动作,亦即在 {} 内的动作,如果有需要多个指令辅助时,可利用分号『;』间隔, 或者直接以 [Enter] 按键来隔开每个指令,例如上面的范例中,鸟哥共按了三次 [enter] 喔!
- 逻辑运算当中,如果是『等于』的情况,则务必使用两个等号『==』!
- 格式化输出时,在 printf 的格式设定当中,务必加上 \n ,才能进行分行!
- 与 bash shell 的变量不同,在 awk 当中,变量可以直接使用,不需加上 $ 符号。
此外, awk 的输出格式当中,常常会以 printf 来辅助,所以, 最好你对 printf 也稍微熟悉一下比较好啦!另外, awk 的动作内 {} 也是支持 if (条件) 的喔! 举例来说:
[root@www ~]# cat pay.txt | \
> awk '{if(NR==1) printf "s s s s s\n",$1,$2,$3,$4,"Total"}
NR>=2{total = $2 + $3 + $4
printf "s d d d .2f\n", $1, $2, $3, $4, total}'
1.5 awk从文件中读取程序
跟 sed 一样,awk 允许将脚本命令存储到文件中,然后再在命令行中引用,比如:
[root@localhost ~]# cat awk.sh
{print $1 "'s home directory is " $6}
[root@localhost ~]# awk -F: -f awk.sh /etc/passwd
root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
...
Christine's home directory is /home/Christine
Samantha's home directory is /home/Samantha
Timothy's home directory is /home/Timothy
awk.sh 脚本文件会使用 print 命令打印 /etc/passwd 文件的主目录数据字段(字段变量 $6),以及 userid 数据字段(字段变量 $1)。注意,在程序文件中,也可以指定多条命令,只要一条命令放一行即可,之间不需要用分号。
1.6 基本框架
awk ‘BEGIN{ commands } pattern{ commands } END{ commands }’
执行过程
-
第一步:执行BEGIN{ commands }语句块中的语句;
-
第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。
-
第三步:当读至输入流末尾时,执行END{ commands }语句块。
echo -e “A line 1nA line 2” | awk ‘BEGIN{ print “Start” } { print } END{ print “End” }’
输出:
Start
A line 1 A line 2
End
条件动作
[root@www ~]# awk '条件类型1{动作1} 条件类型2{动作2} ...' filename
awk 后面接两个单引号并加上大括号 {} 来设定想要对数据进行的处理动作。 awk 可以处理后续接的档案,也可以读取来自前个指令的 standard output 。
awk 中还可以指定脚本命令的运行时机。默认情况下,awk 会从输入中读取一行文本,然后针对该行的数据执行程序脚本,但有时可能需要在处理数据前运行一些脚本命令,这就需要使用 BEGIN 关键字。
BEGIN 会强制 awk 在读取数据前执行该关键字后指定的脚本命令,例如:
[root@localhost ~]# cat data3.txt
Line 1
Line 2
Line 3
[root@localhost ~]# awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
可以看到,这里的脚本命令中分为 2 部分,BEGIN 部分的脚本指令会在 awk 命令处理数据前运行,而真正用来处理数据的是第二段脚本命令。
和 BEGIN 关键字相对应,END 关键字允许我们指定一些脚本命令,awk 会在读完数据后执行它们,例如:
[root@localhost ~]# awk 'BEGIN {print "The data3 File Contents:"}
> {print $0}
> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
End of File
可以看到,当 awk 程序打印完文件内容后,才会执行 END 中的脚本命令。
1.7 awk 的逻辑运算字符
既然有需要用到 “条件” 的类别,自然就需要一些逻辑运算啰~例如底下这些:
运算单元 代表意义
> 大于
< 小于
>= 大于或等于
<= 小于或等于
== 等于
!= 不等于
值得注意的是那个『 == 』的符号,因为:
逻辑运算上面亦即所谓的大于、小于、等于等判断式上面,习惯上是以『 == 』来表示; 如果是直接给予一个值,例如变量设定时,就直接使用 = 而已。
好了,我们实际来运用一下逻辑判断吧!举例来说,在 /etc/passwd 当中是以冒号 “:” 来作为字段的分隔, 该档案中第一字段为账号,第三字段则是 UID。那假设我要查阅,第三栏小于 10 以下的数据,并且仅列出账号与第三栏, 那么可以这样做:
[root@www ~]# cat /etc/passwd | \
> awk '{FS=":"} $3 < 10 {print $1 "\t " $3}'
root:x:0:0:root:/root:/bin/bash
bin 1
daemon 2
....(以下省略)....
有趣吧!不过,怎么第一行没有正确的显示出来呢?这是因为我们读入第一行的时候,那些变数 $1, $2… 默认还是以空格键为分隔的,所以虽然我们定义了 FS=":" 了, 但是却仅能在第二行后才开始生效。那么怎么办呢?我们可以预先设定 awk 的变量啊! 利用 BEGIN 这个关键词喔!这样做:
[root@www ~]# cat /etc/passwd | \
> awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'
root 0
bin 1
daemon 2
......(以下省略)......
很有趣吧!而除了 BEGIN 之外,我们还有 END 呢!另外,如果要用 awk 来进行『计算功能』呢?以底下的例子来看, 假设我有一个薪资数据表档名为 pay.txt ,内容是这样的:
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
如何帮我计算每个人的总额呢?而且我还想要格式化输出喔!我们可以这样考虑:
第一行只是说明,所以第一行不要进行加总 (NR==1 时处理); 第二行以后就会有加总的情况出现 (NR>=2 以后处理)
[root@www ~]# cat pay.txt | \
> awk 'NR==1{printf "s s s s s\n",$1,$2,$3,$4,"Total" }
NR>=2{total = $2 + $3 + $4
printf "s d d d .2f\n", $1, $2, $3, $4, total}'
Name 1st 2nd 3th Total
VBird 23000 24000 25000 72000.00
DMTsai 21000 20000 23000 64000.00
Bird2 43000 42000 41000 126000.00
删除第一列:
awk '{for(i=2;i<=NF;i++) if(i!=NF){printf $i" "}else{print $i} }' list
二、实战训练
2.1 去掉行首位的空格
echo " baby go " | awk '{sub("^ *","");sub(" *$","");print}'
2.2 求和
2.2.1 行求和
test.txt数据:
Name 1st 2nd 3th
VBird 23000 24000 25000
DMTsai 21000 20000 23000
Bird2 43000 42000 41000
命令行:
awk 'NR==1 {print $1 "\t" $2 "\t" $3 "\t" $4 "\t" "Toltal"}
NR!=1{total = $2+$3+$4;print $1 "\t" $2 "\t" $3 "\t" $4 "\t" total' test.txt
结果文件:
Name 1st 2nd 3th Toltal
VBird 23000 24000 25000 72000
DMTsai 21000 20000 23000 64000
Bird2 43000 42000 41000 126000
注解:
- NR代表行
- 所有的的动作在{}完成
- 如果有需要多个指令辅助时,可利用分号『;』间隔, 或者直接以 [Enter] 按键来隔开每个指令
2.2.2 列累计求和
seq 5 | awk 'BEGIN{ sum=0; print "总和:" } { print $1"+"; sum+=$1 } END{ print "等于"; print sum }'
1+
2+
3+
4+
5+
sum
15
2.3 统计文件的行数
awk 'ENDint NR}' test.txt
2.4 写入到文件
echo | awk '{printf("hello word!n") > "datafile"}'
或 echo | awk '{printf("hello word!n") >> "datafile"}'
test4.txt
a aaaa aaaa
a 1 1111
a aa a11
b bb 22
c zz 11
c aa 22
awk '{print $2 >>$1}' test4.txt
2.5 搜索
:显示文本文件mydoc匹配(含有)字符串”sun”的所有行。
awk '/sun/' mydoc
awk '$1 ~ /101/ {print $1}' file 显示文件中第一个域匹配101的行(记录)
2.6 分割
awk -F'[:#]' '{print NF}' helloworld.sh
//指定多个分隔符: #,输出每行多少字段
awk -F'[:#]' '{print $1,$2,$3,$4,$5,$6,$7}' OFS='\t' helloworld.sh
//制表符分隔输出多字段
awk 'BEGIN { FS="[: \t|]" }{print $1,$2,$3}' file 通过设置输入分隔符(FS="[: \t|]")修改输入分隔符。
awk 'BEGIN { OFS="%"} {print $1,$2}' file 通过设置输出分隔符(OFS="%")修改输出格式
2.7 两个文件根据列名合并
test1.txt
a 11 22
b 33 44
c 55 66
d 77 88
test2.txt
a aa bb
b cc dd
c ee ff
d gg hh
test3.txt
a aa,bb
b cc,dd
c ee,ff
d gg hh
e ii,jj
awk 'BEGIN {FS= " "; OFS ="\t"} NR==FNR {a[$1]=$1; b[$1]=$2 "\t" $3} NR>FNR {if($1==a[$1]){print $0 ,$1]} }' test1.txt test2.txt
a aa bb 11 22
b cc dd 33 44
c ee ff 55 66
d gg hh 77 88
代码详解:
首先通过BEGIN{FS=OFS=”\t”}定义了文件输入和输出的分隔符均为tab; 其中NR为awk开始执行程序后所读取的数据行数,而FNR与NR功用类似,但是每打开一个新文件后,FNR便从0重新累计; NR==FNR 对应的是第一个文件,然后补货变量名 NR>FNR 这个时候处理的时第二个文件了,然后判断输出
合并的信息
awk 'BEGIN{FS ="[ |,]"} NR==FNR {a[$1]= $2 "\t" $3;next } NR>FNR {print $0 "\t" a[$1]}' test1.txt test3.txt
a aa,bb 11 22
b cc,dd 33 44
c ee,ff 55 66
d gg hh 77 88
e ii,jj
2.8.提取相同名字中,某一个值最小的行
代表这一行的点为第一列,用于比较的值为第16列
awk 'BEGIN{FS ="\t" ;start_n =0;primer[start_n]=""}
{if($1!=start_n){print primer[start_n];start_n=$1;amplicon[$1]=$16;primer[$1]=$0}
else{if($16<=amplicon[start_n]){amplicon[start_n]=$16;primer[start_n]=$0}}}END{print primer[start_n]}' total_result.tsv >total_result2.tsv
2.9.提取vcf文件中给定bed区间的点
awk 'BEGIN {snps = 0} NR==FNR {snps++ ;chrom[snps]=$1;starts[snps]=$2;ends[snps]=$3;}
NR>FNR {if(/^#/){print $0} else { for(i =1;i<=snps;i++ ){if($1==chrom[i] && $2<=ends[i]&&
$2 >=starts[i]){print $0;break}}}}' BRCAWise_LP.exons.bed clinvar_chrom_13_17.vcf >BRCA_exons_clinvar.vcf
2.10 将VCF中相同的位置的点给分文件处理
awk 'BEGIN {snps = 0} NR==FNR {if(/^#/){print $0 >>"BRCA_exons_clinvar1.vcf";
print $0 >>"BRCA_exons_clinvar2.vcf" } else{snps++ ;chrom[snps]=$1;poss[snps]=$2;
refs[snps]=$4;alts[snps]=$5;hit_called =0;for(i =1;i<snps;i++ ){if($1==chrom[i] &&
$2==poss[i]&& $4 ==refs[i] && $5== alts[i]){hit_called=1;break}};if (hit_called ==0){print $0 >>
"BRCA_exons_clinvar1.vcf"}else {print $0 >> "BRCA_exons_clinvar2.vcf"} }}' BRCA_exons_clinvar.vcf
2.11 提取vcf中chrom和pos相同的行
2.12 根据染色体号来分割vcf
awk '{if(!/^#/){print $0 >> $1".vcf" }} ' SNP147_GRCh37.vcf
2.13 提取gencode.v23.annotation.gtf的基因信息
head gencode.v23.annotation.gtf
chr1 HAVANA gene 69091 70008 . + . gene_id "ENSG00000186092.4"; gene_type "protein_coding"; gene_status "KNOWN"; gene_name "OR4F5"; level 2; havana_gene "OTTHUMG00000001094.1";
chr1 HAVANA transcript 69091 70008 . + . gene_id "ENSG00000186092.4"; transcript_id "ENST00000335137.3"; gene_type "protein_coding"; gene_status "KNOWN"; gene_name "OR4F5"; transcript_type "protein_coding"; transcript_status "KNOWN"; transcript_name "OR4F5-001"; level 2; protein_id "ENSP00000334393.3"; tag "basic"; transcript_support_level "NA"; tag "appris_principal_1"; tag "CCDS"; ccdsid "CCDS30547.1"; havana_gene "OTTHUMG00000001094.1"; havana_transcript "OTTHUMT00000003223.1";
grep protein_coding /bioinfo/data/Gene/gencode.v23.annotation.gtf >genecode_protein_coding.gtf
awk 'BEGIN {FS = "[;|\t]";OFS = "\t"}{if($3=="gene"){print $1,$2,$4,$5,$7,$9,$11,$12>>"genecode_protein_coding_1.tsv"}}' genecode_protein_coding.gtf
(print后面的逗号要是不加的话,OFS就不起作用了)
awk -F '\tgene_id "' 'BEGIN{OFS="\t"}{print $1,$2>>"genecode_protein_coding_2.tsv"}' genecode_protein_coding_1.tsv;
awk -F '"\t gene_status "' 'BEGIN{OFS="\t"}{print $1,$2>>"genecode_protein_coding_3.tsv"}' genecode_protein_coding_2.tsv;
awk -F '"\t gene_name "' 'BEGIN{OFS="\t"}{print $1,$2>>"genecode_protein_coding_4.tsv"}' genecode_protein_coding_3.tsv
awk -F '"' '{print $1>>"genecode.v23.annotation.tsv"}' genecode_protein_coding_4.tsv
这里一定要注意print后面的逗号要是不加的话,OFS就不起作用了
OFS = “\t” 如果是单引号的话,也不会起作用
2.14 提取flat中的snp位置信息
qqin@dragon:[flat]$headoinfo/data/SNP/SNP149_GRCh37/ds_flat_chMT.flat
REFSNP-DOCSUM-SET (FULL-DUMP)
CREATED ON: 2016-11-17 08:55
rs8936 | human | 9606 | snp | genotype=NO | submitterlink=YES | updated 2015-04-29 11:12
ss10974 | CGAP-GAI | 52852 | orient=+ | ss_pick=NO
ss35324609 | SSAHASNP | TA-079.chrM_8121 | orient=+ | ss_pick=YES
SNP | alleles='A/C/T' | het=? | se(het)=?
VAL | validated=NO | min_prob=? | max_prob=? | notwithdrawn
CTG | assembly=GRCh37.p13 | chr=MT | chr-pos=8120 | NC_012920.1 | ctg-start=8120 | ctg-end=8120 | loctype=2 | orient=+
LOC | COX2 | locus_id=4513 | fxn-class=missense | allele=A | frame=1 | residue=I | aa_position=179 | mrna_acc=NC_012920.1 | prot_acc=YP_003024029.1
代码实现:
awk 'BEGIN{FS="|";OFS ="\t"} {if(/^rs/){gsub(/[[:blank:]]*/,"",$1);printf "%s\t",$1};if($2~/ alleles=/){gsub(/ alleles=/,"",$2);gsub(/'\''/,"",$2);aa =index($2,"/");ab=substr($2,aa+1);gsub(/\//,",",ab);gsub(/[[:blank:]]*/,"",ab) ; printf "%s\t%s\t",substr($2,1,aa-1),ab};if($3 ~/ chr/){split($3,b,"=");gsub(/[[:blank:]]*/,"",b[2] ) ; printf "%s\t",b[2]};if($4~/ chr-pos=/){split($4,c,"=");;gsub(/[[:blank:]]*/,"",c[2]);
print c[2]}}' ds_flat_chMT.flat > MT.tsv
这里有如下几个问题需要解决:
1.判断是否包含某个字符
if($2~/ alleles=/)
2.控制换行
printf "%s\n",$0
换行输出
printf "%s",$0
不换行输出
3.分割数据
time="12:34:56"
out=`echo $time | awk '{split($0,a,":");print a[1],a[2],a[3]}'`
echo $out
4.去掉单引号'
gsub(/'\''/,"",$2)
5./替换位,
gusb(/\//,",",ab);
6.A/C/G/T去和替换为A C,G
aa =index($2,"/");ab=substr($2,aa+1);gsub(/\//,",",ab);
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
gsub(regular expression, subsitution string, target string);简称 gsub(r,s,t)。
举例:把一个文件里面所有包含 abc 的行里面的 abc 替换成 def,然后输出第一列和第三列
awk '$0 ~ /abc/ {gsub("abc", "def", $0); print $1, $3}' abc.txt
7.去掉空格
sub(/^[[:blank:]]*/,"",变量) 是去掉变量左边的空白符
sub(/[[:blank:]]*$/,"",变量) 是去掉变量右边的空白符
gsub(/[[:blank:]]*/,"",变量) 是去掉变量中所有的空白符
示例:
echo ' 123 456 789 ' | awk '{
print "<" $0 ">";
sub(/^[[:blank:]]*/,"",$0);print "[" $0 "]";
sub(/[[:blank:]]*$/,"",$0);print "|" $0 "|";
gsub(/[[:blank:]]*/,"",$0);print "/" $0 "/";
}'
sub匹配第一次出现的符合模式的字符串,相当于 sed ’s//' 。
gsub匹配所有的符合模式的字符串,相当于 sed ’s//g' 。
再配上bash脚本,就是完美罗
2.15 指定的snps来提取vcf中对应的行
awk '{print $1}' data/wellwise_snps.tsv>data/snps.tsv
time awk 'BEGIN{FS=" "; OFS="\t";snp="aa";snps[snp]=""}NR==FNR{snps[$1]=$1}
NR>FNR{if(/^##fileformat/){print $0}else if(/^#CHROM/){print $0}
else if(!/^#/){if($3==snps[$3]){print $0}}}' data/snps.tsv
/bioinfo/data/SNP/SNP149_GRCh37/All_20161121.vcf>result/wellwise_dbsnp149.vcf
注意awk在处理最后一列的时候会带有符号,小心
2.16 按照指定的列进行排序
awk '{print $0 |"sort -k3" }' urfile
2.17 字符串连接操作(字符串转数字,数字转字符串)
awk字符串转数字
awk 'BEGIN{a="100";b="10test10";print (a+b+0);}'
110
只需要将变量通过”+”连接运算。自动强制将字符串转为整型。非数字变成0,发现第一个非数字字符,后面自动忽略。
awk数字转为字符串
awk 'BEGIN{a=100;b=100;c=(a""b);print c}'
100100
只需要将变量与””符号连接起来运算即可。
awk字符串连接操作
awk 'BEGIN{a="a";b="b";c=(a""b);print c}'
ab
awk 'BEGIN{a="a";b="b";c=(a+b);print c}'
0
字符串连接操作通”二“,”+”号操作符。模式强制将左右2边的值转为 数字类型。然后进行操作。
2.18 将文件根据内容切分成子文件夹和子文件
这里面有两个注意的地方:
- 切割字符串 substr
- 生成文件夹 “mkdir -p dir”| getline;
代码
head Zhu_2013_Light_Donor_C38_Run9_iglblastn.fa.tsv |awk ' { "mkdir -p " substr($1, 1, 2) "/" substr($1, 3, 2)| getline; print substr($1, 5, 5),$2 >> substr($1, 1, 2)"/" substr($1, 3, 2)"/Zhu_2013_Light_Donor_C38_Run9_iglblastn.fa.tsv" } '
五、案例分析
5.1 先将4个口袋文件进行拆分单个PDB文件
cd /data/user/sam/project/PFSC/data_analysis/PD1/result3_1/input
awk 'BEGIN{FS=","}{print $0 >> "%s/"$1".txt"}' %s
5.2 求4个文件夹中PDB名字的交集
将多个 文件第一列的名字的并集给提取出来
#(注意对query的处理)
ls tmp1 >part1_names;
ls tmp2 >part2_names;
ls tmp3 >part3_names;
ls tmp4 >part4_names;
#
#xxxx 最后手动加入第一行
awk 'BEGIN{FS=","; OFS=""} ARGIND==1{gsub(/[[:blank:]]*/,"",$1);gsub(/.txt/,"",$1);tmp1_a[$1]=$1 } ARGIND==2{gsub(/[[:blank:]]*/,"",$1);gsub(/.txt/,"",$1);tmp2_a[$1]=$1} ARGIND==3{gsub(/[[:blank:]]*/,"",$1);gsub(/.txt/,"",$1);tmp3_a[$1]=$1 } ARGIND==4{gsub(/[[:blank:]]*/,"",$1);gsub(/.txt/,"",$1);tmp4_a[$1]=$1 } END{ for (var in tmp1_a){if (var in tmp2_a && var in tmp3_a && var in tmp4_a){ print var }}}' part1_names part2_names part3_names part4_names > merge_pdb_names
#
#[sam@c01 input]$ wc merge_pdb_names
#133918 133918 669591 merge_pdb_names
#
#grep -v 'xxxx' merge_pdb_names > merge_pdb_names2
5.3. 单个PDB排列组合,计算得分
对于第一列和第二列一样的文件,则按照第三列的名字排列组合,进行求平均值,这里面涉及到多个文件的读写
if [ ! -d $tmpdir ]; then
mkdir $tmpdir
else
rm -fr $tmpdir
mkdir $tmpdir
fi
if [ ! -d $resultdir"/process" ]; then
mkdir $resultdir"/process"
else
rm -fr $resultdir"/process"
mkdir $resultdir"/process"
mkdir $resultdir"/process/sort_1";mkdir $resultdir"/process/sort_2";mkdir $resultdir"/process/sort_3";
mkdir $resultdir"/process/sort_4";mkdir $resultdir"/process/sort_5";mkdir $resultdir"/process/sort_6";
mkdir $resultdir"/process/sort_7";mkdir $resultdir"/process/sort_8";mkdir $resultdir"/process/sort_9";
fi
split -n l/$count $inputfile $tmpdir/_pawk$$
for file in $tmpdir/_pawk$$*
do
cd $resultdir
cat $file | xargs -t -i awk 'BEGIN{FS=","; OFS=""} ARGIND==1{gsub(/[[:blank:]]*/,"",$2);gsub(/[[:blank:]]*/,"",$1);gsub(/[[:blank:]]*/,"",$3);tmp1_a[$2]++;tmp1_b[$2][$3]=($8+$11)/2;pdb_id =$1 } ARGIND==2{gsub(/[[:blank:]]*/,"",$2);gsub(/[[:blank:]]*/,"",$3);tmp2_a[$2]++;tmp2_b[$2][$3]=($8+$11)/2} ARGIND==3{gsub(/[[:blank:]]*/,"",$2);gsub(/[[:blank:]]*/,"",$3);tmp3_a[$2]++;tmp3_b[$2][$3]=($8+$11)/2 } ARGIND==4{gsub(/[[:blank:]]*/,"",$2);gsub(/[[:blank:]]*/,"",$3);tmp4_a[$2]++;tmp4_b[$2][$3]=($8+$11)/2 } END{ for (var in tmp1_b){if (var in tmp2_b && var in tmp1_b && var in tmp3_b && var in tmp4_b){for(var2_1 in tmp1_b[var]){ for (var2_2 in tmp2_b[var]) { for (var2_3 in tmp3_b[var]) {for (var2_4 in tmp4_b[var]) { bb = (tmp1_b[var][var2_1]+tmp2_b[var][var2_2]+tmp3_b[var][var2_3]+tmp4_b[var][var2_4])/4; print pdb_id,"_",var,"-",var2_1,"-",var2_2,"-",var2_3,"-",var2_4,"\t",bb ,"\t",tmp1_b[var][var2_1],"\t",tmp2_b[var][var2_2],"\t",tmp3_b[var][var2_3],"\t", tmp4_b[var][var2_4] ,"\t" >> "process/sort_"substr(bb,3,1)"/"pdb_id".tsv" } }}}}}}' $resultdir/tmp1/{}.txt $resultdir/tmp2/{}.txt $resultdir/tmp3/{}.txt $resultdir/tmp4/{}.txt &
# awk -f script.awk $file > ${file}.out &
done
读多个文件的方法:
- ARGIND 当前被处理参数标志: awk ‘ARGIND==1{…}ARGIND==2{…}ARGIND==3{…}… ' file1 file2 file3 …
- ARGV 命令行参数数组: awk ‘FILENAME==ARGV[1]{…}FILENAME==ARGV[2]{…}FILENAME==ARGV[3]{…}…’ file1 file2 file3 …
- 把文件名直接加入判断: awk ‘FILENAME==“file1”{…}FILENAME==“file2”{…}FILENAME==“file3”{…}…’ file1 file2 file3 … #没有前两种通用
上述例子中用的是ARGIND的方法
5.4 合并所有的结果
根据第二列和第三列进行排序
cd $resultdir
cat process/sort_1/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_1/result.tsv;
cat process/sort_2/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_2/result.tsv;
cat process/sort_3/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_3/result.tsv;
cat process/sort_4/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_4/result.tsv;
cat process/sort_5/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_5/result.tsv;
cat process/sort_6/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_6/result.tsv;
cat process/sort_7/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_7/result.tsv;
cat process/sort_8/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_8/result.tsv;
cat process/sort_9/*.tsv |sort |uniq |awk '{print $0 |"sort -r -n -k2,3"}' >process/sort_9/result.tsv;
cat process/sort_9/result.tsv process/sort_8/result.tsv process/sort_7/result.tsv process/sort_6/result.tsv process/sort_5/result.tsv process/sort_4/result.tsv process/sort_3/result.tsv process/sort_2/result.tsv process/sort_1/result.tsv > intersection_ids.txt;
参考资料
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn