จะลบบรรทัดที่ซ้ำกันในไฟล์โดยไม่เรียงลำดับใน Unix ได้อย่างไร?


148

มีวิธีลบบรรทัดที่ซ้ำกันในไฟล์ใน Unix หรือไม่?

ฉันสามารถทำได้ด้วยsort -uและuniqคำสั่ง แต่ฉันต้องการใช้sedหรือawk. เป็นไปได้หรือไม่


12
หากคุณหมายถึงรายการซ้ำติดต่อกันuniqเพียงอย่างเดียวก็เพียงพอแล้ว
Michael Krelin - แฮ็กเกอร์

และอย่างอื่นฉันเชื่อว่ามันเป็นไปได้awkแต่จะค่อนข้างใช้ทรัพยากรในไฟล์ขนาดใหญ่
Michael Krelin - แฮ็กเกอร์

รายการที่ซ้ำกันstackoverflow.com/q/24324350และstackoverflow.com/q/11532157มีคำตอบที่น่าสนใจซึ่งควรย้ายมาที่นี่
tripleee

คำตอบ:


302
awk '!seen[$0]++' file.txt

seenเป็น Associative-array ที่ Awk จะส่งผ่านทุกบรรทัดของไฟล์ไป หากบรรทัดไม่อยู่ในอาร์เรย์seen[$0]จะประเมินเป็นเท็จ !เป็นผู้ประกอบการไม่เชิงตรรกะและจะกลับเท็จจริง Awk จะพิมพ์บรรทัดที่นิพจน์ประเมินว่าเป็นจริง การ++เพิ่มขึ้นseenเพื่อให้seen[$0] == 1หลังจากครั้งแรกที่พบบรรทัดแล้วseen[$0] == 2และอื่น ๆ
Awk ประเมินทุกอย่างยกเว้น0และ""(สตริงว่าง) เป็นจริง ถ้าเป็นเส้นที่ซ้ำกันจะอยู่ในseenนั้น!seen[$0]จะมีการประเมินเป็นเท็จและสายจะไม่ถูกเขียนขึ้นเพื่อการส่งออก


5
หากต้องการบันทึกเป็นไฟล์เราสามารถทำได้awk '!seen[$0]++' merge_all.txt > output.txt
Akash Kandpal

5
ข้อแม้ที่สำคัญที่นี่: หากคุณต้องการทำเช่นนี้กับไฟล์หลายไฟล์และคุณจัดการกับไฟล์เพิ่มเติมในตอนท้ายของคำสั่งหรือใช้สัญลักษณ์แทน ... อาร์เรย์ 'เห็น' จะเติมบรรทัดที่ซ้ำกันจากทุกไฟล์ หากคุณต้องการจัดการไฟล์แต่ละไฟล์อย่างเป็นอิสระคุณจะต้องทำสิ่งต่างๆเช่นfor f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Nick K9

@ NickK9 การลดขนาดไฟล์หลายไฟล์นั้นยอดเยี่ยมในตัวมันเอง เคล็ดลับที่ดี
sfscs

32

จากhttp://sed.sourceforge.net/sed1line.txt : (โปรดอย่าถามฉันว่ามันทำงานอย่างไร ;-))

 # delete duplicate, consecutive lines from a file (emulates "uniq").
 # First line in a set of duplicate lines is kept, rest are deleted.
 sed '$!N; /^\(.*\)\n\1$/!P; D'

 # delete duplicate, nonconsecutive lines from a file. Beware not to
 # overflow the buffer size of the hold space, or else use GNU sed.
 sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'

geekery ;-) +1 แต่การใช้ทรัพยากรเป็นสิ่งที่หลีกเลี่ยงไม่ได้
Michael Krelin - แฮ็กเกอร์

