ลบรายการที่ซ้ำกันทั้งหมด


13

ฉันมีไฟล์ที่มีลักษณะเช่นนี้

Move to 230.00
Hold
Hold
Hold
Hold
Hold
Hold
Move to 00.00
Hold 
Hold 
Hold 
Hold 
Hold 
FooBar
Hold 
Spam
Hold

ฉันอยากให้มันเป็นแบบนี้:

Move to 230.00
Hold
Move to 00.00
Hold 
FooBar
Hold
Spam
Hold

ฉันแน่ใจว่าจะต้องมีวิธีที่เป็นกลุ่มได้อย่างรวดเร็วสามารถทำเช่นนี้ แต่ฉันไม่สามารถห่อหัวของฉันได้อย่างไร สิ่งนี้เกินความสามารถของมาโครและต้องการ vimscript ไหม?

นอกจากนี้ยังตกลงถ้าฉันต้องใช้แมโครเดียวกันกับแต่ละบล็อกของ "การพัก" ไม่จำเป็นต้องเป็นแมโครเดียวที่ได้รับทั้งไฟล์แม้ว่ามันจะยอดเยี่ยม

คำตอบ:


13

ฉันคิดว่าคำสั่งต่อไปนี้จะทำงาน:

 :%s/^\(.*\)\(\n\1\)\+$/\1/

คำอธิบาย:

เราใช้คำสั่งการแทนที่ในไฟล์ทั้งหมดเพื่อเปลี่ยนpatternเป็นstring:

:%s/pattern/string/

นี่patternเป็น^\(.*\)\(\n\1\)\+$และเป็นstring\1

pattern สามารถถูกทำลายลงเช่นนี้

^\(subpattern1\)\(subpattern2\)\+$

^และ$จับคู่จุดเริ่มต้นของบรรทัดและจุดสิ้นสุดตามลำดับ

\(และ\)จะใช้ในการปิดล้อมเพื่อให้เราสามารถดูได้ในภายหลังจากจำนวนพิเศษsubpattern1 พวกเขายังใช้เพื่อใส่เพื่อให้เราสามารถทำซ้ำได้ 1 ครั้งหรือมากกว่าที่มีปริมาณ\1
subpattern2\+

subpattern1เป็น.*
.ตัวเปรียบเทียบที่จับคู่อักขระใด ๆ ยกเว้นบรรทัดใหม่และ*เป็นตัวระบุปริมาณที่ตรงกับอักขระตัวสุดท้าย 0, 1 หรือมากกว่านั้น
ดังนั้น.*ตรงกับข้อความใด ๆ ที่ไม่มีบรรทัดใหม่

subpattern2คือ\n\1
\nตรงบรรทัดใหม่และ\1ตรงกับข้อความเดียวกับที่ถูกจับคู่ภายในครั้งแรก\(, ที่นี่คือ\)subpattern1

ดังนั้นpatternสามารถอ่านได้เช่นนี้:
จุดเริ่มต้นของบรรทัด ( ^) ตามด้วยข้อความใด ๆ ที่ไม่มีบรรทัดใหม่ ( .*) ตามด้วยบรรทัดใหม่ ( \n) จากนั้นข้อความเดียวกัน ( \1) สองข้อความหลังถูกทำซ้ำอย่างน้อยหนึ่งครั้ง ( \+) และ ในที่สุดก็สิ้นสุดของบรรทัด (ให้$ )

เมื่อใดก็ตามที่patternจับคู่ (บล็อกของบรรทัดที่เหมือนกัน) คำสั่งการแทนที่จะแทนที่ด้วยstringที่นี่คือ\1(บรรทัดแรกของบล็อก)

หากคุณต้องการดูว่าบล็อกบรรทัดใดจะได้รับผลกระทบโดยไม่ต้องเปลี่ยนแปลงอะไรในไฟล์ของคุณคุณสามารถเปิดใช้งานhlsearchตัวเลือกและเพิ่มการnตั้งค่าสถานะการแทนที่ที่ส่วนท้ายของคำสั่ง:

:%s/^\(.*\)\(\n\1\)\+$/\1/n

สำหรับการควบคุมแบบละเอียดมากขึ้นคุณสามารถขอการยืนยันก่อนที่จะเปลี่ยนแต่ละบรรทัดของบล็อกโดยการเพิ่มการcตั้งค่าสถานะการแทนที่แทน:

:%s/^\(.*\)\(\n\1\)\+$/\1/c

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำสั่งเปลี่ยนตัวอ่าน:help :s,
ธงทดแทน:help s_flags,
สำหรับ metacharacters ต่างๆและปริมาณอ่าน:help pattern-atoms,
และการแสดงผลปกติในกลุ่มอ่านนี้

แก้ไข: Wildcardคงมีปัญหาในคำสั่งโดยการเพิ่มในตอนท้ายของ$pattern

นอกจากนี้BloodGainยังมีคำสั่งเดียวกันที่สั้นกว่าและสามารถอ่านได้มากขึ้น


1
นีซ; คำสั่งของคุณจำเป็นต้องมี$ในนั้น มิฉะนั้นจะทำสิ่งที่ไม่คาดคิดกับบรรทัดที่เริ่มต้นด้วยข้อความที่เหมือนกันในบรรทัดก่อนหน้า แต่มีอักขระต่อท้ายอื่น ๆ โปรดทราบว่าคำสั่งพื้นฐานที่คุณให้นั้นมีฟังก์ชั่นเทียบเท่ากับคำตอบของฉัน:%!uniqแต่ไฮไลต์และการตั้งค่าสถานะการยืนยันนั้นดี
Wildcard

คุณถูกต้องฉันเพิ่งตรวจสอบและหากหนึ่งในบรรทัดที่ซ้ำกันมีอักขระต่อท้ายที่แตกต่างกันคำสั่งจะไม่ทำงานอย่างที่คาดไว้ ฉันไม่ทราบวิธีการแก้ไขอะตอม\nตรงกับจุดสิ้นสุดของบรรทัดและควรป้องกันสิ่งนี้ แต่ไม่ได้ ฉันพยายามเพิ่มสัก$ครู่.*โดยไม่ประสบความสำเร็จ ฉันจะพยายามแก้ไข แต่ถ้าฉันทำไม่ได้ฉันอาจลบคำตอบหรือเพิ่มคำเตือนในตอนท้าย ขอบคุณสำหรับการชี้ปัญหานี้
saginaw

1
ลอง:%s/^\(.*\)\(\n\1\)\+$/\1/
ไวลด์การ์ด

1
คุณควรพิจารณาว่า$การแข่งขันสิ้นสุดของสตริง , ไม่สิ้นสุดของเส้น นี่เป็นเทคนิคที่ไม่เป็นความจริง - แต่เมื่อคุณใส่ตัวละครหลังจากนั้นนอกเหนือไปจากข้อยกเว้นบางประการมันจะจับคู่ตัวอักษร$แทนที่จะเป็นอะไรพิเศษ ดังนั้นการใช้\nดีกว่าสำหรับการจับคู่หลายบรรทัด (ดู:help /$)
สัญลักษณ์แทน

ฉันคิดว่าคุณถูกต้องที่\nสามารถใช้งานได้ทุกที่ภายใน regex ในขณะที่$ควรจะใช้เฉพาะในตอนท้าย เพียงเพื่อสร้างความแตกต่างระหว่างทั้งสองฉันได้แก้ไขคำตอบโดยการเขียนที่\nตรงกับบรรทัดใหม่ (ซึ่งทำให้คุณคิดว่ายังมีข้อความหลังจาก) ในขณะที่$ตรงกับจุดสิ้นสุดของบรรทัด (ซึ่งทำให้คุณคิดว่าไม่มีอะไร ซ้าย).
saginaw

10

ลองทำสิ่งต่อไปนี้:

:%s;\v^(.*)(\n\1)+$;\1;

