วิธีจัดการไฟล์ CSV ด้วย sed หรือ awk?


23

ฉันจะทำสิ่งต่อไปนี้กับไฟล์ CSV โดยใช้sedหรือawk?

  • ลบคอลัมน์
  • ทำซ้ำคอลัมน์
  • ย้ายคอลัมน์

ฉันมีโต๊ะใหญ่กว่า 200 sedแถวและฉันไม่ว่าคุ้นเคยกับ


1
Cross โพสต์บน AskUbuntu
enzotib

@enzotib คุณสามารถโพสต์ลิงค์ได้หรือไม่
n0pe

@MaxMackie askubuntu.com/questions/88142/… . ฉันไม่สามารถรับ mod ที่นั่นได้ในเวลานี้ดังนั้นฉันจึงตั้งค่าสถานะเพื่อขอให้พวกเขาย้ายหากพวกเขาเต็มใจ มันมีคำตอบที่ยอมรับแล้วดังนั้นฉันไม่แน่ใจว่าพวกเขาจะ
Michael Mrozek

@MichaelMrozek อืมมักจะเกิดอะไรขึ้นในสถานการณ์เหล่านี้? พวกเราทำสำเนาซ้ำหรือไม่?
n0pe

1
หากคุณไม่จำเป็นต้องเรียกใช้บนระบบที่มีเครื่องมือพื้นฐานเท่านั้นให้ดูที่มีเครื่องมือบรรทัดคำสั่งที่มีประสิทธิภาพสำหรับการประมวลผลไฟล์ csv หรือไม่?
Gilles 'SO- หยุดความชั่วร้าย'

คำตอบ:


7

นอกเหนือจากวิธีการตัดและจัดเรียงเขตข้อมูลอีกครั้ง (ครอบคลุมในคำตอบอื่น ๆ ) มีปัญหาของเขตข้อมูล CSV ที่เล่นโวหาร

หากข้อมูลของคุณอยู่ในหมวดหมู่ "แปลกประหลาด" การกรองล่วงหน้าและโพสต์เล็กน้อยสามารถดูแลได้ ฟิลเตอร์ที่แสดงด้านล่างต้องใช้ตัวละคร\x01, \x02, \x03, \x04ไม่ปรากฏที่ใดก็ได้ในข้อมูลของคุณ

นี่คือตัวกรองที่ล้อมรอบการawkถ่ายโอนข้อมูล แบบง่าย

หมายเหตุ: ฟิลด์ห้ามีเลย์เอาต์ "เขตข้อมูลที่อ้างถึง" ไม่ถูกต้อง / ไม่สมบูรณ์ แต่จะมีรูปแบบที่ไม่เป็นทางการที่ส่วนท้ายของแถว (ขึ้นอยู่กับตัวแยกวิเคราะห์ CSV) แต่แน่นอนว่ามันจะทำให้เกิดผลลัพธ์ที่ยังไม่ได้ชำระที่มีปัญหาหากต้องสลับออกจากตำแหน่งสิ้นสุดแถวปัจจุบัน

อัปเดต; user121196ได้ชี้ให้เห็นข้อผิดพลาดเมื่อเครื่องหมายจุลภาคอยู่ข้างหน้าคำพูดต่อท้าย นี่คือการแก้ไข

ข้อมูล

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

รหัส

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

ผลลัพธ์:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

นี่คือตัวกรองล่วงหน้าขยายออกพร้อมกับความคิดเห็น กรองโพสต์เป็นเพียงการผกผันของความ , ,
\x01\x02\x03\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'

คุณจะลบคอลัมน์ที่ n ตามตัวกรองนี้ได้อย่างไร
user121196

@ user121196 - ตามที่กล่าวไว้ในประโยคเปิดคำตอบนี้แสดงวิธีที่จะทำให้ข้อมูล CSV สอดคล้องกันมากขึ้น .. เช่น โดยการแทนที่เครื่องหมายจุลภาคอัญประกาศเป็นการชั่วคราวด้วยอักขระโทเค็นที่เป็นกลาง ... จากนั้นเปลี่ยนกลับเป็นเครื่องหมายจุลภาคหลังจากย้าย / ตัด / ลบ อีกครั้งเป็นที่กล่าวถึงการย้าย / ตัด / ขั้นตอนการลบจะถูกแทนที่ด้วยง่าย awk ข้อมูลการถ่ายโอนข้อมูล
Peter.O

1
มันล้มเหลวสำหรับกรณีนี้: "15111 N. Hayden Rd., Ste 160,", ""
user121196

@ user121196: ขอบคุณที่ชี้ให้เห็น ฉันได้อัปเดตคำตอบพร้อมการแก้ไขแล้ว
Peter.O

15

ขึ้นอยู่กับว่าไฟล์ CSV ของคุณใช้เครื่องหมายจุลภาคสำหรับตัวคั่นเท่านั้นหรือหากคุณมีความบ้าคลั่งเช่น:

ฟิลด์หนึ่ง, "ฟิลด์สอง", ฟิลด์สาม

สิ่งนี้ถือว่าคุณกำลังใช้ไฟล์ CSV ง่ายๆ:

การลบคอลัมน์

