Bagaimana cara mencetak kolom tertentu dengan nama?


32

Saya memiliki file berikut:

id  name  age
1   ed    50
2   joe   70   

Saya ingin mencetak hanya kolom iddan age. Saat ini saya hanya menggunakan awk:

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

Namun, ini membutuhkan mengetahui nomor kolom. Apakah ada cara untuk melakukannya di mana saya dapat menggunakan nama kolom (ditentukan pada baris pertama), alih-alih nomor kolom?


7
cattidak perlu, BTW. Anda bisa menggunakanawk '{ print $1, $3 }' file.tsv
Eric Wilson

Jika tidak kolom nomor , maka apa yang ingin Anda tergantung pada?
rozcietrzewiacz

2
@rozcietrzewiacz Nama; dia ingin mengatakan idbukannya $1dan agebukannya$3
Michael Mrozek

lihat juga diskusi tentang stackoverflow
Hotschke

Jawaban:


37

Mungkin kira-kira seperti ini:

$ 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

Jika Anda ingin menentukan kolom yang akan dicetak pada baris perintah, Anda dapat melakukan sesuatu seperti ini:

$ 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 

(Perhatikan -vsakelar untuk mendapatkan variabel yang ditentukan dalam BEGINblok.)


Saya telah menunda belajar awk ... apa cara terbaik untuk mendukung sejumlah variabel kolom? awk -f t.awk col1 col2 ... coln inputakan menjadi ideal; awk -f t.awk cols=col1,col2,...,coln inputakan bekerja juga
Brett Thomas

1
Memperbarui jawaban saya. Berhentilah menunda mempelajarinya jika Anda ingin melakukan hal-hal dengannya :)
Mat

3
Contoh ke-2 tidak menampilkan kolom dalam urutan yang diharapkan, for (i in out)tidak memiliki urutan bawaan. gawkmenawarkan PROCINFO["sorted_in"]sebagai solusi, iterasi indeks dengan for( ; ; )mungkin lebih baik.
mr.spuratic

@ BrettThomas, sangat merekomendasikan tutorial ini . (Jika Anda memiliki akses ke lynda.com, saya bahkan lebih merekomendasikan "Awk Essential Training," yang mencakup semua materi yang sama tetapi lebih ringkas dan dengan latihan.)
Wildcard

Mr. Spuratic, Anda seorang pria. Saya berlari di untuk (i in out) masalah, bekerja dengan baik bidang w / 3, ketika saya menambahkan 2 itu 4,5,1,2,3, bukannya 1,2,3,4,5 seperti yang saya harapkan . Untuk mendapatkannya agar Anda harus lakukan untuk (i = 1; i <= length (out); i ++)
Severun

5

Hanya dengan memasukkan solusi Perl ke dalam lot:

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

Konversikan data input ke format csv dan gunakan alat csv seperti csvcutdari csvkit:

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

Instal csvkit:

$ pip install csvkit

Gunakan trdengan opsi pemerasannya -suntuk mengubahnya menjadi file csv yang valid dan menerapkan csvcut:

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

Jika Anda ingin kembali ke format data lama, Anda dapat menggunakan tr ',' ' ' | column -t

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

Catatan

  • csvkit juga mendukung pembatas yang berbeda ( opsi bersama -d atau --delimiter), tetapi mengembalikan file csv:

    • Jika file hanya menggunakan spasi untuk memisahkan kolom (tidak ada tab sama sekali), berikut ini berfungsi

      $ csvcut -d ' ' -S -c 'id,age' test-cols.dat
      id,age
      1,50
      2,70
    • Jika file menggunakan tab untuk memisahkan kolom, berikut ini berfungsi dan csvformatdapat digunakan untuk mendapatkan kembali file tsv:

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

      Sejauh yang saya periksa, hanya satu tab yang diizinkan.

  • csvlook dapat memformat tabel dalam format tabel penurunan harga

    $ csvcut -t -c "id,age" test-cols.dat | csvlook
    | id | age |
    | -- | --- |
    |  1 |  50 |
    |  2 |  70 |
  • UUOC (Penggunaan Cat yang Tidak Berguna) : Saya suka cara ini untuk membangun perintah.


+1. Tapi penggunaan yang tidak perlu trjuga. File TSV didukung secara langsung, tanpa perlu mengubahnya menjadi CSV. Opsi -t(alias --tabs) memberi tahu cvscutuntuk menggunakan tab sebagai pembatas bidang. Dan -datau --delimitermenggunakan karakter apa pun sebagai pembatas.
Kasus

