Elimina tutti i duplicati consecutivi


13

Ho un file che assomiglia a questo.

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

Vorrei che fosse così:

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

Sono sicuro che ci deve essere un modo in cui Vim potrebbe farlo rapidamente, ma non riesco a capire come. È oltre il potere delle macro e ha bisogno di vimscript?

Inoltre, va bene se devo applicare la stessa macro a ciascun blocco di "Hold". Non deve essere una singola macro che ottiene l'intero file, anche se sarebbe fantastico.

Risposte:


13

Penso che il seguente comando dovrebbe funzionare:

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

Spiegazione :

Usiamo il comando di sostituzione sull'intero file per cambiare patternin string:

:%s/pattern/string/

Qui patternè ^\(.*\)\(\n\1\)\+$ed stringè \1.

pattern può essere suddiviso in questo modo:

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

^e $abbinare rispettivamente un inizio di riga e una fine di riga.

\(e \)vengono utilizzati per racchiudere in subpattern1modo che possiamo fare riferimento ad esso in seguito dal numero speciale \1.
Sono anche usati per racchiudere in subpattern2modo che possiamo ripeterlo 1 o più volte con il quantificatore \+.

subpattern1is .*
.è un metacarattere che corrisponde a qualsiasi carattere tranne la nuova riga ed *è un quantificatore che corrisponde all'ultimo carattere 0, 1 o più volte.
Quindi .*corrisponde a qualsiasi testo che non contiene alcuna nuova riga.

subpattern2è \n\1
\ncorrisponde una nuova riga e \1corrisponde lo stesso testo che è stato abbinato all'interno della prima \(, \)che qui è subpattern1.

Quindi patternpuò essere letto in questo modo:
un inizio di riga ( ^) seguito da qualsiasi testo che non contiene alcuna nuova riga ( .*) seguito da una nuova riga ( \n) quindi dallo stesso testo ( \1), gli ultimi due che si ripetono una o più volte ( \+), e finalmente un fine riga ( $) .

Ovunque patternsia abbinato (un blocco di linee identiche), il comando di sostituzione lo sostituisce con quello stringqui \1(la prima riga del blocco).

Se vuoi vedere quali blocchi di linee saranno interessati senza cambiare nulla nel tuo file, puoi abilitare l' hlsearchopzione e aggiungere il nflag di sostituzione alla fine del comando:

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

Per un controllo più granulare, puoi anche chiedere una conferma prima di modificare ogni blocco di linee aggiungendo cinvece il flag di sostituzione:

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

Per ulteriori informazioni sul comando di lettura di sostituzione :help :s,
per le bandiere di sostituzione :help s_flags,
per i vari metacaratteri e quantificatori leggere :help pattern-atoms,
e per le espressioni regolari in vim leggono questo .

Modifica: il carattere jolly ha risolto un problema nel comando aggiungendo un $alla fine di pattern.

Anche BloodGain ha una versione più breve e più leggibile dello stesso comando.


1
Bello; il tuo comando ha bisogno di un $in esso, però. Altrimenti farà cose inaspettate con una riga che inizia con un testo identico alla riga precedente, ma ha alcuni altri caratteri finali. Nota anche che il comando di base che hai dato è funzionalmente equivalente alla mia risposta :%!uniq, ma i flag di evidenziazione e di conferma sono buoni.
Wildcard il

Hai ragione, ho appena controllato e se una delle righe duplicate contiene un diverso carattere finale, il comando non si comporta come previsto. Non so come risolverlo, l'atomo \ncorrisponde a un fine linea e dovrebbe impedirlo, ma non è così. Ho provato ad aggiungere un $poco dopo .*senza successo. Proverò a risolverlo, ma se non ci riesco, forse eliminerò la mia risposta o aggiungerò un avviso alla fine. Grazie per aver segnalato questo problema.
saginaw,

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

1
Dovresti considerare che $corrisponde alla fine della stringa , non alla fine della riga. Questo non è tecnicamente vero, ma quando si inseriscono i personaggi dopo diverse eccezioni, corrisponde a un valore letterale $anziché speciale. Quindi usare \nè meglio per le partite multilinea. (Vedi :help /$)
Wildcard il

Penso che tu abbia ragione, che \npuò essere usato ovunque all'interno della regex mentre $probabilmente dovrebbe essere usato solo alla fine. Solo per fare la differenza tra i due, ho modificato la risposta scrivendo che \ncorrisponde a una nuova riga (che istintivamente ti fa pensare che ci sia ancora del testo dopo) mentre $corrisponde a una fine della riga (che ti fa pensare che non c'è nulla sinistra).
saginaw,

10

Prova quanto segue:

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

