![使用第二个文件中的列名称获取第一个文件中的子集列](https://linux22.com/image/230245/%E4%BD%BF%E7%94%A8%E7%AC%AC%E4%BA%8C%E4%B8%AA%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E5%88%97%E5%90%8D%E7%A7%B0%E8%8E%B7%E5%8F%96%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E5%AD%90%E9%9B%86%E5%88%97.png)
我有两个文本文件:第一个文件是制表符分隔的文件,如下所示:
chrom pos ref alt a1 a2 a3 a4
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
第二个文件如下所示:
a1
a4
我想提取第一个文件中第二个文件中存在的那些列以及第一个文件的前 4 列。因此,在上述情况下,输出将如下所示:
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
我想在 shell 中执行此操作。我怎样才能做到这一点?我的文件比此处显示的要大,因此第一个文件中有很多列
cut -f 1-4,$(grep -Fwf file2.txt <(head -1 file1.txt)) file1.txt
答案1
如果perl
没问题的话:
$ perl -F'\t' -lane 'if(!$#ARGV){ $h{$_}=1; close ARGV if eof; next }
@i = grep { exists $h{$F[$_]} } 4..$#F if $.==1;
print join "\t", @F[0..3, @i]' f2.txt f1.tsv
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
哈希变量将使用第二个文件中的行作为键。
然后,grep
用于通过根据哈希键测试字段名称来从 TSV 文件的标题行获取索引。
最后,前 4 列和过滤后的索引值用于打印。
答案2
使用标准命令,我们可以在第二个文件(此处称为)paste
中创建以逗号分隔的字段名称列表:file2
$ paste -s -d , - <file2
a1,a4
我们可以使用它米勒 ( mlr
),一个用于处理结构化文档(例如 TSV 文件)的实用程序,及其从第一个文件(此处称为)中cut
提取前四个字段以及中的字段的子命令:file2
file1
$ mlr --tsv cut -f "chrom,pos,ref,alt,$(paste -s -d , - <file2)" file1
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
Miller 的子命令cut
允许您使用字段名称来选择要提取的字段,这里我们按名称以及文件中的名称选择前四个字段file2
。
由于该cut
子命令还接受以逗号结尾的字段名称列表,因此您可以选择使用它tr '\n' ',' <file2
来代替paste
前面提到的命令。
如果我们已经获得了想要保留的字段索引,例如file3
像这样的文件:
5
8
...然后我们可以使用cut
如下标准命令来提取所需的数据:
$ cut -f "1-4,$(paste -s -d , - <file3)" file1
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
我们可以使用有点复杂的管道从原始文件创建数字字段索引列表:
$ head -n 1 file1 | tr '\t' '\n' | grep -xF -f file2 -n | cut -d : -f 1 | paste -s -d , -
5,8
这会从数据中提取标题行,并用换行符替换制表符,以使每个字段名称位于单独的行上。然后grep
输出与第二个文件中的字段名称相对应的行号。这些输出在表单 上5:a1
,其中 之前的数字:
是行号,末尾的文本是匹配的字段名称。
使用 隔离该数字cut
,然后paste
使用该命令将所有字段索引放入逗号分隔的列表中。
因此,完整的命令模拟了mlr
这个答案顶部的命令的作用,看起来像这样:
cut -f "1-4,$(
head -n 1 file1 | tr '\t' '\n' |
grep -xF -f file2 -n | cut -d : -f 1 |
paste -s -d , -
)" file1
答案3
使用任何 awk:
$ cat tst.awk
BEGIN { FS=OFS="\t" }
NR == FNR {
a[$1]
next
}
FNR == 1 {
for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
if ( (inFldNr <= 4) || ($inFldNr in a) ) {
out2in[++numOutFlds] = inFldNr
}
}
}
{
for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
inFldNr = out2in[outFldNr]
printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
}
}
$ awk -f tst.awk file2 file1
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
请注意,为了提高效率,读取file1
上述每一行时仅循环与要打印的输出字段数相同的次数,因此如果您的输入有 1000 个字段,但您只想打印 10 个字段,则每行将迭代 10 次,而不是超过 1000。
答案4
使用乐(以前称为 Perl_6)
~$ raku -e 'my ($header,@a) = lines.map: *.split(/ \s+ /);
$header .= list; my @ind = <a1 a4>;
my @col = (0...3, $header.grep( / @ind /, :k ).Slip);
put $header[@col].join("\t");
say $_.join("\t") for @a.map: *.[@col];' data.csv
上面是用 Raku(Perl 编程语言家族的成员)编写的答案。与 Perl 一样,Raku 非常适合承担生物信息学等领域的文本整理工作。
首先,使用 Raku 的例程读取数据lines
。这些行split
位于空白处。将数据保存到$header
标量和@a
数组中,Raku 知道它$header
占据第一行,下一条语句$header
从轻量级升级Seq
到list
.现在,我们内联获取所需的列名称,并将它们存储在@ind
数组中。
我们grep
提供所需列名称的标题,返回:k
它们的索引位置(而不是值)。然后@col
使用所需的前四列创建一个数组0...3
,以及grep
结果,Slip
将其组合在一起(即展平)以创建一个简单的索引。
如果我们处理行,我们将使用@array[@index]
习惯用法(适用于单行标题)。因为我们正在处理列,所以我们需要map
进入数组元素,例如@a.map: *.[@col]
。最后,数据join
在选项卡上重新显示并输出put
。
输入示例:
chrom pos ref alt a1 a2 a3 a4
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
10 12345 C T aa bb cc dd
示例输出:
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
升级代码以处理两个文件很简单。只需使用路径来创建@ind
索引,这可以帮助您从概念上将列索引文件与数据文件分开:
my @ind = "/path/to/index.csv".IO.lines;
如果您确实更喜欢从命令行获取数据,Raku 可以提供@*ARGS
相应的数组。使用 Raku 的IO
例程并确保按照您想要的顺序输入文件:
~$ raku -e 'my ($header,@a) = @*ARGS[0].IO.lines.map: *.split(/ \s+ /);
$header .= list; my @ind = @*ARGS[1].IO.lines;
my @col = (0...3, $header.grep( / @ind /, :k ).Slip);
put $header[@col].join("\t");
say $_.join("\t") for @a.map: *.[@col];' data.csv index.csv
chrom pos ref alt a1 a4
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd
10 12345 C T aa dd