3
'$! น; /^(.*)\n\1$/!P; D 'หมายถึง "ถ้าคุณไม่ได้อยู่ที่บรรทัดสุดท้ายให้อ่านในบรรทัดอื่นตอนนี้ดูสิ่งที่คุณมีและถ้าไม่ใช่สิ่งที่ตามด้วยขึ้นบรรทัดใหม่แล้วพิมพ์สิ่งเดิมอีกครั้งให้พิมพ์สิ่งนั้นออกตอนนี้ลบ สิ่งต่างๆ (ขึ้นบรรทัดใหม่) "
เบต้า

2
'G; s / \ n / && /; / ^ ([- ~] * \ n). * \ n \ 1 / d; ส / \ n //; ซ; P 'หมายถึงประมาณว่า "ต่อท้ายช่องว่างทั้งหมดของบรรทัดนี้จากนั้นหากคุณเห็นเส้นที่ซ้ำกันให้โยนสิ่งทั้งหมดออกไปมิฉะนั้นให้คัดลอกความยุ่งเหยิงทั้งหมดกลับไปที่ช่องว่างและพิมพ์ส่วนแรก (ซึ่งเป็นบรรทัดที่คุณเพิ่ง อ่าน. "
Beta

เป็น$!ส่วนที่จำเป็น? ไม่sed 'N; /^\(.*\)\n\1$/!P; D'ทำสิ่งเดียวกัน? ฉันไม่สามารถหาตัวอย่างที่ทั้งสองแตกต่างกันในเครื่องของฉันได้ (fwiw ฉันลองใช้บรรทัดว่างในตอนท้ายของทั้งสองเวอร์ชันและใช้ได้ทั้งคู่)
eddi

1
เกือบ 7 ปีต่อมาไม่มีใครตอบ @amichair ... <sniff> ทำให้ฉันเสียใจ ;) อย่างไรก็ตาม[ -~]หมายถึงช่วงของอักขระ ASCII ตั้งแต่ 0x20 (ช่องว่าง) ถึง 0x7E (ทิลเดอ) สิ่งเหล่านี้ถือเป็นอักขระ ASCII ที่พิมพ์ได้ (หน้าที่เชื่อมโยงยังมี 0x7F / ลบ แต่ดูเหมือนจะไม่ถูกต้อง) นั่นทำให้โซลูชันเสียสำหรับทุกคนที่ไม่ได้ใช้ ASCII หรือใครก็ตามที่ใช้พูดว่าอักขระแท็บ .. ยิ่งพกพาได้[^\n]ยิ่งมีอักขระมากขึ้น ... ทั้งหมดของพวกเขายกเว้นอันที่จริง
B Layer

15

Perl one-liner คล้ายกับโซลูชัน awk ของ @ jonas:

perl -ne 'print if ! $x{$_}++' file

รูปแบบนี้ลบช่องว่างต่อท้ายก่อนเปรียบเทียบ:

perl -lne 's/\s*$//; print if ! $x{$_}++' file

รูปแบบนี้แก้ไขไฟล์ในตำแหน่ง:

perl -i -ne 'print if ! $x{$_}++' file

รูปแบบนี้แก้ไขไฟล์ในตำแหน่งและทำการสำรองข้อมูล file.bak

perl -i.bak -ne 'print if ! $x{$_}++' file

7

อีกวิธีหนึ่งในการใช้ Vim (เข้ากันได้กับ Vi) :

ลบบรรทัดที่ซ้ำกันและต่อเนื่องกันออกจากไฟล์:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

ลบบรรทัดที่ซ้ำกันไม่ต่อเนื่องและไม่ว่างเปล่าออกจากไฟล์:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq


6

ซับเดียวที่ Andre Miller โพสต์ไว้ข้างต้นใช้งานได้ยกเว้น sed เวอร์ชันล่าสุดเมื่อไฟล์อินพุตลงท้ายด้วยบรรทัดว่างและไม่มีตัวอักษร บน Mac ของฉัน CPU ของฉันหมุน

ลูปไม่มีที่สิ้นสุดหากบรรทัดสุดท้ายว่างเปล่าและไม่มีตัวอักษร :

