วิธีการแตกไฟล์ csv หนึ่งคอลัมน์


111

หากฉันมีไฟล์ csv มีวิธีทุบตีด่วนในการพิมพ์เนื้อหาจากคอลัมน์เดียวหรือไม่ สามารถสันนิษฐานได้อย่างปลอดภัยว่าแต่ละแถวมีจำนวนคอลัมน์เท่ากัน แต่เนื้อหาของแต่ละคอลัมน์จะมีความยาวต่างกัน

คำตอบ:


137

คุณสามารถใช้ awk สำหรับสิ่งนี้ เปลี่ยน '$ 2' เป็นคอลัมน์ที่ n ที่คุณต้องการ

awk -F "\"*,\"*" '{print $2}' textfile.csv

13
echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}'จะพิมพ์แทน2 2,3,4,5
Igor Mikushkin

หากคุณเป็นคนโชคดีที่ใช้ GNU Tools ใน Windows คุณสามารถเรียกใช้ comand เดียวกันกับ @IgorMikushkin ได้ดังนี้:gawk -F"|" "{print $13}" files*.csv
Elidio Marquina

10
ฉันคิดว่าสิ่งนี้ล้มเหลวเมื่อมีสตริงที่มีลูกน้ำเช่น...,"string,string",...
โซเดียมไนเตรต

ฉันคิดว่าสำหรับ colume ที่ 1 และสุดท้ายนี้จะมีข้อบกพร่องบางอย่าง คอลัมน์แรกจะเริ่มต้นด้วย"และสุดท้ายจะลงท้ายด้วย"
BigTailWolf

บางโปรแกรมส่งคืนไฟล์ CSV ด้วยตัวคั่นที่แตกต่างกันดังนั้นจึงอาจจำเป็นต้องเปลี่ยนนิพจน์ทั่วไปให้สอดคล้องกัน ตัวอย่างตัวคั่นอัฒภาค: awk -F "\"*;\"*" '{print $2}' textfile.csv
gekkedev

88

ใช่. cat mycsv.csv | cut -d ',' -f3จะพิมพ์คอลัมน์ที่ 3


8
เว้นแต่คอลัมน์สองจะมีเครื่องหมายจุลภาคซึ่งในกรณีนี้คุณจะได้ครึ่งหลังของคอลัมน์สอง กรณีในจุด <col1>, "3,000", <col2> คำตอบของฉันไม่ได้ดีไปกว่าเมื่อเทียบกับปัญหานั้น ดังนั้นอย่าได้รับความเสียหาย
ซินธิไซเซอร์

@synthesizerpatel ฉันเห็นด้วยดีกว่าที่จะใช้awk
MattSizzle

1
เราไม่แน่ใจว่าไฟล์ CSV ของเขามีเครื่องหมายคำพูดคู่เพื่อแยกความแตกต่างของค่าความแตกต่าง มันจะดีกว่าถ้าเขาจัดเตรียมไฟล์อินพุตเพื่อให้เราสามารถประเมินโซลูชันที่เหมาะสมที่สุดได้
Idriss Neumann

51

วิธีที่ง่ายที่สุดที่ฉันสามารถทำได้คือใช้ csvtool ฉันมีกรณีการใช้งานอื่น ๆ เช่นกันในการใช้ csvtool และสามารถจัดการเครื่องหมายคำพูดหรือตัวคั่นได้อย่างเหมาะสมหากปรากฏในข้อมูลคอลัมน์เอง

csvtool format '%(2)\n' input.csv

การแทนที่ 2 ด้วยหมายเลขคอลัมน์จะดึงข้อมูลคอลัมน์ที่คุณต้องการได้อย่างมีประสิทธิภาพ


14
นี่ควรเป็นคำตอบที่ได้รับการยอมรับ เครื่องมือนี้รู้วิธีจัดการกับไฟล์ CSV นอกเหนือจากการใช้เครื่องหมายจุลภาคเป็นตัวคั่นฟิลด์ ในการแยกคอลัมน์ที่ 2 "csvtool col 2 input.csv"
Vladislavs Dovgalecs