คุณสามารถกำจัดคอลัมน์เดียวได้หลายวิธี ฉันใช้คอลัมน์ 2 เป็นตัวอย่าง วิธีที่ง่ายที่สุดน่าจะเป็นในการใช้งานcutซึ่งจะช่วยให้คุณระบุตัวคั่น-dและเขตข้อมูลที่คุณต้องการพิมพ์-f; สิ่งนี้บอกให้แยกบนคอมม่าและฟิลด์เอาต์พุต 1 และฟิลด์ 3 ถึงจุดสิ้นสุด:

$ cut -d, -f1,3- /path/to/your/file

หากคุณต้องการใช้งานจริงsedคุณสามารถเขียนนิพจน์ทั่วไปที่ตรงกับn-1ฟิลด์แรกฟิลด์nth และส่วนที่เหลือและข้ามการแสดงผลลัพธ์nth (นี่nคือ 2 ดังนั้นกลุ่มแรกจะจับคู่1เวลา:) \{1\}:

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

มีหลายวิธีในการทำเช่นนี้awkไม่มีวิธีใดที่งดงามเป็นพิเศษ คุณสามารถใช้forลูปได้ แต่การใช้คอมม่าต่อท้ายเป็นความเจ็บปวด ไม่สนใจว่ามันจะเป็นสิ่งที่ต้องการ:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

ฉันพบว่าการเอาต์พุตฟิลด์ 1 ง่ายขึ้นจากนั้นใช้substrเพื่อดึงทุกอย่างออกหลังจากฟิลด์ 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

สิ่งนี้น่ารำคาญสำหรับคอลัมน์อื่น ๆ

การทำสำเนาคอลัมน์

ในส่วนsedนี้เป็นนิพจน์เดียวกับที่เคยเป็นมา แต่คุณยังจับคอลัมน์เป้าหมายและรวมกลุ่มนั้นหลาย ๆ ครั้งในการแทนที่:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

ในawkวิธีวนรอบมันจะเป็นสิ่งที่ต้องการ (อีกครั้งละเว้นเครื่องหมายจุลภาคต่อท้าย):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

substrวิธี:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(tcdyl ขึ้นมาด้วยวิธีที่ดีกว่าในคำตอบของเขา )

ย้ายคอลัมน์

ฉันคิดว่าsedวิธีการแก้ปัญหาเป็นไปตามธรรมชาติจากที่อื่น ๆ แต่มันก็เริ่มที่จะเยาะเย้ยนาน


นั่นเป็นคำตอบที่โหลด! +1 :)
jaypal singh


12

awkเป็นทางออกที่ดีที่สุดของคุณ awkพิมพ์ฟิลด์ตามจำนวนดังนั้น ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

หากต้องการลบคอลัมน์อย่าพิมพ์:

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

วิธีเปลี่ยนลำดับ:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

ตรงไปยังไฟล์ที่ส่งออก

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk สามารถจัดรูปแบบผลลัพธ์เช่นกัน

เอาต์พุตรูปแบบ Awk


เนื่องจากเป็น CSV BEGIN { FS=","; OFS=","; }คุณจะยังจำเป็นที่จะต้อง

1
ฉันคิดว่าแม้แต่ FS = OFS = "," ก็ใช้ได้

5

กำหนดไฟล์ที่คั่นด้วยช่องว่างในรูปแบบต่อไปนี้:

1 2 3 4 5

คุณสามารถลบฟิลด์ 2 ด้วย awk ดังนี้:

awk '{ sub($2,""); print}' file

ซึ่งผลตอบแทน

1  3 4 5

แทนที่คอลัมน์ 2 ด้วยคอลัมน์ n ตามความเหมาะสม

หากต้องการทำซ้ำคอลัมน์ 2

awk '{ col = $2 " " $2; $2 = col; print }' file

ซึ่งผลตอบแทน

1 2 2 3 4 5

ในการสลับคอลัมน์ 2 และ 3

awk '{temp = $2; $2 = $3; $3 = temp; print}'

ซึ่งผลตอบแทน

1 3 2 4 5

awk โดยทั่วไปดีมากที่จัดการกับแนวคิดของสาขา หากคุณกำลังจัดการกับ CSV และไม่ใช่ไฟล์ที่มีการเว้นวรรคคุณสามารถใช้

awk -F,

เพื่อกำหนดเขตข้อมูลของคุณเป็นเครื่องหมายจุลภาคแทนที่จะเว้นวรรค (ซึ่งเป็นค่าเริ่มต้น) มีทรัพยากร awk ที่ดีจำนวนหนึ่งซึ่งหนึ่งในนั้นเป็นรายการด้านล่าง

แหล่งที่มาสำหรับ # 3


ฉันไม่ค่อยรู้อะไรมากนักawkแต่ดูเหมือนว่าจะแยกช่องว่างออกแม้ว่าตัวคั่นฟิลด์จะเป็น,( ตัวคั่นฟิลด์จะควบคุมวิธีการจัดการอินพุต)
Michael Mrozek

@MichaelMrozek: ใช่มันเป็นตัวแปร OFS awk ที่ควบคุมตัวคั่นฟิลด์เอาต์พุต
enzotib

ใช่และที่ผมพูดถึงในคำตอบของฉันคุณสามารถส่งผ่านตัวเลือก -F จะ awk ที่จะเปลี่ยนแปลงตัวคั่น (เช่น -F)
tcdyl

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