如果整个列与另一个文件的列表匹配,则将其检索到新文件

时间:2019-03-26 13:39:39

标签: bash matching

我有一个大文件,我需要从File1中检索特定的列,如果与file2的列表匹配,则它是〜200000行和〜1000列。 (比B更喜欢Bash) 例如我的虚拟数据文件如下,

file1

gene    s1  s2  s3  s4  s5
a      1    2   1   2   1
b      2    3   2   3   3
c      1    1   1   1   1
d      1    1   2   2   2

和file2

sample
s4
s3
s7
s8

我想要的输出是

gene       s3   s4  
    a       1   2   
    b       2   3   
    c       1   1   
    d       2   2

同样,我有3个不同的file2,并且我必须从同一file1中选择不同的样本到一个新文件中。 如果你们能为我提供宝贵的建议,我将非常感激

P.S:我是生物学家,我几乎没有编码经验

致谢

Ateeq

1 个答案:

答案 0 :(得分:1)

$ cat file1 
gene    s1  s2  s3  s4  s5
a      1    2   1   2   1
b      2    3   2   3   3
c      1    1   1   1   1
d      1    1   2   2   2
$ cat file2
gene
s4
s3
s8
s7
$ cat a 
awk '
NR == FNR {
  columns[ NR ] = $0
  printf "%s\t", $0
  next
}
FNR == 1 { 
  print ""
  split( $0, headers ) 
  for (x = 1 ; x <= length(headers) ; x++ ) 
  { 
    aheaders[  headers[x]] = x
  }
  next
}
{
  for ( x = 1 ; x <= length( columns ) ; x++ ) 
  {
    if (length( aheaders[ columns[x]  ] ) == 0 )
      printf "N/A\t"
    else
      printf "%s\t" , $aheaders[ columns[x] ]
  }
  print ""
}
' $*

$ ./a file2 file1 | column -t

gene  s4  s3  s8   s7
a     2   1   N/A  N/A
b     3   2   N/A  N/A
c     1   1   N/A  N/A
d     2   2   N/A  N/A

以上内容将助您一臂之力。这是一个非常乐观的程序,没有进行负面测试。

Awk是一种工具,它将一组命令应用于与表达式匹配的每个文件的每一行。通常,awk脚本的格式为:

<pattern> <command>

上面有三对这样的对。每个人都需要一点解释:

NR == FNR {
  columns[ NR ] = $0
  printf "%s\t", $0
  next
}

NR == FNR是一种怪异的行为。 NR是记录号,FNR是当前文件中的记录号。 NR始终在增加,但是当awk解析下一个文件时,FNR重置为1。 NR == FNR是一个惯用法,仅在解析第一个文件时才适用。

我设计了awk程序来首先读取column文件(您正在调用此file2)。 File2具有要输出的列的列表。如您所见,我们将第一个文件(file2)中的每一行存储到称为列的数组中。阅读时,我们还将列打印出来。为了避免在每个列名后添加换行符(因为我们希望所有列标题都在同一行上),我们使用printf,它不输出换行符(与之相对的print则不行)。

该节末尾的“下一个”告诉awk读取文件中的下一行,而不处理其他任何节。毕竟,我们只想读取第一个文件。

总而言之,第一个节会记住列名(和顺序),并将它们打印在一行上(没有换行符)。

第二个“节”:

FNR == 1 { 
  print ""
  split( $0, headers ) 
  for (x = 1 ; x <= length(headers) ; x++ ) 
  { 
    aheaders[  headers[x]] = x
  }
  next
}

FNR == 1将在任何文件的第一行匹配。由于上一个节中的下一个节,我们只有在第二个文件(file1)的第一行时才打此节。第一个打印的“”语句添加了第一个节中缺少的换行符。现在,带有列标题的行已完成。

split命令采用第一个参数$ 0,即当前行,并根据空格对其进行分割。我们知道当前行是第一行,并且其中包含列标题。 split命令将写入第二个参数标题中命名的数组。现在headers [1] =“ gene”和headers [2] =“ s4”,headers [3] =“ s3”,等等。

我们需要将列名映射到列号。代码的下一位采用每个标头值并创建一个超前条目。 aheders是一个关联数组,它将列标题名称映射到列号。

aheaders["gene"] = 1
aheaders["s1"] = 2
aheaders["s2"] = 3
aheaders["s3"] = 4
aheaders["s4"] = 5
aheaders["s5"] = 6

当我们完成了转发数组的创建后,下一条命令告诉awk跳至输入的下一行。从这一点开始,只有第三个节将具有真实条件。

{
  for ( x = 1 ; x <= length( columns ) ; x++ ) 
  {
    if (length( aheaders[ columns[x]  ] ) == 0 )
      printf "N/A\t"
    else
      printf "%s\t" , $aheaders[ columns[x] ]
  }
  print ""
}

第三个节没有明确的。 Awk将一如既往地处理此问题。因此,对第二个文件的每一行都执行最后一个。

在这一点上,我们想打印在columns数组中指定的列。我们按顺序遍历数组的每个元素。第一次通过循环,columns [1] =“ gene_symbol”。这给了我们:

printf "%s\t" , $aheaders[ "gene" ]

而且由于agosters [“ gene”] = 1,我们得到了:

printf "%s\t" , $1

awk将$ 1理解为输入行中的第一个字段(或列)。因此,第一列传递给printf,后者输出带有附加制表符(\ t)的值。

然后,循环使用x = 2和column [2] =“ s4”再次执行一次。这将导致以下打印执行:

printf "%s\t" , $5

这将打印第五列,后跟一个选项卡。下一次迭代:

columns[3] = "s3"
aheaders["s3"] = 4

这将导致:

printf "%s\t" , $4

即,输出第四个字段。

下一次迭代我们遇到了失败情况:

columns[4] = "s8"
aheaders["s8"] = ''

在这种情况下,length(fronters [columns [x]])== 0为true,所以我们只打印出一个占位符-告诉操作员输入的内容可能无效:

printf "N/A\t"

当处理最后一列[x]值“ s7”时,输出相同。

现在,由于列中没有更多条目,因此存在循环,我们点击了最终打印:

print ""

提供空字符串以进行打印,因为print本身默认情况下将打印$ 0-整行。

这时,awk再次从file1中读取下一行,命中了第三个块(仅)。因此,awk一直持续到完全读取第二个文件为止。