3
โปรดทราบว่า ... หากคุณต้องการใช้ csvtool กับอินพุตมาตรฐาน (ตัวอย่าง csv มาจากคำสั่งอื่น) เป็นสิ่งเช่นนี้cat input.csv | csvtool formath '%(2)\n' -หมายเหตุฉันรู้ว่า cat ที่นี่ไม่มีประโยชน์ แต่ย่อยสำหรับคำสั่งใด ๆ ที่ปกติจะส่งออก csv
General Redneck

มีฟิลด์หลายformat '%(2)\n'บรรทัดคำสั่งไม่สามารถบอกได้ว่าฟิลด์ใดฟิลด์หนึ่งสิ้นสุดลง (csvtool 1.4.2)
jarno

1
เวอร์ชันที่ใหม่กว่าcsvtoolดูเหมือนจะต้องใช้-เป็นชื่อไฟล์อินพุตเพื่ออ่านจาก stdin
Connor Clark

@GeneralRedneck ทำไมต้องใช้ cat? และรูปแบบไม่ใช่รูปแบบcsvtool format '%(1),%(10)\n' - < in.csv > out.csv
sijanec

15

มาถึงที่นี่เพื่อดึงข้อมูลจากไฟล์ที่คั่นด้วยแท็บ คิดว่าฉันจะเพิ่ม

cat textfile.tsv | cut -f2 -s

โดย-f2แยกคอลัมน์ 2 คอลัมน์ที่ไม่ได้จัดทำดัชนีที่ไม่ใช่ศูนย์หรือคอลัมน์ที่สอง


ง่ายตรงประเด็นเกินไปและปรับเปลี่ยนได้ง่ายกว่าตัวอย่างอื่น ๆ ขอบคุณ!
Nick Jennings

6
Nitpicking แต่catไม่จำเป็น:< textfile.tsv cut -f2 -s
Anne van Rossum

8

คำตอบหลายข้อสำหรับคำถามนี้ดีมากและบางคนยังมองในมุมกลับ ฉันต้องการเพิ่มคำตอบง่ายๆที่สามารถใช้ได้ทุกวัน ...

FS (Field Separator) คือตัวแปรที่มีค่า dafaulted เป็นช่องว่าง ดังนั้นโดยค่าเริ่มต้น awk จะแบ่งที่ว่างสำหรับบรรทัดใด ๆ

ดังนั้นการใช้ BEGIN (ดำเนินการก่อนป้อนข้อมูล) เราสามารถตั้งค่าฟิลด์นี้เป็นอะไรก็ได้ที่เราต้องการ ...

awk 'BEGIN {FS = ","}; {print $3}'

โค้ดด้านบนจะพิมพ์คอลัมน์ที่ 3 ในไฟล์ csv


1
ฉันได้ลองสิ่งนี้แล้วและยังถือว่ามีเครื่องหมายจุลภาคอยู่ในช่องที่ยกมา
Daniel C. Sobral

5

คำตอบอื่น ๆ ใช้ได้ดี แต่เนื่องจากคุณขอวิธีแก้ปัญหาโดยใช้เพียง bash shell คุณสามารถทำได้:

AirBoxOmega:~ d$ cat > file #First we'll create a basic CSV
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10

จากนั้นคุณสามารถดึงคอลัมน์ (รายการแรกในตัวอย่างนี้) ดังนี้:

AirBoxOmega:~ d$ while IFS=, read -a csv_line;do echo "${csv_line[0]}";done < file
a
1
a
1
a
1
a
1
a
1
a
1

