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 的处理流程是:

  1. 读入第一行,并将第一行的资料填入 $0, $1, $2…. 等变数当中;
  2. 依据 “条件类型” 的限制,判断是否需要进行后面的 “动作”;
  3. 做完所有的动作与条件类型;
  4. 若还有后续的『行』的数据,则重复上面 1~3 的步骤,直到所有的数据都读完为止。

经过这样的步骤,你会晓得, awk 是『以行为一次处理的单位』, 而『以字段为最小的处理单位』。好了,那么 awk 怎么知道我到底这个数据有几行?有几栏呢?这就需要 awk 的内建变量的帮忙啦~

变量名称 代表意义
NF 每一行 ($0) 拥有的字段总数
NR 目前 awk 所处理的是『第几行』数据
FS 目前的分隔字符,默认是空格键

我们继续以上面 last -n 5 的例子来做说明,如果我想要:

  1. 列出每一行的账号(就是 $1);
  2. 列出目前处理的行数(就是 awk 内的 NR 变量)
  3. 并且说明,该行有多少字段(就是 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 的指令间隔:

  1. 所有 awk 的动作,亦即在 {} 内的动作,如果有需要多个指令辅助时,可利用分号『;』间隔, 或者直接以 [Enter] 按键来隔开每个指令,例如上面的范例中,鸟哥共按了三次 [enter] 喔!
  2. 逻辑运算当中,如果是『等于』的情况,则务必使用两个等号『==』!
  3. 格式化输出时,在 printf 的格式设定当中,务必加上 \n ,才能进行分行!
  4. 与 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 }’

执行过程

  1. 第一步:执行BEGIN{ commands }语句块中的语句;

  2. 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。

  3. 第三步:当读至输入流末尾时,执行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 将文件根据内容切分成子文件夹和子文件

这里面有两个注意的地方:

  1. 切割字符串 substr
  2. 生成文件夹 “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

读多个文件的方法:

  1. ARGIND 当前被处理参数标志: awk ‘ARGIND==1{…}ARGIND==2{…}ARGIND==3{…}… ' file1 file2 file3 …
  2. ARGV 命令行参数数组: awk ‘FILENAME==ARGV[1]{…}FILENAME==ARGV[2]{…}FILENAME==ARGV[3]{…}…’ file1 file2 file3 …
  3. 把文件名直接加入判断: 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
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn