Awk 从制表符分隔的文件中提取列

Awk 从制表符分隔的文件中提取列,awk,Awk,我有一个文件data.rdb,格式如下: col1 col2 col3 col4 col5 col6 col7 aaa1 bbb1 ccc1 ddd1 eee1 fff1 ggg1 aaa2 bbb2 ccc2 ddd2 eee2 fff2 ggg2 aaa3 bbb3 ccc3 ddd3 eee3 fff3 ggg3 数据的某些属性: 所有列都以制表

我有一个文件data.rdb,格式如下:

col1    col2    col3    col4    col5    col6    col7
aaa1    bbb1    ccc1    ddd1    eee1    fff1    ggg1
aaa2    bbb2    ccc2    ddd2    eee2    fff2    ggg2
aaa3    bbb3    ccc3    ddd3    eee3    fff3    ggg3
数据的某些属性:

所有列都以制表符分隔 这些列的宽度不同 这些单元格的长度可能不同 该文件的列数将远远超过显示的列数和数百行 我提供的列名称只是通用的,真实名称可以是任何单词,没有制表符、空格或特殊字符。 我需要使用bash按名称提取一些列,例如col1、col3和col6,其中要选择的列来自定义为COLUMN_LIST=$@的shell变量,其中$@是传递给我的shell脚本的参数。每次调用脚本时,参数的数量和名称可能会更改

脚本必须使用bash,不能是python或类似的语言

有什么想法吗?我考虑过使用awk/gawk,但我不知道如何按列名进行选择。列顺序可能因文件而异

谢谢 豪尔赫

更新

出于某种原因,这些解决方案似乎都无法在我的真实数据文件上工作,即我根本没有得到任何输出,因此我发布了其中一个解决方案的子集:

date    star    jdb texp
2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  HD217987 2400000.23551544   900.
2013-11-22  TOI-134  2400000.23551544   900.
2013-11-22  tauCet   2400000.23551544   60. 
2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.
在本例中,我对starjdb和texp列感兴趣

更新2

我使用了@EdMorton的代码,结果如下:

date    star    jdb texp    date    star    jdb texp
2013-11-22  epsInd   2400000.23551544   100.    2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.    2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.    2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  HD217987 2400000.23551544   900.    2013-11-22  HD217987 2400000.23551544   900.
2013-11-22  TOI-134  2400000.23551544   900.    2013-11-22  TOI-134  2400000.23551544   900.
2013-11-22  tauCet   2400000.23551544   60.     2013-11-22  tauCet   2400000.23551544   60. 
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
更新3

我最终使用了EdMorton版本的awk—主要是为了输出的灵活性—但经过修改,我不希望它输出错误的列:

BEGIN {
    numCols = split(column_list,cols)
    OFS="\t"
}
{ sub(/\r$/,"") }
NR==1 {
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        colVal  = (colName in f ? $(f[colName]) : "")
        printf "%s%s", colVal, (colNr<numCols ? OFS : ORS)
    }
}
列顺序可能因文件而异

您可以使用awk使用这种方法,该awk将空格分隔的标题列名作为输入,并通过处理第一条记录将其首先转换为列号。一旦检索到所需的列号,我们只需从下一行开始打印它们

awk -v cols='col1 col3 col6' 'BEGIN {
   FS=OFS="\t"
   n = split(cols, a, " ")
   for (i=1; i <= n; i++)
      c[a[i]]
}
{
   sub(/\r$/, "")
}
NR == 1 {
   for (i=1; i<=NF; i++)
      if ($i in c)
         hdr[i]
}
{
   for (i=1; i<=NF; i++)
      if (i in hdr)
         s = sprintf(s "%s%s", OFS, $i)
   sub(OFS, "", s)
   print s
   s =""
} ' file | column -t
PS:添加了列-t以表格格式格式化输出。

您可以使用coreutils来完成。假设您有一个名为cols的文件,其中包含所需的列,例如:

col2
col3
col6
可以按如下方式提取列号:

head -n1 infile | tr '\t' '\n' | grep -nf cols | cut -d: -f1 | paste -sd,
输出:

