如何按名称打印某些列?


32

我有以下文件:

id  name  age
1   ed    50
2   joe   70   

我只想打印idage列。现在我只用awk

cat file.tsv | awk '{ print $1, $3 }'

但是,这需要知道列号。有没有一种方法可以在其中使用列名(在第一行中指定)代替列号?


7
cat没必要,顺便说一句 您可以使用awk '{ print $1, $3 }' file.tsv
埃里克·威尔逊

如果没有列号,那么什么你想依靠?
rozcietrzewiacz

2
@rozcietrzewiacz名称;他想说id的,而不是$1age替代$3
迈克尔Mrozek

另请参阅关于stackoverflow的
Hotschke

Answers:


37

也许是这样的:

$ cat t.awk
NR==1 {
    for (i=1; i<=NF; i++) {
        ix[$i] = i
    }
}
NR>1 {
    print $ix[c1], $ix[c2]
}
$ awk -f t.awk c1=id c2=name input 
1 ed
2 joe
$ awk -f t.awk c1=age c2=name input 
50 ed
70 joe

如果要指定要在命令行上打印的列,则可以执行以下操作:

$ cat t.awk 
BEGIN {
    split(cols,out,",")
}
NR==1 {
    for (i=1; i<=NF; i++)
        ix[$i] = i
}
NR>1 {
    for (i in out)
        printf "%s%s", $ix[out[i]], OFS
    print ""
}
$ awk -f t.awk -v cols=name,age,id,name,id input 
ed 1 ed 50 1 
joe 2 joe 70 2 

(请注意,该-v开关将获取BEGIN块中定义的变量。)


我一直在推迟学习awk ...支持可变数量列的最佳方法是什么?awk -f t.awk col1 col2 ... coln input会很理想;awk -f t.awk cols=col1,col2,...,coln input也会工作
Brett Thomas

1
更新了我的答案。如果您想使用它来停止学习它,:)
Mat Mat

3
第二个示例不按预期顺序输出列,for (i in out)没有固有顺序。gawk提供PROCINFO["sorted_in"]作为解决方案,用a遍历索引for( ; ; )可能更好。
spuratic先生

@BrettThomas,强烈推荐本教程。(如果您可以访问lynda.com,我强烈建议您进行“ Awk基本培训”,该课程涵盖所有相同的材料,但要更加简洁和实践练习。)
通配符

Spuratic先生,你这家伙。我遇到了for(i in out)问题,在3个字段上都工作正常,当我添加2时,它确实完成了4,5,1,2,3,而不是我期望的1,2,3,4,5 。为了获得它们,您必须要做(i = 1; i <= length(out); i ++)
Severun

5

只是将Perl解决方案引入其中:

#!/usr/bin/perl -wnla

BEGIN {
    @f = ('id', 'age');   # field names to print
    print "@f";           # print field names
}

if ($. == 1) {            # if line number 1
    @n = @F;              #   get all field names
} else {                  # or else
    @v{@n} = @F;          #   map field names to values
    print "@v{@f}";       #   print values based on names
}

5

csvkit

将输入数据转换成CSV格式和使用CSV工具如csvcutcsvkit

$ cat test-cols.dat 
id  name  age
1   ed    50
2   joe   70 

安装csvkit:

$ pip install csvkit

tr与其squeeze选项-s一起使用,可将其转换为有效的csv文件并应用csvcut

$ cat test-cols.dat | tr -s ' ' ',' | csvcut -c id,age
id,age
1,50
2,70

如果要返回旧数据格式,可以使用 tr ',' ' ' | column -t

$ cat test-cols.dat | tr -s ' ' ',' | csvcut -c id,age | tr ',' ' ' | column -t
id  age
1   50
2   70

笔记

  • csvkit还支持不同的定界符(共享选项 -d--delimiter),但返回一个csv文件:

    • 如果文件仅使用空格来分隔列(根本没有制表符),则可以进行以下操作

      $ csvcut -d ' ' -S -c 'id,age' test-cols.dat
      id,age
      1,50
      2,70
    • 如果文件使用制表符分隔列,则csvformat可以进行以下工作,并且可以将其用于获取tsv文件:

      $ csvcut -t -c 'id,age' test-cols.dat | csvformat -T
      id  age
      1   50
      2   70

      据我检查,只允许一个选项卡。

  • csvlook 可以将表格格式化为降价表格格式

    $ csvcut -t -c "id,age" test-cols.dat | csvlook
    | id | age |
    | -- | --- |
    |  1 |  50 |
    |  2 |  70 |
  • UUOC(猫的无用使用):我喜欢这种方式来构造命令。


