Як надрукувати певні стовпці по імені?


32

У мене є такий файл:

id  name  age
1   ed    50
2   joe   70   

Я хочу надрукувати лише колонки idта age. Зараз я просто використовую awk:

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

Однак для цього потрібно знати номери стовпців. Чи є спосіб це зробити, коли я можу використовувати назву стовпця (вказаний у першому рядку) замість номера стовпця?


7
catне потрібно, BTW. Ви можете скористатисяawk '{ print $1, $3 }' file.tsv
Ерік Вілсон

Якщо не номер стовпця , то від чого ви хочете залежати?
rozcietrzewiacz

2
@rozcietrzewiacz Назва; він хоче сказати idзамість $1і ageзамість$3
Майкл Мрозек

Дивись також обговорення на StackOverflow
Hotschke

Відповіді:


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працював би теж
Бретт Томас

1
Оновлено мою відповідь. Перестаньте відкладати її, якщо хочете займатися цим :)
Мат

3
2-й приклад не виводить стовпці в очікуваному порядку, for (i in out)не має властивого впорядкування. Пропозиція gawkпропонує PROCINFO["sorted_in"]як рішення, ітерація над індексом з a for( ; ; ), ймовірно, краща.
mr.spuratic

@BrettThomas, дуже рекомендую цей підручник . (Якщо у вас є доступ до lynda.com, я ще більше рекомендую "Основні тренування Awk", які охоплюють все той же матеріал, але більш стисло та з практичними вправами.)
Wildcard

Пане Спуратич, ви людина. Я зіткнувся з проблемою (я вийшов), добре працював з 3 полями, коли я додав 2, це зробив 4,5,1,2,3, а не 1,2,3,4,5, як я очікував . Щоб отримати їх для того, що вам потрібно зробити для (i = 1; i <= length (out); i ++)
Северун

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, такий як csvcutзcsvkit :

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

Встановіть csvkit:

$ pip install csvkit

Скористайте trйого опцією стискання, -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(aka --tabs) пропонує cvscutвикористовувати вкладки як роздільник поля. І -dабо --delimiterвикористовувати будь-який символ як роздільник.
cas

З деяким тестуванням, здається, -dі -tваріанти напівзламані. вони працюють для визначення вхідного роздільника, але роздільник виводу є твердим кодом, щоб він завжди був комою. ІМО, який зламаний - він повинен або бути таким самим, як вхідний роздільник, або мати інший варіант, щоб дозволити користувачеві встановлювати роздільник виводу, як, наприклад awk, FS та OFS vars.
cas

4

Якщо ви просто хочете посилатися на ці поля за їх іменами, а не за номерами, ви можете використовувати read:

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

EDIT

Нарешті я побачив ваш сенс! Ось функція 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.tsvта 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

Примітка. Зверніть увагу на назви стовпців, які ви запитуєте! У цій версії не вистачає перевірок на обґрунтованість, тому можуть статися неприємні речі, якщо один із аргументів щось подібне"anything; rm /my/precious/file"


1
Для цього також потрібно знати номери стовпців. Просто тому, що ви їх називаєте id, nameі ageце не змінює того факту, що замовлення жорстко закодовано у вашому readрядку.
janmoesen

1
@janmoesen Так, я, нарешті, зрозумів :)
rozcietrzewiacz

Це приємно, дякую. Я працюю з великими файлами (1000 стовпців, мільйони рядків), тому використовую awk для швидкості.
Бретт Томас

@BrettThomas О, бачу. Мені тоді дуже цікаво: чи можете ви розмістити якийсь орієнтир, який дає порівняння часу? (Використовувати time { command(s); }).
rozcietrzewiacz

@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
Brett Thomas

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всі імена полів із рядка заголовка передаються як імена змінних. Перший з них присвоює 1, який seq 100генерує, другий отримує 2, третій отримує 3 і так далі. Зайвий seqвихід намочується фіксованою змінною extra. Якщо ви знаєте кількість вхідних стовпців заздалегідь, ви можете змінити 100 на відповідність та позбутисяextra .

awkСценарій є подвійні лапки, дозволяючи змінні оболонки , певний 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

Шукаючи подібне рішення (мені потрібен стовпчик з ідентифікатором, який може мати різний номер стовпця), я натрапив на це:

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)

0

awk, незважаючи на всі свої урожай, за своєю суттю індексовано цілими числами cut.

Ось кілька інструментів, призначених для обробки даних, індексованих іменами (більшість з них обробляє лише CSV та TSV, які є дуже популярними форматами файлів):


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.