วิธีพิมพ์คอลัมน์บางชื่อ?


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
Eric Wilson

ถ้าไม่ได้หมายเลขคอลัมน์แล้วสิ่งที่คุณต้องการจะขึ้นอยู่กับ?
rozcietrzewiacz

2
@rozcietrzewiacz ชื่อ; เขาต้องการที่จะพูดidแทน$1และageแทนที่จะเป็น$3
Michael Mrozek

ดูการสนทนาเกี่ยวกับ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 -f t.awk col1 col2 ... coln inputจะเหมาะ; awk -f t.awk cols=col1,col2,...,coln inputจะทำงานด้วย
Brett Thomas

1
อัปเดตคำตอบของฉัน หยุดการวางปิดการเรียนรู้ถ้าคุณต้องการที่จะทำสิ่งที่มีมัน :)
จ้า

3
ตัวอย่างที่ 2 ไม่ส่งออกคอลัมน์ตามลำดับที่คาดไว้for (i in out)ไม่มีการเรียงลำดับโดยธรรมชาติ gawkข้อเสนอPROCINFO["sorted_in"]เป็นวิธีแก้ปัญหาการทำซ้ำดัชนีด้วยfor( ; ; )น่าจะดีกว่า
mr.spuratic

@BrettThomas, ขอแนะนำการกวดวิชานี้ (หากคุณสามารถเข้าถึง lynda.com ได้ฉันขอแนะนำ "Awk Essential Training" ซึ่งครอบคลุมเนื้อหาเดียวกันทั้งหมด แต่กระชับและฝึกซ้อมมากขึ้น)
Wildcard

นาย Spuratic คุณดาแมน ฉันพบปัญหา (สำหรับฉันเข้า) ทำงานได้ดีกับฟิลด์ 3 เมื่อฉันเพิ่ม 2 มันได้ 4,5,1,2,3 แทนที่จะเป็น 1,2,3,4,5 เหมือนที่ฉันคาดไว้ . ในการรับมันตามลำดับคุณต้องทำเพื่อ (i = 1; i <= ความยาว (ออก); i ++)
Severun

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 สามารถจัดรูปแบบตารางในรูปแบบตาราง markdown

    $ 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ตัวเลือกจะเสียกึ่ง มันทำงานเพื่อระบุตัวคั่นอินพุต แต่ตัวคั่นเอาต์พุตจะถูกฮาร์ดโค้ดให้เป็นจุลภาคเสมอ IMO ที่ใช้งานไม่ได้ควรเป็นตัวคั่นอินพุตหรือมีตัวเลือกอื่นเพื่อให้ผู้ใช้ตั้งค่าตัวคั่นเอาต์พุตเช่นawkFS และ OFS vars
cas

4

หากคุณต้องการอ้างถึงเขตข้อมูลเหล่านั้นด้วยชื่อของพวกเขาแทนตัวเลขคุณสามารถใช้read:

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

แก้ไข

ฉันเห็นความหมายของคุณในที่สุด! นี่คือฟังก์ชั่นทุบตีที่จะพิมพ์เฉพาะคอลัมน์ที่คุณระบุในบรรทัดคำสั่ง (ตามชื่อ )

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

นี่เป็นสิ่งที่ดีขอบคุณ ฉันทำงานกับไฟล์ขนาดใหญ่ (1,000 คอลัมน์, นับล้านแถว) ดังนั้นฉันจึงใช้ awk เพื่อเพิ่มความเร็ว
Brett Thomas

@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

สำหรับสิ่งที่คุ้มค่า สิ่งนี้สามารถจัดการคอลัมน์จำนวนเท่าใดก็ได้ในแหล่งที่มาและจำนวนคอลัมน์ที่จะพิมพ์ในลำดับเอาต์พุตที่คุณเลือก เพียงแค่จัดเรียง args ...

เช่น. โทร: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

กำลังมองหาโซลูชันที่คล้ายกัน (ฉันต้องการคอลัมน์ชื่อ id ซึ่งอาจมีจำนวนคอลัมน์ที่แตกต่างกัน) ฉันเจอสิ่งนี้:

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 ซึ่งเป็นรูปแบบไฟล์ที่นิยมมาก):


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.