sed '$!N; /^\(.*\)\n\1$/!P; D'

ไม่ค้าง แต่คุณจะสูญเสียบรรทัดสุดท้าย

sed '$d;N; /^\(.*\)\n\1$/!P; D'

คำอธิบายอยู่ท้ายสุดของคำถามที่พบบ่อย sed :

ผู้ดูแล GNU sed รู้สึกว่าแม้จะมีปัญหาในการพกพา
แต่การเปลี่ยนคำสั่ง N เพื่อพิมพ์ (แทนที่จะ
ลบ) พื้นที่รูปแบบนั้นสอดคล้องกับสัญชาตญาณของคน ๆ หนึ่ง
เกี่ยวกับวิธีการที่คำสั่ง "ต่อท้ายบรรทัดถัดไป" ควรจะทำงาน
ข้อเท็จจริงอีกประการหนึ่งที่สนับสนุนการเปลี่ยนแปลงคือ "{N; command;}" จะ
ลบบรรทัดสุดท้ายหากไฟล์มีจำนวนบรรทัดคี่ แต่
พิมพ์บรรทัดสุดท้ายหากไฟล์มีจำนวนบรรทัดคู่

ในการแปลงสคริปต์ที่ใช้พฤติกรรมเดิมของ N (การลบ
พื้นที่รูปแบบเมื่อถึง EOF) เป็นสคริปต์ที่เข้ากันได้กับ
sed ทุกเวอร์ชันให้เปลี่ยน "N;" ถึง "$ d; N;" .


4

โซลูชันแรกมาจากhttp://sed.sourceforge.net/sed1line.txt

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5

แนวคิดหลักคือ:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

อธิบาย:

  1. $!N;ถ้าบรรทัดปัจจุบันไม่ได้เป็นบรรทัดสุดท้ายให้ใช้คำสั่งในการอ่านบรรทัดถัดลงNpattern space
  2. /^(.*)\n\1$/!P: หากเนื้อหาของกระแสpattern spaceสองรายการduplicate stringคั่นด้วย\nซึ่งหมายความว่าบรรทัดถัดไปคือsameกับบรรทัดปัจจุบันเราจะไม่สามารถพิมพ์ตามแนวคิดหลักของเราได้ มิฉะนั้นซึ่งหมายความว่าบรรทัดปัจจุบันคือลักษณะสุดท้ายของบรรทัดต่อเนื่องที่ซ้ำกันทั้งหมดตอนนี้เราสามารถใช้Pคำสั่งเพื่อพิมพ์ตัวอักษรในpattern spaceutil ปัจจุบัน\n( \nพิมพ์ด้วย)
  3. D: เราใช้Dคำสั่งเพื่อลบตัวอักษรในpattern spaceutil ปัจจุบัน\n( \nลบด้วย) จากนั้นเนื้อหาของpattern spaceคือบรรทัดถัดไป
  4. และDคำสั่งจะบังคับsedให้ข้ามไปที่FIRSTคำสั่ง$!Nแต่ไม่อ่านบรรทัดถัดไปจากไฟล์หรือสตรีมอินพุตมาตรฐาน

วิธีที่สองเข้าใจง่าย (จากตัวเอง):

$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5

แนวคิดหลักคือ:

print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.

อธิบาย:

  1. อ่านบรรทัดใหม่จากอินพุตสตรีมหรือไฟล์และพิมพ์ครั้งเดียว
  2. ใช้:loopคำสั่งตั้งlabelชื่อloop.
  3. ใช้Nเพื่ออ่านบรรทัดถัดไปในไฟล์pattern space.
  4. ใช้s/^(.*)\n\1$/\1/เพื่อลบบรรทัดปัจจุบันหากบรรทัดถัดไปเหมือนกันกับบรรทัดปัจจุบันเราใช้sคำสั่งเพื่อdeleteดำเนินการ
  5. หากดำเนินการsคำสั่งสำเร็จแล้วให้ใช้tloopcommand force sedเพื่อข้ามไปยังlabelชื่อloopซึ่งจะทำลูปเดียวกันไปยังบรรทัดถัดไปโดยไม่มีบรรทัดต่อเนื่องที่ซ้ำกันของบรรทัดซึ่งคือlatest printed; มิฉะนั้นให้ใช้Dคำสั่งไปdeleteยังบรรทัดซึ่งเหมือนกันกับlatest-printed lineและบังคับsedให้ข้ามไปที่คำสั่งแรกซึ่งเป็นpคำสั่งเนื้อหาของปัจจุบันpattern spaceคือบรรทัดใหม่ถัดไป

