มีวิธีการ 'uniq' โดยคอลัมน์หรือไม่


195

ฉันมีไฟล์. csv เช่นนี้:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

ฉันต้องลบอีเมลที่ซ้ำกัน (ทั้งบรรทัด) จากไฟล์ (เช่นหนึ่งในบรรทัดที่มีoverflow@example.comในตัวอย่างข้างต้น) ฉันจะใช้งานuniqในฟิลด์ 1 เท่านั้น (คั่นด้วยเครื่องหมายจุลภาค) ได้อย่างไร ตามman, uniqไม่ได้มีตัวเลือกสำหรับคอลัมน์

ฉันลองบางอย่างด้วยsort | uniqแต่ใช้งานไม่ได้

คำตอบ:


327
sort -u -t, -k1,1 file
  • -u สำหรับที่ไม่ซ้ำกัน
  • -t, เครื่องหมายจุลภาคจึงเป็นตัวคั่น
  • -k1,1 สำหรับเขตข้อมูลคีย์ 1

ผลการทดสอบ:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
สิ่งนี้จะไม่ทำงานหากคอลัมน์มีเครื่องหมายจุลภาค (อ้างข้อความ)
user775187

13
ทำไมคุณต้องการ 1 ใน -k1,1 ทำไมไม่เพียงแค่ -k1
hello_there_andy

18
@hello_there_andy: นี่คือคำอธิบายในคู่มือ ( man sort) มันย่อมาจากตำแหน่งเริ่มต้นและหยุด
Serrano

3
@CarlSmotricz: ผมทดสอบแล้วมันยืนยันในสิ่งที่sort's manpage บอกว่า: ' มีการตรวจสอบสำหรับการสั่งซื้ออย่างเข้มงวดโดยไม่ต้อง, การส่งออกเพียงครั้งแรกของการดำเนินการเท่ากัน .' ดังนั้นมันจึงเป็น "การเกิดซ้ำครั้งแรกของการทำสำเนาก่อนการเรียงลำดับ" -u--unique-c-c
Geremia

2
นี่เป็นการเปลี่ยนลำดับของเส้นด้วยใช่ไหม?
rkachach

104
awk -F"," '!_[$1]++' file
  • -F ตั้งค่าตัวคั่นฟิลด์
  • $1 เป็นสนามแรก
  • _[val]ค้นหาvalในแฮช_(ตัวแปรปกติ)
  • ++ การเพิ่มค่าและส่งคืนค่าเก่า
  • ! ส่งกลับค่าตรรกะไม่
  • มีการพิมพ์โดยนัยในตอนท้าย

4
วิธีนี้เร็วกว่าการเรียงลำดับสองเท่า
คำพิพากษา

9
สิ่งนี้ยังมีประโยชน์เพิ่มเติมในการรักษาสายในลำดับเดิม!
AffluentOwl

8
หากคุณต้องการuniq ล่าสุดแทนที่จะเป็นสคริปต์ตัวแรก awk นี้จะช่วยให้:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima

4
@eshwar เพียงเพิ่มเขตข้อมูลเพิ่มเติมลงในดัชนีพจนานุกรม! ตัวอย่างเช่น!_[$1][$2]++สามารถใช้เพื่อจัดเรียงตามสองฟิลด์แรก awkแม้ว่า -fu ของฉันจะไม่แข็งแรงพอที่จะสามารถสร้างความแตกต่างให้กับสนามได้ :(
Soham Chowdhury

1
ยอดเยี่ยม! ตัวเลือกนี้ดีกว่าคำตอบเพราะมันรักษาลำดับบรรทัด
rkachach

16

พิจารณาหลายคอลัมน์

จัดเรียงและให้รายการที่ไม่ซ้ำตามคอลัมน์ 1 และคอลัมน์ 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : โคลอนคือตัวคั่น
  • -k 1,1 -k 3,3 ตามคอลัมน์ 1 และคอลัมน์ 3

8

หรือถ้าคุณต้องการใช้ uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

ให้:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
ฉันต้องการชี้ให้เห็นถึงความเรียบง่ายที่เป็นไปได้: คุณสามารถทิ้งcat! แทนที่จะท่อเข้าไปใน TR, TR <เพียงแค่ให้อ่านไฟล์โดยใช้ การวางท่อผ่านcatเป็นภาวะแทรกซ้อนที่ไม่จำเป็นทั่วไปที่ใช้โดยผู้ฝึกหัด สำหรับข้อมูลจำนวนมากมีผลต่อประสิทธิภาพที่ต้องมี
Carl Smotricz

4
ดีแล้วที่รู้. ขอบคุณ! (แน่นอนว่าเหมาะสมแล้วที่คิดว่า "cat" และ "lazyness";))
Carsten C.

revการกลับของเขตข้อมูลได้ง่ายด้วย
Hielke Walinga

5

หากคุณต้องการเก็บสำเนาที่ซ้ำกันล่าสุดที่คุณสามารถใช้ได้

 tac a.csv | sort -u -t, -r -k1,1 |tac

ซึ่งเป็นความต้องการของฉัน

