我在想是否有办法通过匹配标题来拆分列?
数据看起来像这样
ID_1 ID_2 ID_3 ID_6 ID_15
value1 0 2 4 7 6
value2 0 4 4 3 8
value3 2 2 3 7 8
我想只在ID_3和& ID_15
ID_3 ID_15
4 6
4 8
3 8
如果我知道列的顺序,awk可以简单地将它分开
但是,我有一张非常庞大的桌子,只有手中的ID列表
我仍然可以使用awk或者在linux中有一种更简单的方法吗?
答案 0 :(得分:1)
$ cat c.awk
NR == 1 {
for (i=1; i<=NF; ++i) {
if ($i == "ID_3") col_3 = (i + 1)
if ($i == "ID_15") col_15 = (i + 1)
}
print "ID_3", "ID_15"
}
NR > 1 { print $col_3, $col_15 }
$ awk -f c.awk c.txt
ID_3 ID_15
4 6
4 8
3 8
答案 1 :(得分:1)
你可以选择这样的东西:
BEGIN {
keys["ID_3"]
keys["ID_15"]
}
NR == 1 {
for (i = 1; i <= NF; ++i)
if ($i in keys) cols[++n] = i
}
{
for (i = 1; i <= n; ++i)
printf "%s%s", $(cols[i]+(NR>1)), (i < n ? OFS : ORS)
}
将脚本保存到文件并像awk -f script.awk file
一样运行。
或者,作为&#34;单行&#34;:
awk 'BEGIN { keys["ID_3"]; keys["ID_15"] }
NR == 1 { for (i = 1; i <= NF; ++i) if ($i in keys) cols[++n] = i }
{ for (i = 1; i <= n; ++i) printf "%s%s", $(cols[i]+(NR>1)), (i < n ? OFS : ORS) }' file
在处理文件之前,密钥在keys
数组中设置,对应于感兴趣的列标题。
在第一行,记录包含cols
数组中某个键的所有列号。
循环遍历每个列并打印出来,然后输出字段分隔符OFS
或输出记录分隔符ORS
,具体取决于它是否是最后一个。 $(cols[i]+(NR>1))
处理第一个行之后的行在开头有一个额外字段的事实,因为NR>1
对于那些行是真(1)而对于第一行是假(0)。
答案 2 :(得分:1)
输入格式定义不明确,但有一些简单的方法,awk
,perl
和sqlite
。
(FNR==1) {
nocol=split(col,ocols,/,/) # cols contains named columns
ncols=split("vals " $0,cols) # header line
for (nn=1; nn<=ncols; nn++) colmap[cols[nn]]=nn # map names
OFS="\t" # to align output
for (nn=1; nn<=nocol; nn++) printf("%s%s",ocols[nn],OFS)
printf("\n") # output header line
}
(FNR>1) { # read data
for (nn=1; nn<=nocol; nn++) {
if (nn>1) printf(OFS) # pad
if (ocols[nn] in colmap) { printf("%s",$(colmap[ocols[nn]])) }
else { printf "--" } # named column not in data
}
printf("\n") # wrap line
}
$ nawk -f mycols.awk -v col=ID_3,ID_15 data
ID_3 ID_15
4 6
4 8
3 8
Perl,只是上面的一个变体与一些perl成语混淆/娱乐:
use strict;
use warnings;
our @ocols=split(/,/,$ENV{cols}); # cols contains named columns
our $nocol=scalar(@ocols);
our ($nn,%colmap);
$,="\t"; # OFS equiv
# while (<>) {...} implicit with perl -an
if ($. == 1) { # FNR equiv
%colmap = map { $F[$_] => $_+1 } 0..$#F ; # create name map hash
$colmap{vals}=0; # name anon 1st col
print @ocols,"\n"; # output header
} else {
for ($nn = 0; $nn < $nocol; $nn++) {
print "\t" if ($nn>0);
if (exists($colmap{$ocols[$nn]})) { printf("%s",$F[$colmap{$ocols[$nn]}]) }
else { printf("--") } # named column not in data
}
printf("\n")
}
$ cols="ID_3,ID_15" perl -an mycols.pl < data
使用环境变量来跳过解析命令行的工作。它需要perl选项-an
来设置字段分割和输入读取循环(就像awk一样)。
使用sqlite
(我使用v3.11,v3.8或更高版本是有用的.import
我需要的)。这使用内存中的临时数据库(如果文件对于内存来说太大,或者对于已解析数据的持久副本,则称为文件),并根据第一行自动创建表。这里的优点是您可能根本不需要任何脚本,并且您只需一次解析就可以对数据执行多次查询。
如果您有一个硬标签分隔列,则可以跳过此下一步,在这种情况下,在下面的sqlite示例中将.mode csv
替换为.mode tab
。
否则,要将数据转换为合适的CSV-ish格式:
nawk -v OFS="," '(FNR==1){$0="vals " $0} {$1=$1;print} < data > data.csv
这会在第一行添加一个虚拟的第一列“vals”,然后以逗号分隔的方式打印每一行,它通过对$1
的看似毫无意义的赋值来完成此操作,但这会导致$0
重新计算用OFS
(逗号)替换FS(空格/制表符)。
$ sqlite3
sqlite> .mode csv
sqlite> .import data.csv mytable
sqlite> .schema mytable
CREATE TABLE mytable(
"vals" TEXT,
"ID_1" TEXT,
"ID_2" TEXT,
"ID_3" TEXT,
"ID_6" TEXT,
"ID_15" TEXT
);
sqlite> select ID_3,ID_15 from mytable;
ID_3,ID_15
4,6
4,8
3,8
sqlite> .mode column
sqlite> select ID_3,ID_15 from mytable;
ID_3 ID_15
---------- ----------
4 6
4 8
3 8
使用.once
或.output
将输出发送到文件(sqlite docs)。根据需要使用.headers on
或.headers off
。
sqlite非常乐意创建一个未命名的列,因此您不必在标题行的第一列添加名称,但您需要确保所有输入行和格式的列数相同。
如果在.import
期间出现“预期X列但发现Y”错误,那么您需要稍微清理数据格式。
答案 3 :(得分:0)
尝试以下脚本:
#!/bin/sh
file="$1"; shift
awk -v cols="$*" '
BEGIN{
split(cols,C)
OFS=FS="\t"
getline
split($0,H)
for(c in C){
for(h in H){
if(C[c]==H[h])F[i++]=h
}
}
}
{ l="";for(f in F){l=l $F[f] OFS}print l }
' "$file"
在命令行类型中:
[sumit.gupta@rpm01 ~]$ test.sh filename ID_3 ID_5