คำสั่งเดียวกันบน Windows กับ busybox:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
scavenger

1

สามารถทำได้โดยใช้awk
Below Line จะแสดงค่าที่ไม่ซ้ำกัน

awk file_name | uniq

คุณสามารถส่งออกค่าเฉพาะเหล่านี้ไปยังไฟล์ใหม่

awk file_name | uniq > uniq_file_name

ไฟล์ใหม่ uniq_file_name จะมีเฉพาะค่าที่ไม่ซ้ำกันไม่มีรายการที่ซ้ำกัน


1

uniq จะถูกหลอกด้วยการเว้นวรรคและแท็บต่อท้าย เพื่อเลียนแบบการเปรียบเทียบของมนุษย์ฉันกำลังตัดแต่งช่องว่างและแท็บต่อท้ายทั้งหมดก่อนทำการเปรียบเทียบ

ฉันคิดว่า $! N; ต้องการการจัดฟันแบบหยิกมิฉะนั้นจะดำเนินต่อไปและนั่นคือสาเหตุของการวนซ้ำที่ไม่สิ้นสุด

ฉันมี bash 5.0 และ sed 4.7 ใน Ubuntu 20.10 ซับในตัวที่สองใช้งานไม่ได้ในการจับคู่ชุดอักขระ

รูปแบบสามรูปแบบอันดับแรกกำจัดบรรทัดซ้ำที่อยู่ติดกันอันดับที่สองเพื่อกำจัดบรรทัดซ้ำทุกที่ที่เกิดขึ้นสามเพื่อกำจัดอินสแตนซ์ของบรรทัดสุดท้ายทั้งหมดในไฟล์

Pastebin

# First line in a set of duplicate lines is kept, rest are deleted.
# Emulate human eyes on trailing spaces and tabs by trimming those.
# Use after norepeat() to dedupe blank lines.

dedupe() {
 sed -E '
  $!{
   N;
   s/[ \t]+$//;
   /^(.*)\n\1$/!P;
   D;
  }
 ';
}

# Delete duplicate, nonconsecutive lines from a file. Ignore blank
# lines. Trailing spaces and tabs are trimmed to humanize comparisons
# squeeze blank lines to one

norepeat() {
 sed -n -E '
  s/[ \t]+$//;
  G;
  /^(\n){2,}/d;
  /^([^\n]+).*\n\1(\n|$)/d;
  h;
  P;
  ';
}

lastrepeat() {
 sed -n -E '
  s/[ \t]+$//;
  /^$/{
   H;
   d;
  };
  G;
  # delete previous repeated line if found
  s/^([^\n]+)(.*)(\n\1(\n.*|$))/\1\2\4/;
  # after searching for previous repeat, move tested last line to end
  s/^([^\n]+)(\n)(.*)/\3\2\1/;
  $!{
   h;
   d;
  };
  # squeeze blank lines to one
  s/(\n){3,}/\n\n/g;
  s/^\n//;
  p;
 ';
}

-4
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

ลบบรรทัดที่ซ้ำกันโดยใช้ awk


1
สิ่งนี้จะรบกวนลำดับของบรรทัด
Vijay

1
ไฟล์ข้อความประมาณ 20 GB คืออะไร? ช้าเกินไป.
Alexander Lubyagin

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