Come per la risposta di saginaw , questo usa Vim: comando sostitutivo. Tuttavia, sfrutta un paio di funzionalità extra per migliorare la leggibilità:

  1. Vim ci consente di utilizzare qualsiasi carattere ASCII non alfanumerico tranne la barra rovesciata ( \ ), la doppia virgoletta ( " ) o la pipe ( | ) per dividere il testo di corrispondenza / sostituzione / flag. Qui, ho selezionato il punto e virgola ( ; ), ma puoi scegli un altro.
  2. Vim fornisce impostazioni "magiche" per le espressioni regolari, in modo che i personaggi vengano interpretati per i loro significati speciali invece di richiedere una scappatoia. Questo è utile per ridurre la verbosità e perché è più coerente del default "nomagico". Iniziare con \v"molto magico" significa che tutti i caratteri tranne quelli alfanumerici ( A-z0-9 ) e il trattino basso ( _ ) hanno un significato speciale.

Il significato dei componenti sono:

% per l'intero file

s sostituto

; inizia la stringa sostitutiva

\ v "molto magico"

^ inizio della riga

(. *) 0 o più di qualsiasi carattere (gruppo 1)

(\ n \ 1) + newline seguito da (testo di corrispondenza gruppo 1), 1 o più volte (gruppo 2)

$ end of line (o in questo caso, pensa che il prossimo personaggio debba essere una nuova riga )

; inizia a sostituire la stringa

\ 1 gruppo 1 corrisponde al testo

; fine comando o inizio flag


1
Mi piace molto la tua risposta, perché è più leggibile ma anche perché mi ha fatto capire meglio la differenza tra \ne $. \naggiunge qualcosa al modello: la nuova riga di carattere che dice a Vim che il testo seguente è su una nuova riga. Considerando $che non aggiunge nulla al modello, proibisce semplicemente di fare una corrispondenza se il personaggio successivo al di fuori del modello non è una nuova linea. Almeno, è quello che ho capito leggendo la tua risposta e :help zero-width.
saginaw,

E lo stesso deve essere vero per ^, non aggiunge nulla al modello, impedisce solo di fare una corrispondenza se il carattere precedente al di fuori del modello non è una nuova riga ...
saginaw

@saginaw Hai ragione, e questa è una buona spiegazione. Nelle espressioni regolari, alcuni personaggi possono essere considerati come personaggi di controllo . Ad esempio, +significa "ripetere l'espressione precedente (carattere o gruppo) 1 o più volte", ma non corrisponde a nulla in sé. Il ^mezzo "non può iniziare nel mezzo della stringa" e $significa "non può finire nel mezzo della stringa". Notate che non ho detto "line", ma "string" lì. Vim tratta ogni riga come una stringa di default - ed è qui che \nentra in gioco. Dice a Vim di consumare una nuova riga per provare a fare questa partita.
Bloodgain,

8

Se vuoi rimuovere TUTTE le linee identiche adiacenti, non solo Hold, puoi farlo estremamente facilmente con un filtro esterno dall'interno vim:

:%!uniq (in un ambiente Unix).

Se vuoi farlo direttamente vim, in realtà è molto complicato. Penso che ci sia un modo, ma per il caso generale è molto complicato renderlo funzionale al 100% e non ho ancora risolto tutti i bug.

Tuttavia, per questo caso specifico , poiché puoi vedere visivamente che la riga successiva non duplicata non inizia con lo stesso carattere, puoi usare:

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

La +intende la linea dopo la riga corrente. Il . si riferisce alla riga corrente. La /^[^H]/-significa che la linea prima ( -) la prossima linea che non inizia con H.

Quindi d viene eliminato.


3
Mentre i comandi sostitutivi e globali di Vim sono buoni esercizi, chiamare uniq(dall'interno di Vim o usando la shell) è come risolverei questo. Per prima cosa, sono abbastanza sicuro uniqche gestirà le linee che sono vuote / tutti gli spazi come equivalenti (non lo hanno testato), ma sarebbe molto più difficile da catturare con una regex. Significa anche non "reinventare la ruota" mentre sto cercando di fare il lavoro.
Bloodgain,

2
La capacità di alimentare il testo tramite strumenti esterni è il motivo per cui di solito consiglio Vim e Cygwin su Windows. Vim e Shell semplicemente appartengono insieme.
DevSolar

2

Una risposta basata su Vim:

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

= Sostituisci ogni riga seguita da sola almeno una volta , con quella stessa riga.


2

Ancora uno, supponendo Vim 7.4.218 o successivo:

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>)

Questo non è necessariamente migliore rispetto alle altre soluzioni, però.


2

Ecco una soluzione basata su un vecchio (2003) vim (golf) di Preben Gulberg e Piet Delport.

  • Le sue radici stanno dentro %g/^\v(.*)\n\1$/d
  • A differenza delle altre soluzioni, è stato incapsulato in una funzione, quindi non modifica il registro di ricerca, né il registro senza nome.
  • Ed è stato anche incapsulato in un comando per semplificarne l'utilizzo:
    • :Uniq(equivalente a :%Uniq),
    • :1,Uniq (dall'inizio del buffer alla riga corrente),
    • seleziona visivamente le linee + premi :Uniq<cr>(espanso da vim in :'<,'>Uniq)
    • ecc ( :h range)

Ecco il codice:

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

Nota: i loro primi tentativi furono:

" 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'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.