Belirli sütunlar ada göre nasıl yazdırılır?


32

Aşağıdaki dosya var:

id  name  age
1   ed    50
2   joe   70   

Sadece idve agesütunlarını yazdırmak istiyorum . Şu anda sadece kullanıyorum awk:

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

Ancak, bu sütun numaralarını bilmek gerektirir. Sütun numarası yerine (ilk satırda belirtilen) sütunun adını kullanabileceğim bir yol var mı?


7
catgerekli değil, BTW. Kullanabilirsinizawk '{ print $1, $3 }' file.tsv
Eric Wilson

Sütun numarası değilse , neye güvenmek istersiniz?
rozcietrzewiacz 22:11

2
@ rozcietrzewiacz adı; idyerine $1ve ageyerine söylemek istiyor$3
Michael Mrozek

Yanıtlar:


37

Belki böyle bir şey:

$ 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

Komut satırında yazdırılacak sütunları belirlemek istiyorsanız, bunun gibi bir şey yapabilirsiniz:

$ 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 

( Blokta -vtanımlanan değişkeni alma anahtarına dikkat edin BEGIN.)


Awk öğrenmeyi bırakıyorum ... değişken sayıda sütunu desteklemenin en iyi yolu nedir? awk -f t.awk col1 col2 ... coln inputideal olurdu; awk -f t.awk cols=col1,col2,...,coln inputde işe yarayacaktı
Brett Thomas

1
Cevabım güncellendi. Bununla bir şeyler yapmak istersen öğrenmeyi bırakmayı bırak :)
Mat

3
İkinci örnek, sütunları beklenen sırada for (i in out)vermez, içsel bir sıraya sahip değildir. Dizin üzerinde bir ite yinelenen bir çözüm olarak gawksunmaktadır , muhtemelen daha iyidir. PROCINFO["sorted_in"]for( ; ; )
mr.spuratic

@BrettThomas, bu dersi şiddetle tavsiye ediyorum . (Lynda.com sitesine erişiminiz varsa, aynı malzemeyi kapsayan ancak daha özlü ve pratik alıştırmalarla "Awk Essential Training" i şiddetle tavsiye ederim.)
Wildcard

Bay Spuratic, siz beyler. Ben (dışarıdan) sorunuyla karşılaştım, w / 3 tarlalarında iyi çalıştım, 2'yi eklediğimde beklediğim gibi 1,2,3,4,5 yerine 4,5,1,2,3 yaptı . Bunları sıraya koymak için yapmanız gerekenler (i = 1; i <= uzunluk (çıktı); i ++)
Severun

5

Sadece Perl çözümünü partiye fırlatmak:

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

Bir CSV biçimine giriş verileri dönüştürme ve bu şekilde bir CSV aracı kullanmak csvcutden csvkit:

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

Csvkit'i yükleyin:

$ pip install csvkit

Kullanım tronun sıkmak seçeneği ile -sgeçerli bir csv dosyasına dönüştürmek ve uygulamak için csvcut:

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

Eski veri formatına dönmek istiyorsanız, tr ',' ' ' | column -t

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

notlar

  • csvkit ayrıca farklı sınırlayıcıları da destekler ( paylaşılan seçenek -d veya --delimiter), ancak bir csv dosyası döndürür:

    • Dosya yalnızca sütunları ayırmak için boşluk kullanırsa (hiç sekme yok), aşağıdaki işler

      $ csvcut -d ' ' -S -c 'id,age' test-cols.dat
      id,age
      1,50
      2,70
    • Dosya sütunları ayırmak için bir sekme kullanıyorsa, aşağıdakiler çalışır ve csvformattsv dosyasını geri almak için kullanılabilir:

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

      Kontrol ettiğim sürece, sadece bir sekmeye izin verilir.

  • csvlook masayı bir markdown masa formatında biçimlendirebilir

    $ csvcut -t -c "id,age" test-cols.dat | csvlook
    | id | age |
    | -- | --- |
    |  1 |  50 |
    |  2 |  70 |
  • UUOC (Yararsız Kedinin Kullanımı) : Komutu oluşturmak için bu şekilde seviyorum.


+1. Ama gereksiz kullanımları trda. TSV dosyaları, CSV'ye dönüştürülmeleri gerekmeden doğrudan desteklenir. -t(Aka --tabs) seçeneği söyler cvscutalan ayırıcı olarak sekmeleri kullanın. Ve -dveya --delimiterherhangi bir karakteri sınırlayıcı olarak kullanmak için.
kas

