Làm thế nào để in các cột nhất định theo tên?


32

Tôi có tập tin sau:

id  name  age
1   ed    50
2   joe   70   

Tôi muốn chỉ in idagecột. Ngay bây giờ tôi chỉ sử dụng awk:

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

Tuy nhiên, điều này đòi hỏi phải biết số cột. Có cách nào để làm điều đó khi tôi có thể sử dụng tên của cột (được chỉ định trên hàng đầu tiên), thay vì số cột không?


7
catkhông cần thiết, BTW. Bạn có thể sử dụngawk '{ print $1, $3 }' file.tsv
Eric Wilson

Nếu không được số cột , sau đó những gì bạn muốn phụ thuộc vào?
rozcietrzewiacz

2
@rozcietrzewiacz Tên; anh ấy muốn nói idthay vì $1agethay vì$3
Michael Mrozek

xem thêm thảo luận về stackoverflow
Hotschke 23/2/2015

Câu trả lời:


37

Có lẽ một cái gì đó như thế này:

$ 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

Nếu bạn muốn chỉ định các cột sẽ in trên dòng lệnh, bạn có thể làm một cái gì đó như thế này:

$ 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 

(Lưu ý công -vtắc để lấy biến được xác định trong BEGINkhối.)


Tôi đã ngừng học hỏi ... cách tốt nhất để hỗ trợ số lượng cột khác nhau là gì? awk -f t.awk col1 col2 ... coln inputsẽ là lý tưởng; awk -f t.awk cols=col1,col2,...,coln inputcũng sẽ hoạt động
Brett Thomas

1
Cập nhật câu trả lời của tôi. Chấm dứt việc bắt tắt học nó nếu bạn muốn làm mọi thứ với nó :)
Mat

3
Ví dụ thứ 2 không xuất ra các cột theo thứ tự dự kiến, for (i in out)không có thứ tự vốn có. gawkcung cấp PROCINFO["sorted_in"]như một giải pháp, lặp đi lặp lại chỉ số với một for( ; ; )có lẽ là tốt hơn.
mr.spuratic

@BrettThomas, rất khuyến khích hướng dẫn này . (Nếu bạn có quyền truy cập vào lynda.com, tôi thậm chí cao hơn đề nghị "AWK Đào tạo khái quát," trong đó bao gồm tất cả các tài liệu tương tự nhưng ngắn gọn hơn và với các bài tập thực hành.)
Wildcard

Ông Spuratic, bạn da man. Tôi đã chạy qua vấn đề for (i in out), hoạt động tốt với 3 trường, khi tôi thêm 2, nó đã làm 4,5,1,2,3, thay vì 1,2,3,4,5 như tôi mong đợi . Để có được chúng theo thứ tự bạn phải làm cho (i = 1; i <= length (out); i ++)
Severun

5

Chỉ cần đưa một giải pháp Perl vào lô:

#!/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

Chuyển đổi dữ liệu đầu vào sang định dạng csv và sử dụng công cụ csv, chẳng hạn như csvcuttừ csvkit:

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

Cài đặt csvkit:

$ pip install csvkit

Sử dụng trvới tùy chọn bóp của nó -sđể chuyển đổi nó thành tệp csv hợp lệ và áp dụng csvcut:

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

Nếu bạn muốn trở về định dạng dữ liệu cũ, bạn có thể sử dụng tr ',' ' ' | column -t

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

Ghi chú

  • csvkit cũng hỗ trợ các dấu phân cách khác nhau ( tùy chọn chia sẻ -d hoặc --delimiter), nhưng trả về tệp csv:

    • Nếu tệp chỉ sử dụng khoảng trắng để phân tách các cột (hoàn toàn không có tab), các công việc sau đây

      $ csvcut -d ' ' -S -c 'id,age' test-cols.dat
      id,age
      1,50
      2,70
      
    • Nếu tệp sử dụng tab để phân tách các cột, các công việc sau đây và csvformatcó thể được sử dụng để lấy lại tệp tsv:

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

      Theo như tôi đã kiểm tra, chỉ cho phép một tab duy nhất.

  • csvlook có thể định dạng bảng theo định dạng bảng đánh dấu

    $ csvcut -t -c "id,age" test-cols.dat | csvlook
    | id | age |
    | -- | --- |
    |  1 |  50 |
    |  2 |  70 |
    
  • UUOC (Usless Use Of Cat) : Tôi thích nó theo cách này để xây dựng lệnh.


+1. Nhưng sử dụng không cần thiết tr, quá. Các tệp TSV được hỗ trợ trực tiếp mà không cần chuyển đổi chúng sang CSV. Các -t(aka --tabs) tùy chọn kể cvscutđể sử dụng các tab như lĩnh vực delimiter. Và -dhoặc --delimiterđể sử dụng bất kỳ nhân vật như là dấu phân cách.
cas