ที่นี่

tac จะย้อนกลับบรรทัดไฟล์ทีละบรรทัด


1

นี่คือวิธีที่ดีมาก

อันดับแรกจัดรูปแบบเนื้อหาที่คอลัมน์ที่จะเปรียบเทียบสำหรับความไม่ซ้ำกันคือความกว้างคงที่ วิธีหนึ่งในการทำเช่นนี้คือใช้ awk printf กับตัวระบุความกว้างของฟิลด์ / คอลัมน์ ("% 15s")

ตอนนี้อ็อพชัน -f และ -w ของ uniq สามารถใช้เพื่อข้ามฟิลด์ / คอลัมน์ก่อนหน้าและเพื่อระบุความกว้างการเปรียบเทียบ (คอลัมน์)

นี่คือสามตัวอย่าง

ในตัวอย่างแรก ...

1) ทำให้คอลัมน์ที่น่าสนใจมีความกว้างคงที่ชั่วคราวมากกว่าหรือเท่ากับความกว้างสูงสุดของฟิลด์

2) ใช้ตัวเลือก -f uniq เพื่อข้ามคอลัมน์ก่อนหน้าและใช้ตัวเลือก -w uniq เพื่อจำกัดความกว้างของ tmp_fixed_width

3) ลบช่องว่างต่อท้ายออกจากคอลัมน์ไปที่ "คืนค่า" ความกว้างของมัน (สมมติว่าไม่มีช่องว่างต่อท้ายมาก่อน)

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

ในตัวอย่างที่สอง ...

สร้างคอลัมน์ uniq ใหม่ 1 จากนั้นลบออกหลังจากใช้ตัวกรอง uniq แล้ว

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

ตัวอย่างที่สามเหมือนกันเป็นอย่างที่สอง แต่สำหรับหลายคอลัมน์

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

ง่ายกว่าการแยกคอลัมน์ด้วย awk ถ้าคุณต้องการลบทุกอย่างด้วยค่าที่แน่นอนสำหรับไฟล์ที่กำหนดทำไมไม่ทำ grep -v:

เช่นลบทุกอย่างด้วยค่า "col2" ในบรรทัดที่สอง: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

หากสิ่งนี้ยังไม่ดีพอเนื่องจากบางบรรทัดอาจถูกปล้นอย่างไม่เหมาะสมเนื่องจากอาจมีค่าการจับคู่ปรากฏในคอลัมน์อื่นคุณสามารถทำสิ่งนี้:

awk เพื่อแยกคอลัมน์ที่ละเมิด: เช่น

awk -F, '{print $2 "|" $line}'

-F ตั้งค่าเขตข้อมูลที่คั่นด้วย ",", $ 2 หมายถึงคอลัมน์ 2 ตามด้วยตัวคั่นแบบกำหนดเองบางส่วนจากนั้นทั้งบรรทัด จากนั้นคุณสามารถกรองโดยลบบรรทัดที่ขึ้นต้นด้วยค่าที่ละเมิด:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

แล้วตัดสิ่งที่อยู่ข้างหน้าตัวคั่นออก:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(หมายเหตุ - คำสั่ง sed เป็นเลอะเทอะเพราะมันไม่รวมค่าการหลบหลีกนอกจากนี้รูปแบบ sed ควรเป็นอะไรเช่น "[^ |] +" (เช่นอะไรก็ตามที่ไม่ใช่ตัวคั่น) แต่หวังว่านี่จะชัดเจนพอ


3
เขาไม่ต้องการลบบรรทัดเขาต้องการเก็บสำเนาหนึ่งบรรทัดด้วยสตริงที่ระบุ Uniq เป็นกรณีการใช้งานที่ถูกต้อง
ingyhere

-3

โดยเรียงลำดับไฟล์ที่มีครั้งแรกแล้วคุณสามารถนำไปใช้sortuniq

ดูเหมือนว่าจะเรียงไฟล์ได้ดี:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

คุณสามารถทำเวทย์มนตร์ AWK ได้:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

คอลัมน์นี้ไม่ซ้ำกันตามที่ถามในคำถาม นี่เป็นลักษณะเฉพาะสำหรับทั้งบรรทัด นอกจากนี้คุณไม่ต้องทำการเรียงลำดับเพื่อทำ uniq ทั้งสองเป็นเอกสิทธิ์เฉพาะบุคคล
Javid Jamae

1
ใช่คุณถูก. ตัวอย่างสุดท้ายทำในสิ่งที่คำถามถามแม้ว่าคำตอบที่ยอมรับจะสะอาดกว่ามาก เกี่ยวกับการsortแล้วuniq, sortที่ต้องทำก่อนที่จะทำuniqอย่างอื่นมันไม่ทำงาน ( แต่คุณสามารถข้ามคำสั่งที่สองและการใช้งานเพียงsort -u) จากuniq(1): "กรองบรรทัดการจับคู่ที่อยู่ติดกันจาก INPUT (หรืออินพุตมาตรฐาน) เขียนไปยัง OUTPUT (หรือเอาต์พุตมาตรฐาน)
Mikael S

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