Bazı testler ile görünüşte -dve -tseçenekler yarı bozulmuştur. giriş sınırlayıcısını belirtmek için çalışırlar, ancak çıkış sınırlayıcısının her zaman virgül olması zor kodlanır. Bozuk IMO - ya giriş sınırlayıcıyla aynı olmalı ya da kullanıcının çıkış sınırlayıcısını awkFS ve OFS varyasyonları gibi ayarlayabilmesi için başka bir seçeneğe sahip olmalıdır .
kas

4

Bu alanlara sadece sayılar yerine isimleriyle başvurmak istiyorsanız , şunları kullanabilirsiniz read:

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

DÜZENLE

Sonunda anlamını gördüm! İşte yalnızca komut satırında belirttiğiniz sütunları ( adıyla ) yazdıracak bir bash işlevi .

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

İşte sunduğunuz dosya ile nasıl kullanabilirsiniz:

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

(İşlev okur stdin. < file.tsv printColumns ... Eşittir printColumns ... < file.tsvve 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

Not: İstediğiniz sütunların isimlerine dikkat edin! Bu sürüm akıl sağlığı kontrolünden yoksundur, yani argümanlardan biri şöyle bir şeyse, kötü şeyler olabilir."anything; rm /my/precious/file"


1
Bu da sütun numaralarını bilmek gerektirir. Eğer onlara isim diye id, nameve agesiparişin, içinde kodlanmış olduğu gerçeğini değiştirmez readhattı.
janmoesen

1
@ janmoesen Evet, nihayet
konuya geldim

Bu çok hoş, teşekkürler. Büyük dosyalar (1000 sütun, milyonlarca satır) ile çalışıyorum bu yüzden awk için awk kullanıyorum.
Brett Thomas

@BrettThomas Oh anlıyorum. O zaman çok merak ediyorum: zaman karşılaştırması veren bir değerlendirme yazabilir misiniz? (Kullan 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

Buna değer. Bu, seçtiğiniz çıktı dizisinde, kaynaktaki herhangi bir sayıda sütunu ve yazdırılacak herhangi bir sayıda sütunu işleyebilir; sadece argüları yeniden ayarla ...

Örneğin. aramak: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[@]}") 

çıktı

id      age
1       50
2       70

2

Okuduğunuz dosya hiçbir zaman kullanıcı tarafından oluşturulamadıysa, okuma yerleşik yapısını kötüye kullanabilirsiniz:

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

Giriş dosyasının ilk satırının tamamı argüman listesine değiştirilir, bu nedenle readtüm alan adlarını başlık satırından değişken adları olarak geçirir. Bunlardan ilki seq 100üreten 1 , ikincisi 2, üçüncüsü 3, vb. Fazla seqçıkış, sahte değişken tarafından ıslatılır extra. Önceden giriş sütunlarının sayısını biliyorsanız, eşleşecek ve kurtulacak 100 değerini değiştirebilirsiniz extra.

awkKomut tarafından tanımlanan kabuk değişkenleri sağlayan bir çift tırnaklı dize readkadar komut dosyası ikame edilmesi awkalan numaraları.


1

Genellikle sadece dosya başlığına bakmak daha kolaydır, ihtiyacınız olan sütun sayısını sayın ( c ) ve ardından Unix kullanın cut:

cut -f c -d, file.csv

Ancak çok fazla sütun veya çok dosya olduğunda aşağıdaki çirkin numarayı kullanıyorum:

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

OSX'te test edilmiştir file.csv, virgülle sınırlandırılmıştır.


1

İşte tek bir sütun seçmek için hızlı bir yol.

Diyelim ki "foo" isimli sütunu istiyoruz:

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

Temel olarak, başlık satırını alın, satır başına bir sütun adıyla birden çok satıra bölün, satırları numaralandırın, istediğiniz adı taşıyan satırı seçin ve ilişkili satır numarasını alın; daha sonra bu satır numarasını cut komutunun sütun numarası olarak kullanın.


0

Benzer bir çözüm arıyorsanız (değişken bir sütun numarasına sahip olabilecek id adlı sütuna ihtiyacım var), bununla karşılaştım:

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

0

Bunun için temelde çalışan bir Python betiği yazdım:

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

Başlık grephgrep için aradım , böyle kullanılabilir:

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

Komut dosyasının tamamı biraz daha uzun, çünkü argparsekomut satırı argümanlarını ayrıştırmak için kullanıyor ve kod şöyle:

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


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.