เช่นเดียวกับคำตอบของ saginawสิ่งนี้ใช้คำสั่ง Vim's: replace อย่างไรก็ตามมันใช้ประโยชน์จากคุณสมบัติพิเศษสองอย่างเพื่อปรับปรุงความสามารถในการอ่าน:

  1. Vim ให้เราใช้อักขระ ASCII ที่ไม่ใช่ตัวอักษรและตัวเลขยกเว้น backslash ( \ ), เครื่องหมายคำพูดคู่ ( " ) หรือไพพ์ ( | ) เพื่อแบ่งข้อความการจับคู่ / แทนที่ / แฟล็กของเราที่นี่ฉันเลือกเซมิโคลอน ( ; ) แต่คุณสามารถ เลือกอันอื่น.
  2. Vim จัดให้มีการตั้งค่า "เวทย์มนตร์" สำหรับนิพจน์ทั่วไปเพื่อให้ตีความตัวละครสำหรับความหมายพิเศษแทนการใช้เครื่องหมายทับขวา สิ่งนี้มีประโยชน์ในการลดความฟุ่มเฟื่อยและเนื่องจากมันมีความสอดคล้องมากกว่าค่าเริ่มต้น "nomagic" เริ่มต้นด้วยความ\vหมายว่า "very magic" หรือตัวละครทั้งหมดยกเว้นตัวอักษรและตัวเลข ( A-z0-9 ) และขีดล่าง ( _ ) มีความหมายพิเศษ

ความหมายของส่วนประกอบคือ:

% สำหรับไฟล์ทั้งหมด

s แทน

; เริ่มต้นสตริงแทนที่

\ v "วิเศษมาก"

^ จุดเริ่มต้นของบรรทัด

(*). 0 หรือมากกว่าของใด ๆตัวอักษร (กลุ่มที่ 1)

(\ n \ 1) + บรรทัดใหม่ตามด้วย (ข้อความการจับคู่กลุ่ม 1) 1 ครั้งขึ้นไป (กลุ่ม 2)

$ จุดสิ้นสุดของบรรทัด (หรือในกรณีนี้คิดว่าตัวละครตัวถัดไปต้องเป็นบรรทัดใหม่ )

; เริ่มแทนที่สตริง

\ 1 กลุ่มที่ 1 จับคู่ข้อความ

; สิ้นสุดคำสั่งหรือเริ่มแฟล็ก


1
ผมชอบคำตอบของคุณเพราะมันอ่านได้มากขึ้น แต่ยังเพราะมันทำให้ผมเข้าใจความแตกต่างระหว่างและ\n เพิ่มบางอย่างในรูปแบบ: อักขระขึ้นบรรทัดใหม่ที่บอกเป็นกลุ่มว่าข้อความต่อไปนี้อยู่ในบรรทัดใหม่ ในขณะที่ไม่ได้เพิ่มอะไรลงในรูปแบบมันก็ห้ามการแข่งขันที่จะทำถ้าตัวละครต่อไปนอกรูปแบบที่ไม่ได้ขึ้นบรรทัดใหม่ อย่างน้อยก็เป็นสิ่งที่ฉันได้เข้าใจโดยการอ่านคำตอบของคุณและ $\n$:help zero-width
saginaw

และสิ่งเดียวกันจะต้องเป็นจริงสำหรับ^มันไม่ได้เพิ่มอะไรลงในรูปแบบมันเพียงป้องกันการจับคู่ที่จะทำถ้าตัวละครก่อนหน้านอกรูปแบบไม่ใช่บรรทัดใหม่ ...
saginaw

@ saginaw คุณพูดถูกและนั่นเป็นคำอธิบายที่ดี ในนิพจน์ทั่วไปอักขระบางตัวอาจเป็นอักขระควบคุมได้ ตัวอย่างเช่น+หมายถึง "ทำซ้ำนิพจน์ก่อนหน้า (อักขระหรือกลุ่ม) 1 ครั้งขึ้นไป" แต่ไม่ตรงกับสิ่งใดเลย ^หมายถึง "ไม่สามารถเริ่มต้นในช่วงกลางของสตริง" และ$หมายถึง "ไม่สามารถจบในช่วงกลางของสตริง." สังเกตุฉันไม่ได้พูดว่า "line" แต่ "string" ตรงนั้น Vim ถือว่าแต่ละสตริงเป็นค่าเริ่มต้น - และนั่นคือที่\nมามันบอกให้ Vim ใช้บรรทัดใหม่เพื่อพยายามทำการจับคู่นี้
Bloodgain

8

หากคุณต้องการลบบรรทัดที่เหมือนกันทั้งหมดออกไม่ใช่เพียงแค่Holdคุณสามารถทำได้อย่างง่ายดายด้วยตัวกรองภายนอกจากภายในvim:

:%!uniq (ในสภาพแวดล้อม Unix)

ถ้าคุณต้องการที่จะทำมันโดยตรงvimมันเป็นเรื่องยากมาก ฉันคิดว่ามันมีวิธี แต่สำหรับกรณีทั่วไปมันเป็นเรื่องยากมากที่จะทำให้มันใช้งานได้ 100% และฉันยังไม่ได้จัดการข้อบกพร่องทั้งหมด

อย่างไรก็ตามสำหรับการนี้โดยเฉพาะกรณีเนื่องจากคุณสามารถสายตาเห็นว่าเส้นถัดไปที่ไม่ซ้ำกันไม่ได้เริ่มต้นด้วยตัวอักษรเดียวกันคุณสามารถใช้:

:+,./^[^H]/-d

+หมายถึงเส้นหลังเส้นปัจจุบัน การ อ้างถึงบรรทัดปัจจุบัน /^[^H]/-หมายถึงบรรทัดก่อน (คน-) บรรทัดถัดไปที่ไม่ได้เริ่มต้นด้วยเอช

จากนั้น d ถูกลบ


3
ในขณะที่คำสั่งแทนที่และคำสั่งโกลบอลเป็นแบบฝึกหัดที่ดีการโทรuniq(จากภายในเป็นกลุ่มหรือใช้เชลล์) เป็นวิธีที่ฉันจะแก้ปัญหานี้ สำหรับสิ่งหนึ่งฉันค่อนข้างแน่ใจว่าuniqจะจัดการกับบรรทัดที่ว่างเปล่า / ช่องว่างทั้งหมดเท่ากัน (ไม่ได้ทดสอบ) แต่มันจะยากกว่ามากในการจับภาพด้วย regex นอกจากนี้ยังหมายถึงไม่ "reinventing wheel" ในขณะที่ฉันพยายามทำงานให้เสร็จ
Bloodgain

2
ความสามารถในการป้อนข้อความผ่านเครื่องมือภายนอกคือสาเหตุที่ฉันมักจะแนะนำ Vim และ Cygwin บน Windows เป็นกลุ่มและเปลือกเพียงอยู่ด้วยกัน
DevSolar

2

คำตอบที่เป็นกลุ่ม:

:%s/\(^.*\n\)\1\{1,}/\1

= แทนที่ทุกบรรทัดตามด้วยตัวของมันเองอย่างน้อยหนึ่งครั้งด้วยบรรทัดเดียวกัน


2

อีกหนึ่งสมมติว่าเป็นกลุ่ม 7.4.218 หรือใหม่กว่า:

function! s:Uniq(line1, line2)
    let cursor = getcurpos()
    let lines = uniq(getline(a:line1, a:line2))
    if setline(a:line1, lines) == 0 && len(lines) <= a:line2 - a:line1
        silent execute (a:line1 + len(lines)) . ',' . a:line2 . 'd _'
    endif
    call setpos('.', cursor)
endfunction

command! -range=% Uniq call <SID>Uniq(<line1>, <line2>)

นี่ไม่ได้ดีไปกว่าโซลูชันอื่น ๆ เสมอไป


2

นี่คือวิธีการแก้ปัญหาตามเดิม (2003) เป็นกลุ่ม (กอล์ฟ)โดย Preben Gulberg และ Piet Delport

  • มันอยู่ในราก %g/^\v(.*)\n\1$/d
  • ไม่เหมือนกับโซลูชันอื่น ๆ มันถูกห่อหุ้มในฟังก์ชันดังนั้นจึงไม่แก้ไขการลงทะเบียนการค้นหาหรือการลงทะเบียนที่ไม่มีชื่อ
  • และมันยังถูกห่อหุ้มไว้ในคำสั่งเพื่อทำให้การใช้งานง่ายขึ้น:
    • :Uniq(เทียบเท่า:%Uniq)
    • :1,Uniq (ตั้งแต่เริ่มบัฟเฟอร์จนถึงบรรทัดปัจจุบัน)
    • มองเห็นเส้นที่เลือก + การตี:Uniq<cr>(ขยายเป็นกลุ่ม:'<,'>Uniq)
    • ฯลฯ ( :h range)

นี่คือรหัส:

command! -range=% -nargs=0 Uniq <line1>,<line2>call s:EmuleUniq()

function! s:EmuleUniq() range
  let l1 = a:firstline
  let l2 = a:lastline
  if l1 < l2
    " Note the "-" to avoid spilling over the end of the range
    " Note also the use of ":delete", along with the black hole register "_"
    silent exe l1.','l2.'-g/^\(.*\)\n\1$/d _'

    call histdel('search', -1)          " necessary
    " let @/ = histget('search', -1)    " useless within a function
  endif
endfunction

หมายเหตุ: ความพยายามครั้งแรกของพวกเขาคือ:

" Version1 from: Preben 'Peppe' Guldberg <peppe {at} xs4all {dot} nl>
" silent exe l1 . ',' . (l2 - 1) . 's/^\(.*\)\%(\n\%<' . (l2 + 1)
      " \ . 'l\1$\)\+/\1/e'

" Version from: Piet Delport <pjd {at} 303.za {dot} net>
" silent exe l1.','l2.'g/^\%<'.l2.'l\(.*\)\n\1$/d'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.