+1。但也有不必要的用途tr。直接支持TSV文件,无需将它们转换为CSV。该-t(又名--tabs)选项告诉cvscut使用制表符作为字段分隔符。并且-d--delimiter使用任何字符作为分隔符。
cas

经过一些测试,-d-t选项似乎已损坏。它们可以指定输入定界符,但是输出定界符被硬编码为始终是逗号。损坏的IMO-它应该与输入定界符相同,或者具有允许用户设置输出定界符的另一个选项,例如awk的FS和OFS vars。
cas

4

如果您只想通过名称而不是数字来引用这些字段,则可以使用read

while read id name age
do
  echo "$id $age"
done < file.tsv 

编辑

我终于明白了你的意思!这是一个bash函数,它将仅打印出您在命令行上指定的列(按名称)。

printColumns () 
{ 
read names
while read $names; do
    for col in $*
    do
        eval "printf '%s ' \$$col"
    done
    echo
done
}

您可以通过以下方式将其与您提供的文件一起使用:

$ < file.tsv printColumns id name
1 ed 
2 joe 

(该函数读取stdin< file.tsv printColumns ... 等效于printColumns ... < file.tsvcat file.tsv | printColumns ...)。

$ < file.tsv printColumns name age
ed 50 
joe 70 

$ < file.tsv printColumns name age id name name name
ed 50 1 ed ed ed 
joe 70 2 joe joe joe

注意:请注意您要求的列的名称!这个版本缺乏健全性检查,因此,如果其中一个参数是"anything; rm /my/precious/file"


1
这也需要知道列号。仅仅因为你他们的名字idname并且age,不会改变的事实,为了在您的硬编码read线。
janmoesen 2011年

1
@janmoesen是的,我终于明白了:)
rozcietrzewiacz 2011年

很好,谢谢。我正在处理大型文件(1000列,数百万行),所以我使用awk来提高速度。
布雷特·托马斯

@BrettThomas哦,我明白了。当时我很好奇:您能发布一些可以进行时间比较的基准测试吗?(使用time { command(s); })。
rozcietrzewiacz 2011年

@rozceitrewaicz:time cat temp.txt | ./col1 CHR POS > /dev/null 99.144u 38.966s 2:19.27 99.1% 0+0k 0+0io 0pf+0w time awk -f col2 c1=CHR c2=POS temp.txt > /dev/null 0.294u 0.127s 0:00.50 82.0% 0+0k 0+0io 0pf+0w
布雷特·托马斯

3

物有所值。它可以按照您选择的输出顺序处理源中任意数量的列以及要打印的任意数量的列。只是重新排列参数...

例如。呼叫:script-name id age