Dengan beberapa pengujian, tampaknya opsi -ddan -tsetengah rusak. mereka bekerja untuk menentukan pembatas input, tetapi pembatas output hardcoded untuk selalu menjadi koma. IMO yang rusak - itu harus sama dengan pembatas input atau memiliki opsi lain untuk memungkinkan pengguna untuk mengatur pembatas output, seperti awkFS dan OFS vars.
cas

4

Jika Anda hanya ingin merujuk ke bidang itu dengan nama mereka alih-alih angka, Anda dapat menggunakan read:

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

EDIT

Akhirnya aku melihat maksudmu! Inilah fungsi bash yang hanya akan mencetak kolom yang Anda tentukan pada baris perintah (berdasarkan nama ).

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

Inilah cara Anda dapat menggunakannya dengan file yang Anda sajikan:

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

(Fungsi membaca stdin. < file.tsv printColumns ... Setara dengan printColumns ... < file.tsvdan 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

Catatan: Perhatikan nama kolom yang Anda minta! Versi ini tidak memiliki pemeriksaan kewarasan, sehingga hal-hal buruk dapat terjadi jika salah satu argumennya seperti"anything; rm /my/precious/file"


1
Ini juga membutuhkan mengetahui nomor kolom. Hanya karena Anda menamai mereka id, namedan age, tidak mengubah fakta bahwa pesanan tersebut dikodekan dengan keras di readbaris Anda .
janmoesen

1
@janmoesen Ya, saya akhirnya mengerti :)
rozcietrzewiacz

Ini bagus, terima kasih. Saya bekerja dengan file besar (1000 kolom, jutaan baris) jadi saya menggunakan awk untuk kecepatan.
Brett Thomas

@ BrettThomas Oh, begitu. Saya sangat penasaran: bisakah Anda memposting beberapa patokan yang memberikan perbandingan waktu? (Gunakan 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

Untuk apa nilainya. Ini dapat menangani sejumlah kolom di sumber, dan sejumlah kolom untuk dicetak, dalam urutan output apa pun yang Anda pilih; hanya mengatur ulang args ...

misalnya. panggilan: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[@]}") 

keluaran

id      age
1       50
2       70

2

Jika file yang Anda baca tidak pernah mungkin dibuat oleh pengguna, Anda dapat menyalahgunakan yang sudah dibaca:

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

Seluruh baris pertama file input diganti ke dalam daftar argumen, jadi readditeruskan semua nama bidang dari baris header sebagai nama variabel. Yang pertama akan diberi 1 yang seq 100menghasilkan, yang kedua mendapat 2, yang ketiga mendapat 3 dan seterusnya. Kelebihan seqoutput direndam oleh variabel dummy extra. Jika Anda mengetahui jumlah kolom input sebelumnya, Anda dapat mengubah 100 untuk mencocokkan dan menghilangkanextra .

The awkScript adalah string dikutip ganda, memungkinkan variabel shell didefinisikan oleh readharus diganti ke dalam naskah sebagai awknomor lapangan.


1

Biasanya lebih mudah hanya dengan melihat file header, menghitung jumlah kolom yang Anda butuhkan ( c ) dan kemudian menggunakan Unix cut:

cut -f c -d, file.csv

Tetapi ketika ada banyak kolom atau banyak file saya menggunakan trik jelek berikut:

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

Diuji pada OSX, file.csvkoma-delimted.


1

Inilah satu cara cepat untuk memilih satu kolom.

Katakanlah kita ingin kolom bernama "foo":

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

Pada dasarnya, ambil baris tajuk, bagi menjadi beberapa baris dengan satu nama kolom per baris, beri nomor pada baris, pilih baris dengan nama yang diinginkan, dan ambil nomor baris yang terkait; kemudian gunakan nomor baris itu sebagai nomor kolom ke perintah cut.


0

Mencari solusi yang sama (saya perlu kolom bernama id, yang mungkin memiliki nomor kolom yang bervariasi), saya menemukan yang ini:

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

0

Saya menulis skrip Python untuk tujuan ini yang pada dasarnya berfungsi seperti ini:

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

Saya menyebutnya hgrepuntuk sundulan grep , dapat digunakan seperti ini:

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

Keseluruhan skrip sedikit lebih lama, karena digunakan argparseuntuk menguraikan argumen baris perintah dan kode adalah sebagai berikut:

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


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.