Como imprimir certas colunas pelo nome?

29

Eu tenho o seguinte arquivo:

id  name  age
1   ed    50
2   joe   70   

Eu quero imprimir apenas as colunas id e age . No momento, uso apenas o awk :

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

No entanto, isso requer conhecer os números das colunas. Existe uma maneira de fazer isso onde eu posso usar o nome da coluna (especificado na primeira linha), em vez do número da coluna?

    
por Brett Thomas 22.11.2011 / 17:40

12 respostas

31

Talvez algo assim:

$ 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

Se você quiser especificar as colunas para imprimir na linha de comando, você pode fazer algo assim:

$ 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 

(Observe a opção -v para obter a variável definida no bloco BEGIN .)

    
por 22.11.2011 / 18:36
5

csvkit

Converta os dados de entrada em um formato csv e use uma ferramenta csv, como csvcut do csvkit :

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

Instale o csvkit:

$ pip install csvkit

Use tr com sua opção de compactação -s para convertê-lo em um arquivo csv válido e aplicar csvcut :

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

Se você quiser retornar ao formato de dados antigo, use tr ',' ' ' | column -t

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

Notas

  • O csvkit também suporta diferentes delimitadores ( opção compartilhada -d ou --delimiter ), mas retorna um arquivo csv:

    • Se o arquivo usa apenas espaços para separar colunas (sem guias), siga os trabalhos

      $ csvcut -d ' ' -S -c 'id,age' test-cols.dat
      id,age
      1,50
      2,70
      
    • Se o arquivo usa uma guia para separar colunas, as seguintes obras e csvformat podem ser usado para recuperar o arquivo tsv:

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

      Tanto quanto eu verifiquei, apenas uma única guia é permitida.

  • csvlook pode formatar a tabela em um formato de tabela de remarcação

    $ csvcut -t -c "id,age" test-cols.dat | csvlook
    | id | age |
    | -- | --- |
    |  1 |  50 |
    |  2 |  70 |
    
  • UUOC (uso inútil do gato) : eu gosto Desta forma, para construir o comando.

por 23.02.2015 / 11:26
4

Se você quiser se referir a esses campos pelos nomes em vez de números, use read :

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

EDITAR

Eu finalmente vi seu significado! Aqui está uma função bash que irá imprimir apenas as colunas que você especificar na linha de comando (por nome ).

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

Veja como você pode usá-lo com o arquivo que você apresentou:

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

(A função lê stdin . < file.tsv printColumns ... é equivalente a printColumns ... < file.tsv e cat 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

Nota: Preste atenção aos nomes das colunas solicitadas! Esta versão carece de verificações de sanidade, então coisas desagradáveis podem acontecer se um dos argumentos for algo como "anything; rm /my/precious/file"

    
por 22.11.2011 / 18:28
4

Apenas usando uma solução Perl no lote:

#!/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
}
    
por 23.11.2011 / 11:11
3

Por que vale a pena. Isso pode manipular qualquer número de colunas na origem e qualquer número de colunas para imprimir, em qualquer seqüência de saída que você escolher; apenas reorganize os argumentos ...

por exemplo. chamada: 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[@]}") 

saída

id      age
1       50
2       70
    
por 22.11.2011 / 21:29
1

Normalmente é mais fácil olhar o cabeçalho do arquivo, contar o número da coluna que você precisa ( c ) e então usar o Unix cut :

cut -f c -d, file.csv

Mas quando há muitas colunas ou muitos arquivos, uso o seguinte truque feio:

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

Testado no OSX, o file.csv é delimitado por vírgulas.

    
por 14.02.2014 / 17:44
1

Aqui está uma maneira rápida de selecionar uma única coluna.

Digamos que queremos a coluna chamada "foo":

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

Basicamente, pegue o cabeçalho, divida-o em várias linhas com um nome de coluna por linha, numere as linhas, selecione a linha com o nome desejado e recupere o número de linha associado; em seguida, use esse número de linha como o número da coluna para o comando de corte.

    
por 02.11.2014 / 05:26
1

Se o arquivo que você está lendo nunca poderia ser gerado pelo usuário, você poderia abusar da leitura incorporada:

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

A primeira linha inteira do arquivo de entrada é substituída na lista de argumentos, portanto, read é passado a todos os nomes de campo da linha de cabeçalho como nomes de variáveis. O primeiro deles recebe o 1 que seq 100 gera, o segundo recebe o 2, o terceiro obtém o 3 e assim por diante. Excesso de seq de saída é absorvido pela variável dummy extra . Se você souber o número de colunas de entrada antecipadamente, poderá alterar as 100 para corresponder e se livrar de extra .

O script awk é uma cadeia com aspas duplas, permitindo que as variáveis do shell definidas por read sejam substituídas no script como awk números de campo.

    
por 27.02.2016 / 14:12
0

Procurando uma solução semelhante (preciso da coluna chamada id, que pode ter um número de coluna variável), deparei-me com esta:

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

Eu escrevi um script Python para esse propósito que basicamente funciona assim:

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))

Eu chamei de hgrep para cabeçalho grep , ele pode ser usado assim:

$ 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$

O script inteiro é um pouco mais longo, porque usa argparse para analisar os argumentos da linha de comando e o código é o seguinte:

#!/usr/bin/python3

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


def underline(s):
    return '3[4m{}3[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)
    
por 08.08.2016 / 20:26
0

awk , para toda a sua safra, é inerentemente indexado por números inteiros, como é cut .

Aqui estão várias ferramentas projetadas para lidar com dados indexados por nome (a maioria deles manipula apenas CSV e TSV, que são formatos de arquivo muito populares):

por 16.11.2016 / 03:14
0

Experimente este pequeno utilitário awk para cortar cabeçalhos específicos - link

Exemplo de uso -

awk -f toyeca-cutter.awk -v c="col1, col2, col3, col4" my_file.csv
    
por 07.08.2018 / 21:23

Tags