Với một số thử nghiệm, có vẻ như -d-tcác tùy chọn là bán phá vỡ. chúng hoạt động để xác định dấu phân cách đầu vào, nhưng dấu phân cách đầu ra được mã hóa cứng để luôn luôn là dấu phẩy. IMO đã bị hỏng - nó phải giống như dấu phân cách đầu vào hoặc có một tùy chọn khác để cho phép người dùng đặt dấu phân cách đầu ra, như awkcác bình FS và OFS.
cas

4

Nếu bạn chỉ muốn tham chiếu đến các trường đó bằng tên của chúng thay vì số, bạn có thể sử dụng read:

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

CHỈNH SỬA

Tôi thấy ý nghĩa của bạn cuối cùng! Đây là một hàm bash sẽ chỉ in ra các cột bạn chỉ định trên dòng lệnh (theo tên ).

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

Đây là cách bạn có thể sử dụng nó với tệp bạn đã trình bày:

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

(Hàm đọc stdin. < file.tsv printColumns ... Tương đương với 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

Lưu ý: Hãy chú ý đến tên của các cột bạn yêu cầu! Phiên bản này thiếu kiểm tra sự tỉnh táo, vì vậy những điều khó chịu có thể xảy ra nếu một trong những tranh luận là một cái gì đó như"anything; rm /my/precious/file"


1
Điều này cũng đòi hỏi phải biết số cột. Chỉ vì bạn đặt tên cho chúng id, nameage, không thay đổi thực tế là thứ tự được mã hóa cứng trong readdòng của bạn .
janmoesen

1
@janmoesen Vâng, cuối cùng tôi đã có điểm :)
rozcietrzewiacz

Điều này là tốt đẹp, cảm ơn. Tôi đang làm việc với các tệp lớn (1000 cột, hàng triệu hàng) vì vậy tôi đang sử dụng awk cho tốc độ.
Brett Thomas

@BrettThomas ơi tôi hiểu rồi. Sau đó tôi rất tò mò: bạn có thể đăng một số điểm chuẩn để so sánh thời gian không? (Sử dụng time { command(s); }).
rozcietrzewiacz

@rozceitrewomsz: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

Cho những gì nó có giá trị. Điều này có thể xử lý bất kỳ số lượng cột nào trong nguồn và bất kỳ số lượng cột nào để in, trong bất kỳ chuỗi đầu ra nào bạn chọn; chỉ cần sắp xếp lại các đối số ...

ví dụ. gọi điện: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[@]}") 

đầu ra

id      age
1       50
2       70

2

Nếu tệp bạn đang đọc không bao giờ có thể do người dùng tạo, bạn có thể lạm dụng nội dung đã đọc:

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

Toàn bộ dòng đầu tiên của tệp đầu vào được thay thế vào danh sách đối số, do đó readđược chuyển tất cả các tên trường từ dòng tiêu đề dưới dạng tên biến. Cái đầu tiên trong số này được gán 1 seq 100tạo ra, cái thứ hai lấy 2, cái thứ ba lấy 3 và cứ thế. seqSản lượng dư thừa được ngâm lên bởi biến giả extra. Nếu bạn biết số lượng cột đầu vào trước thời hạn, bạn có thể thay đổi 100 để khớp và loại bỏ extra.

Tập awklệnh là một chuỗi trích dẫn kép, cho phép các biến shell được xác định bằng cách readđược thay thế vào tập lệnh dưới dạng awksố trường.


1

Thông thường sẽ dễ dàng hơn khi chỉ nhìn vào tiêu đề tệp, đếm số cột bạn cần ( c ) và sau đó sử dụng Unix cut:

cut -f c -d, file.csv

Nhưng khi có nhiều cột hoặc nhiều tệp tôi sử dụng thủ thuật xấu xí sau:

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

Đã thử nghiệm trên OSX, file.csvdấu phẩy được phân cách.


1

Đây là một cách nhanh chóng để chọn một cột duy nhất.

Nói rằng chúng tôi muốn cột có tên "foo":

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

Về cơ bản, lấy dòng tiêu đề, chia nó thành nhiều dòng với một tên cột trên mỗi dòng, đánh số dòng, chọn dòng có tên mong muốn và lấy số dòng liên quan; sau đó sử dụng số dòng đó làm số cột cho lệnh cắt.


0

Tìm kiếm một giải pháp tương tự (tôi cần cột có tên id, có thể có số cột khác nhau), tôi đã tìm thấy giải pháp này:

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

0

Tôi đã viết một kịch bản Python cho mục đích này về cơ bản hoạt động như thế này:

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

Tôi đã gọi nó hgrepcho tiêu đề grep , nó có thể được sử dụng như thế này:

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

Toàn bộ tập lệnh dài hơn một chút, vì nó sử dụng argparseđể phân tích các đối số dòng lệnh và mã như sau:

#!/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, đối với tất cả cổ điển của nó, vốn đã được lập chỉ mục số nguyên cut.

Dưới đây là một số công cụ được thiết kế để xử lý dữ liệu được lập chỉ mục tên (hầu hết chúng chỉ xử lý CSV và TSV, là các định dạng tệp rất phổ biến):


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.