outseq=($@)
colnum=($( 
  for ((i; i<${#outseq[@]}; i++)) ;do 
    head -n 1 file |
     sed -r 's/ +/\n/g' |
      sed -nr "/^${outseq[$i]}$/="
  done ))
tr ' ' '\t' <<<"${outseq[@]}"
sed -nr '1!{s/ +/\t/gp}' file |
  cut -f $(tr ' ' ','<<<"${colnum[@]}") 

输出

id      age
1       50
2       70

2

如果您正在读取的文件永远无法由用户生成,则可以滥用内置的读取文件:

f=file.tsv
read $(head -n1 "$f") extra <<<`seq 100`
awk "{print \$$id, \$$age}" "$f"

输入文件的整个第一行将替换为参数列表,因此read会将标题行中的所有字段名称作为变量名称传递。其中第一个分配给seq 100生成的1,第二个分配给2,第三个获取3,依此类推。多余的seq输出被伪变量吸收extra。如果您提前知道输入列的数量,则可以更改100以匹配并摆脱extra

awk脚本是双引号引起来的字符串,允许将定义的shell变量read替换为脚本中的awk字段编号。


1

通常,仅查看文件头,计算所需的列数(c),然后使用Unix 会更容易cut

cut -f c -d, file.csv

但是,当有许多列或许多文件时,我将使用以下丑陋技巧:

cut \
  -f $(head -1 file.csv | sed 's/,/\'$'\n/g' | grep -n 'column name' | cut -f1 -d,) \
  -d, \ 
  file.csv

在OSX上经过测试,file.csv是逗号分隔的。


1

这是选择单个列的一种快速方法。

假设我们要使用名为“ foo”的列:

f=file.csv; colnum=`head -1 ${f} | sed 's/,/\n/g' | nl | grep 'foo$' | cut -f 1 `; cut -d, -f ${colnum} ${f}

基本上,采用标题行,将其分成多行,每行一个列名,对行编号,选择具有所需名称的行,并检索关联的行号;然后使用该行号作为cut命令的列号。


0

寻找一种类似的解决方案(我需要一个名为id的列,它的列号可能有所不同),我遇到了这个问题:

head -n 1 file.csv | awk -F',' ' {
      for(i=1;i < NF;i++) {
         if($i ~ /id/) { print i }
      }
} '

0

为此,我编写了一个Python脚本,其基本工作原理如下:

with fileinput.input(args.file) as data:
    headers = data.readline().split()
    selectors = [any(string in header for string in args.fixed_strings) or
                 any(re.search(pat, header) for pat in args.python_regexp)
                 for header in headers]

    print(*itertools.compress(headers, selectors))
    for line in data:
        print(*itertools.compress(line.split(), selectors))

我把它叫做hgrep标题的grep,它可以这样使用:

$ hgrep data.txt -F foo bar -P ^baz$
$ hgrep -F foo bar -P ^baz$ -- data.txt
$ grep -v spam data.txt | hgrep -F foo bar -P ^baz$

整个脚本要长一些,因为它用于argparse解析命令行参数,并且代码如下:

#!/usr/bin/python3

import argparse
import fileinput
import itertools
import re
import sys
import textwrap


def underline(s):
    return '\033[4m{}\033[0m'.format(s)


parser = argparse.ArgumentParser(
    usage='%(prog)s [OPTIONS] {} [FILE]'.format(
        underline('column-specification')),
    description=
        'Print selected columns by specifying patterns to match the headers.',
    epilog=textwrap.dedent('''\
    examples:
      $ %(prog)s data.txt -F foo bar -P ^baz$
      $ %(prog)s -F foo bar -P ^baz$ -- data.txt
      $ grep -v spam data.txt | %(prog)s -F foo bar -P ^baz$
    '''),
    formatter_class=argparse.RawTextHelpFormatter,
)

parser.add_argument(
    '-d', '--debug', action='store_true', help='include debugging information')
parser.add_argument(
    'file', metavar='FILE', nargs='?', default='-',
    help="use %(metavar)s as input, default is '-' for standard input")
spec = parser.add_argument_group(
    'column specification', 'one of these or both must be provided:')
spec.add_argument(
    '-F', '--fixed-strings', metavar='STRING', nargs='*', default=[],
    help='show columns containing %(metavar)s in header\n\n')
spec.add_argument(
    '-P', '--python-regexp', metavar='PATTERN', nargs='*', default=[],
    help='show a column if its header matches any %(metavar)s')

args = parser.parse_args()

if args.debug:
    for k, v in sorted(vars(args).items()):
        print('{}: debug: {:>15}: {}'.format(parser.prog, k, v),
              file=sys.stderr)

if not args.fixed_strings and not args.python_regexp:
    parser.error('no column specifications given')


try:
    with fileinput.input(args.file) as data:
        headers = data.readline().split()
        selectors = [any(string in header for string in args.fixed_strings) or
                     any(re.search(pat, header) for pat in args.python_regexp)
                     for header in headers]

        print(*itertools.compress(headers, selectors))
        for line in data:
            print(*itertools.compress(line.split(), selectors))

except BrokenPipeError:
    sys.exit(1)
except KeyboardInterrupt:
    print()
    sys.exit(1)


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.