มีสองสิ่งเกิดขึ้นที่นี่:

  • while IFS=,- นี่คือการใช้เครื่องหมายจุลภาคเป็น IFS (Internal Field Separator) ซึ่งเป็นสิ่งที่เชลล์ใช้เพื่อทราบว่าอะไรที่แยกฟิลด์ (บล็อกของข้อความ) ดังนั้นการพูดว่า IFS = ก็เหมือนกับการพูดว่า "a, b" เหมือนกับ "a b" ก็คือถ้า IFS = "" (ซึ่งเป็นค่าเริ่มต้น)

  • read -a csv_line; - นี่คือการบอกว่าอ่านในแต่ละบรรทัดทีละบรรทัดและสร้างอาร์เรย์โดยแต่ละองค์ประกอบเรียกว่า "csv_line" และส่งไปยังส่วน "do" ของ while loop ของเรา

  • do echo "${csv_line[0]}";done < file- ตอนนี้เราอยู่ในเฟส "do" และเรากำลังพูดว่า echo องค์ประกอบที่ 0 ของอาร์เรย์ "csv_line" การดำเนินการนี้ซ้ำในทุกบรรทัดของไฟล์ < fileส่วนหนึ่งเป็นเพียงการบอกห่วงขณะที่อ่านจาก หมายเหตุ: จำไว้ว่าใน bash อาร์เรย์เป็น 0 ดัชนีดังนั้นคอลัมน์แรกจึงเป็นองค์ประกอบที่ 0

คุณมีมันแล้วดึงคอลัมน์จาก CSV ในเชลล์ออกมา วิธีแก้ปัญหาอื่น ๆ น่าจะเป็นประโยชน์มากกว่า แต่อันนี้เป็นการทุบตีล้วนๆ


5

คุณสามารถใช้ GNU Awk ดูบทความของคู่มือผู้ใช้นี้ ในการปรับปรุงโซลูชันที่นำเสนอในบทความ (ในเดือนมิถุนายน 2015) คำสั่ง gawk ต่อไปนี้อนุญาตให้ใส่เครื่องหมายคำพูดคู่ในช่องที่มีเครื่องหมายอัญประกาศคู่ เครื่องหมายคำพูดคู่จะทำเครื่องหมายด้วยเครื่องหมายคำพูดคู่ ("") สองครั้งที่นั่น นอกจากนี้ยังอนุญาตให้มีช่องว่างแต่ก็ไม่สามารถจัดการกับเขตข้อมูลหลายเส้นได้ ตัวอย่างต่อไปนี้พิมพ์คอลัมน์ที่ 3 (ผ่านc=3) ของ textfile.csv:

#!/bin/bash
gawk -- '
BEGIN{
    FPAT="([^,\"]*)|(\"((\"\")*[^\"]*)*\")"
}
{
    if (substr($c, 1, 1) == "\"") {
        $c = substr($c, 2, length($c) - 2) # Get the text within the two quotes
        gsub("\"\"", "\"", $c)  # Normalize double quotes
    }
    print $c
}
' c=3 < <(dos2unix <textfile.csv)

สังเกตการใช้dos2unixเพื่อแปลงตัวแบ่งสไตล์ DOS ที่เป็นไปได้ (CRLF เช่น "\ r \ n") และการเข้ารหัส UTF-16 (พร้อมเครื่องหมายลำดับไบต์) เป็น "\ n" และ UTF-8 (โดยไม่มีเครื่องหมายลำดับไบต์) ตามลำดับ มาตรฐานการใช้งานไฟล์ CSV CRLF เป็นเส้นแบ่งดูวิกิพีเดีย

หากอินพุตอาจมีหลายช่องคุณสามารถใช้สคริปต์ต่อไปนี้ สังเกตการใช้สตริงพิเศษสำหรับการแยกเร็กคอร์ดในเอาต์พุต (เนื่องจากตัวคั่นดีฟอลต์ขึ้นบรรทัดใหม่อาจเกิดขึ้นภายในเร็กคอร์ด) อีกครั้งตัวอย่างต่อไปนี้พิมพ์คอลัมน์ที่ 3 (ผ่านc=3) ของ textfile.csv:

#!/bin/bash
gawk -- '
BEGIN{
    RS="\0" # Read the whole input file as one record;
    # assume there is no null character in input.
    FS="" # Suppose this setting eases internal splitting work.
    ORS="\n####\n" # Use a special output separator to show borders of a record.
}
{
    nof=patsplit($0, a, /([^,"\n]*)|("(("")*[^"]*)*")/, seps)
    field=0;
    for (i=1; i<=nof; i++){
        field++
        if (field==c) {
            if (substr(a[i], 1, 1) == "\"") {
                a[i] = substr(a[i], 2, length(a[i]) - 2) # Get the text within 
                # the two quotes.
                gsub(/""/, "\"", a[i])  # Normalize double quotes.
            }
            print a[i]
        }
        if (seps[i]!=",") field=0
    }
}
' c=3 < <(dos2unix <textfile.csv)

มีแนวทางอื่นในการแก้ปัญหา csvquoteสามารถส่งออกเนื้อหาของไฟล์ CSV ที่แก้ไขเพื่อให้มีการแปลงอักขระพิเศษภายในฟิลด์เพื่อให้สามารถใช้เครื่องมือประมวลผลข้อความ Unix ตามปกติเพื่อเลือกคอลัมน์บางคอลัมน์ได้ ตัวอย่างเช่นโค้ดต่อไปนี้แสดงคอลัมน์ที่สาม:

csvquote textfile.csv | cut -d ',' -f 3 | csvquote -u

csvquote สามารถใช้เพื่อประมวลผลไฟล์ขนาดใหญ่โดยพลการ


5

นี่คือตัวอย่างไฟล์ csv ที่มี 2 คอลัมน์

myTooth.csv

Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom

ในการรับคอลัมน์แรกให้ใช้:

cut -d, -f1 myTooth.csv

f ย่อมาจาก Field และ d หมายถึงตัวคั่น

การรันคำสั่งดังกล่าวจะสร้างผลลัพธ์ต่อไปนี้

เอาต์พุต

Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28

ในการรับคอลัมน์ที่ 2 เท่านั้น:

cut -d, -f2 myTooth.csv

และนี่คือเอาต์พุต เอาต์พุต

Tooth
wisdom
canine
canine
wisdom
incisor

กรณีการใช้งานอื่น:

ไฟล์อินพุต csv ของคุณมี 10 คอลัมน์และคุณต้องการคอลัมน์ 2 ถึง 5 และคอลัมน์ 8 โดยใช้คอมมาเป็นตัวคั่น "

ตัดใช้ -f (หมายถึง "เขตข้อมูล") เพื่อระบุคอลัมน์และ -d (หมายถึง "ตัวคั่น") เพื่อระบุตัวคั่น คุณต้องระบุหลังเนื่องจากไฟล์บางไฟล์อาจใช้ช่องว่างแท็บหรือโคลอนเพื่อแยกคอลัมน์

cut -f 2-5,8 -d , myvalues.csv

cut เป็นยูทิลิตี้คำสั่งและนี่คือตัวอย่างเพิ่มเติม:

SYNOPSIS
     cut -b list [-n] [file ...]
     cut -c list [file ...]
     cut -f list [-d delim] [-s] [file ...]

4

ฉันต้องการการแยกวิเคราะห์ CSV ที่เหมาะสมไม่ใช่cut/ awkและคำอธิษฐาน ฉันกำลังลองใช้กับ mac โดยไม่มีcsvtoolแต่mac มาพร้อมทับทิมดังนั้นคุณสามารถทำได้:

echo "require 'csv'; CSV.read('new.csv').each {|data| puts data[34]}" | ruby

4

ขั้นแรกเราจะสร้าง CSV พื้นฐาน

[dumb@one pts]$ cat > file 
a,b,c,d,e,f,g,h,i,k  
1,2,3,4,5,6,7,8,9,10  
a,b,c,d,e,f,g,h,i,k  
1,2,3,4,5,6,7,8,9,10

จากนั้นเราจะได้คอลัมน์ที่ 1

[dumb@one pts]$  awk -F , '{print $1}' file  
a  
1  
a  
1

3
csvtool col 2 file.csv 

โดยที่ 2 คือคอลัมน์ที่คุณสนใจ

คุณยังสามารถทำได้

csvtool col 1,2 file.csv 

เพื่อทำหลายคอลัมน์


3

ฉันคิดว่าง่ายที่สุดคือใช้csvkit :

รับคอลัมน์ที่ 2: csvcut -c 2 file.csv

อย่างไรก็ตามยังมีcsvtoolและอาจมีเครื่องมือ csv bash อื่น ๆ อีกมากมาย:

sudo apt-get install csvtool (สำหรับระบบที่ใช้ Debian)

สิ่งนี้จะส่งคืนคอลัมน์โดยแถวแรกมี 'ID' อยู่ csvtool namedcol ID csv_file.csv

สิ่งนี้จะส่งคืนแถวที่สี่: csvtool col 4 csv_file.csv

หากคุณต้องการวางแถวส่วนหัว:

csvtool col 4 csv_file.csv | sed '1d'


2

ฉันสงสัยว่าทำไมไม่มีคำตอบใดที่กล่าวถึง csvkit

csvkit เป็นชุดเครื่องมือบรรทัดคำสั่งสำหรับการแปลงและทำงานกับ CSV

เอกสาร csvkit

ฉันใช้มันเฉพาะสำหรับการจัดการข้อมูล csv และจนถึงตอนนี้ฉันยังไม่พบปัญหาที่ฉันไม่สามารถแก้ไขได้โดยใช้ cvskit

ในการแยกคอลัมน์อย่างน้อยหนึ่งคอลัมน์จากไฟล์ cvs คุณสามารถใช้csvcutยูทิลิตี้ที่เป็นส่วนหนึ่งของกล่องเครื่องมือ ในการแยกคอลัมน์ที่สองให้ใช้คำสั่งนี้:

csvcut -c 2 filename_in.csv > filename_out.csv 

หน้าอ้างอิง csvcut

หากสตริงใน csv ถูกยกมาให้เพิ่มอักขระเครื่องหมายคำพูดด้วยqตัวเลือก:

csvcut -q '"' -c 2 filename_in.csv > filename_out.csv 

ติดตั้งด้วยpip install csvkitหรือsudo apt install csvkit.


1

คุณไม่สามารถทำได้หากไม่มีตัวแยกวิเคราะห์ CSV แบบเต็ม


1
เมื่อใดที่บางสิ่งนับเป็นตัวแยกวิเคราะห์ CSV แบบเต็ม ไม่cutนับ?
HelloGoodbye

0

เมื่อใช้รหัสนี้มาระยะหนึ่งแล้วจะไม่ "ด่วน" เว้นแต่คุณจะนับ "การตัดและวางจาก stackoverflow"

ใช้ตัวดำเนินการ $ {##} และ $ {%%} ในการวนซ้ำแทน IFS มันเรียกว่า 'err' และ 'die' และรองรับเฉพาะเครื่องหมายจุลภาคเส้นประและท่อเป็นตัวอักษร SEP (นั่นคือทั้งหมดที่ฉันต้องการ)

err()  { echo "${0##*/}: Error:" "$@" >&2; }
die()  { err "$@"; exit 1; }

# Return Nth field in a csv string, fields numbered starting with 1
csv_fldN() { fldN , "$1" "$2"; }

# Return Nth field in string of fields separated
# by SEP, fields numbered starting with 1
fldN() {
        local me="fldN: "
        local sep="$1"
        local fldnum="$2"
        local vals="$3"
        case "$sep" in
                -|,|\|) ;;
                *) die "$me: arg1 sep: unsupported separator '$sep'" ;;
        esac
        case "$fldnum" in
                [0-9]*) [ "$fldnum" -gt 0 ] || { err "$me: arg2 fldnum=$fldnum must be number greater or equal to 0."; return 1; } ;;
                *) { err "$me: arg2 fldnum=$fldnum must be number"; return 1;} ;;
        esac
        [ -z "$vals" ] && err "$me: missing arg2 vals: list of '$sep' separated values" && return 1
        fldnum=$(($fldnum - 1))
        while [ $fldnum -gt 0 ] ; do
                vals="${vals#*$sep}"
                fldnum=$(($fldnum - 1))
        done
        echo ${vals%%$sep*}
}

ตัวอย่าง:

$ CSVLINE="example,fields with whitespace,field3"
$ $ for fno in $(seq 3); do echo field$fno: $(csv_fldN $fno "$CSVLINE");  done
field1: example
field2: fields with whitespace
field3: field3

0

คุณยังสามารถใช้ในขณะวนซ้ำ

IFS=,
while read name val; do
        echo "............................"

        echo Name: "$name"
done<itemlst.csv

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