2,3,6
将此传递给切割,例如:

输出:

2,3,6
col2 col3 col6 bbb1 ccc1 fff1 bbb2 ccc2 fff2 bbb3 ccc3 fff3
处理此问题的最佳方法是在下面创建一个数组f[],该数组在读取标题行时将列标题字符串(即字段名称)映射到字段编号,然后从那时起仅按字段名称访问字段

已更新以防止调用者请求不存在的列名和DOS行尾:

$ cat tst.awk
BEGIN {
    numCols = split(column_list,cols)
    FS=OFS="\t"
}
{ sub(/\r$/,"") }
NR==1 {
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        colVal  = (colName in f ? $(f[colName]) : (NR>1 ? "N/A" : colName))
        printf "%s%s", colVal, (colNr<numCols ? OFS : ORS)
    }
}

$ awk -v column_list='col1 col3 col6' -f tst.awk data.rdb
col1    col3    col6
aaa1    ccc1    fff1
aaa2    ccc2    fff2
aaa3    ccc3    fff3

$ awk -v column_list='col1 col3 col6 bob' -f tst.awk data.rdb
col1    col3    col6    bob
aaa1    ccc1    fff1    N/A
aaa2    ccc2    fff2    N/A
aaa3    ccc3    fff3    N/A

你怎么称呼你的剧本?例如./myscript 1 3 6或./myscript colname1 colname2colname3@kvantour:./myscript colname1 colname2对不起,不管我说的是什么,我都已经更新了问题。它们确实是分开的选项卡,似乎找不到任何问题。这是一个很好的命令,谢谢!当我运行它时,我会代替选项卡,但是在标题中会有空格,尽管我使用“header=$echo date$”\t“star$”\t“rv$”\t“dvrms$”\t”创建标题。为什么会这样?谢谢,但我似乎无法让它在我的文件上工作。一个注释,真正的列是名为colX的注释,但有完全不同的名称,如rv、fwhm等,这会有区别吗?@jorgehumberto:用新更新的输入检查更新的答案cols的格式是什么?一个文件还是一个shell变量?好的,设法使上述工作正常,只需将FS=OFS=\t替换为OFS=\t。您在问题中说过,所有列都是用制表符分隔的。如果您必须不将FS设置为tab才能使其工作,那么该语句是不正确的,这当然可以解释为什么您无法获得基于该语句提供的任何解决方案。这是一件奇怪的事情,我已经仔细检查了我的数据文件,当我在命令行上选择这些分隔符时,它们显示为选项卡,或者至少显示为选项卡……我已经将它们生成为选项卡。当我使用改编自的awk代码创建文件时,我确保设置了OFS=\t。好的,问题似乎在于标题,对此表示抱歉。line1:date star jdb texp line2:2013-11-22epsInd2400000.23551544100。
2,3,6
cut -f $(head -n1 infile | tr '\t' '\n' | grep -nf cols | cut -d: -f1 | paste -sd,) infile
$ cat tst.awk
BEGIN {
    numCols = split(column_list,cols)
    FS=OFS="\t"
}
{ sub(/\r$/,"") }
NR==1 {
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        colVal  = (colName in f ? $(f[colName]) : (NR>1 ? "N/A" : colName))
        printf "%s%s", colVal, (colNr<numCols ? OFS : ORS)
    }
}

$ awk -v column_list='col1 col3 col6' -f tst.awk data.rdb
col1    col3    col6
aaa1    ccc1    fff1
aaa2    ccc2    fff2
aaa3    ccc3    fff3

$ awk -v column_list='col1 col3 col6 bob' -f tst.awk data.rdb
col1    col3    col6    bob
aaa1    ccc1    fff1    N/A
aaa2    ccc2    fff2    N/A
aaa3    ccc3    fff3    N/A
$ awk -v column_list='col5 col2 col4' -f tst.awk data.rdb
col5    col2    col4
eee1    bbb1    ddd1
eee2    bbb2    ddd2
eee3    bbb3    ddd3