Supprimer tous les doublons consécutifs

13

J'ai un fichier qui ressemble à ceci.

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

Je voudrais qu'il ressemble à ceci:

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

Je suis sûr qu'il doit y avoir un moyen pour que vim puisse le faire rapidement, mais je n'arrive pas à comprendre comment. Est-ce au-delà du pouvoir des macros et a-t-il besoin de vimscript?

Aussi, c'est OK si je dois appliquer la même macro à chaque bloc de "Holds". Il n'est pas nécessaire que ce soit une seule macro qui récupère tout le fichier, bien que ce soit génial.

James
la source

Réponses:

13

Je pense que la commande suivante devrait fonctionner:

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

Explication:

Nous utilisons la commande de substitution sur l'ensemble du fichier pour changer patternen string:

:%s/pattern/string/

Ici patternest ^\(.*\)\(\n\1\)\+$et stringest \1.

pattern peut être décomposé comme ceci:

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

^et $correspondent respectivement à un début de ligne et à une fin de ligne.

\(et \)sont utilisés pour joindre subpattern1afin que nous puissions y faire référence plus tard par le numéro spécial \1.
Ils sont également utilisés pour entourer subpattern2afin que nous puissions le répéter 1 ou plusieurs fois avec le quantificateur \+.

subpattern1is .*
.est un métacaractère correspondant à n'importe quel caractère sauf la nouvelle ligne et *est un quantificateur qui correspond au dernier caractère 0, 1 ou plusieurs fois.
Correspond donc à .*tout texte ne contenant pas de nouvelle ligne.

subpattern2is \n\1
\ncorrespond à une nouvelle ligne et \1correspond au même texte qui a été trouvé à l'intérieur de la première \(, \)qui est ici subpattern1.

On patternpeut donc le lire ainsi:
un début de ligne ( ^) suivi de tout texte ne contenant pas de nouvelle ligne ( .*) suivi d'une nouvelle ligne ( \n) puis du même texte ( \1), les deux derniers étant répétés une ou plusieurs fois ( \+), et enfin une fin de ligne ( $) .

Partout où patternest mis en correspondance (un bloc de lignes identiques), la commande de substitution le remplace par stringlequel se trouve ici \1(la première ligne du bloc).

Si vous souhaitez voir quels blocs de lignes seront affectés sans rien changer dans votre fichier, vous pouvez activer l' hlsearchoption et ajouter l' nindicateur de substitution à la fin de la commande:

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

Pour un contrôle plus précis, vous pouvez également demander une confirmation avant de modifier chaque bloc de lignes en ajoutant à la cplace l'indicateur de substitution:

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

Pour plus d'informations sur la commande de substitution lue :help :s,
pour les indicateurs de substitution :help s_flags,
pour les différents métacaractères et quantificateurs lus :help pattern-atoms,
et pour les expressions régulières dans vim, lisez ceci .

Edit: Wildcard a corrigé un problème dans la commande en ajoutant un $à la fin de pattern.

Aussi BloodGain a une version plus courte et plus lisible de la même commande.

saginaw
la source
1
Agréable; votre commande en a cependant besoin $. Sinon, il fera des choses inattendues avec une ligne qui commence par un texte identique à la ligne précédente, mais qui a d'autres caractères de fin. Notez également que la commande de base que vous avez donnée est fonctionnellement équivalente à ma réponse :%!uniq, mais les indicateurs de surbrillance et de confirmation sont agréables.
Wildcard
Vous avez raison, je viens de vérifier et si l'une des lignes en double contient un caractère de fin différent, la commande ne se comporte pas comme prévu. Je ne sais pas comment le réparer, l'atome \ncorrespond à une fin de ligne et devrait empêcher cela, mais ce n'est pas le cas. J'ai essayé d'ajouter un $peu après .*sans succès. Je vais essayer de le réparer, mais si je ne peux pas, je vais peut-être supprimer ma réponse ou ajouter un avertissement à la fin. Merci d'avoir signalé ce problème.
saginaw
1
Essayez:%s/^\(.*\)\(\n\1\)\+$/\1/
Wildcard
1
Vous devez considérer que $correspond à la fin de la chaîne et non à la fin de la ligne. Ce n'est techniquement pas vrai, mais lorsque vous ajoutez des caractères après quelques exceptions, cela correspond à un littéral $au lieu de quelque chose de spécial. Il \nest donc préférable d' utiliser pour les correspondances sur plusieurs lignes. (Voir :help /$)
Wildcard
Je pense que vous avez raison, cela \npeut être utilisé n'importe où à l'intérieur de l'expression régulière alors qu'il $ne devrait probablement être utilisé qu'à la fin. Juste pour faire une différence entre les deux, j'ai édité la réponse en écrivant qui \ncorrespond à une nouvelle ligne (ce qui vous fait instinctivement penser qu'il y a encore du texte après) alors que $correspond à une fin de ligne (ce qui vous fait penser qu'il n'y a rien la gauche).
saginaw
10

Essayez ce qui suit:

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

Comme pour la réponse de saginaw , cela utilise la commande Vim: substitute. Cependant, il profite de quelques fonctionnalités supplémentaires pour améliorer la lisibilité:

  1. Vim nous permet d'utiliser n'importe quel caractère ASCII non alphanumérique à l'exception de la barre oblique inverse ( \ ), des guillemets doubles ( " ) ou du tuyau ( | ) pour diviser notre texte de correspondance / remplacement / drapeaux. Ici, j'ai sélectionné le point-virgule ( ; ), mais vous pouvez choisis un autre.
  2. Vim fournit des paramètres "magiques" pour les expressions régulières, de sorte que les caractères sont interprétés pour leur signification spéciale au lieu de nécessiter un échappement de barre oblique inverse. Ceci est utile pour réduire la verbosité, et parce qu'il est plus cohérent que la valeur par défaut "nomagique". Commencer par \vsignifie «très magique» ou tous les caractères sauf alphanumérique ( A-z0-9 ) et trait de soulignement ( _ ) ont une signification particulière.

La signification des composants est:

% pour l'ensemble du fichier

s substitut

; commencer la chaîne de substitution

\ v "très magique"

^ début de ligne

(. *) 0 ou plus de n'importe quel caractère (groupe 1)

(\ n \ 1) + nouvelle ligne suivie de (groupe 1 correspond au texte), 1 fois ou plus (groupe 2)

$ fin de ligne (ou dans ce cas, pensez que le caractère suivant doit être une nouvelle ligne )

; commencer à remplacer la chaîne

\ 1 groupe 1 correspond au texte

; fin de commande ou début des drapeaux

Gain de sang
la source
1
J'aime beaucoup votre réponse, car elle est plus lisible mais aussi parce qu'elle m'a permis de mieux comprendre la différence entre \net $. \najoute quelque chose au motif: la nouvelle ligne de caractère qui indique à vim que le texte suivant est sur une nouvelle ligne. Alors $que n'ajoute rien au motif, il interdit simplement une correspondance si le caractère suivant en dehors du motif n'est pas une nouvelle ligne. C'est du moins ce que j'ai compris en lisant votre réponse et :help zero-width.
saginaw
Et la même chose doit être vraie pour ^, cela n'ajoute rien au motif, cela empêche juste qu'une correspondance soit faite si le caractère précédent en dehors du motif n'est pas une nouvelle ligne ...
saginaw
@saginaw Vous avez tout à fait raison, et c'est une bonne explication. Dans les expressions régulières, certains caractères peuvent être considérés comme des caractères de contrôle . Par exemple, +signifie «répéter l'expression précédente (caractère ou groupe) 1 fois ou plus», mais ne correspond à rien lui-même. Le ^moyen «ne peut pas commencer au milieu de la chaîne» et $signifie «ne peut pas se terminer au milieu de la chaîne». Remarquez que je n'ai pas dit «ligne», mais «chaîne». Vim traite chaque ligne comme une chaîne par défaut - et c'est là \nqu'intervient. Il dit à Vim de consommer une nouvelle ligne pour essayer de faire cette correspondance.
Bloodgain
8

Si vous souhaitez supprimer TOUTES les lignes identiques adjacentes, et pas seulement Hold, vous pouvez le faire extrêmement facilement avec un filtre externe de l'intérieur vim:

:%!uniq (dans un environnement Unix).

Si vous voulez le faire directement vim, c'est en fait très délicat. Je pense qu'il y a un moyen, mais pour le cas général, il est très difficile de le rendre 100% fonctionnel et je n'ai pas encore résolu tous les bugs.

Cependant, pour ce cas spécifique , puisque vous pouvez voir visuellement que la ligne suivante qui n'est pas en double ne commence pas par le même caractère, vous pouvez utiliser:

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

La +signifie la ligne après la ligne actuelle. Le . fait référence à la ligne actuelle. La /^[^H]/-signifie la ligne avant ( -) la ligne suivante qui ne commence pas par H.

Ensuite, d est supprimé.

Caractère générique
la source
3
Bien que les commandes de remplacement et globales de Vim soient de bons exercices, appeler uniq(à partir de l'intérieur de vim ou à l'aide du shell) est la façon dont je résoudrais cela. D'une part, je suis sûr uniqque les lignes vides / tous les espaces seront équivalents (je ne l'ai pas testé), mais ce serait beaucoup plus difficile à capturer avec une expression régulière. Cela signifie également de ne pas «réinventer la roue» pendant que j'essaie de faire le travail.
Bloodgain
2
La possibilité d'alimenter du texte via des outils externes est la raison pour laquelle je recommande généralement Vim et Cygwin sous Windows. Vim et shell simplement appartiennent ensemble.
DevSolar
2

Une réponse basée sur Vim:

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

= Remplacez chaque ligne suivie par elle-même au moins une fois , par cette même ligne.

VanLaser
la source
2

Un de plus, en supposant que Vim 7.4.218 ou version ultérieure:

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

Ce n'est pas nécessairement meilleur que les autres solutions.

Sato Katsura
la source
2

Voici une solution basée sur un vieux (2003) vim (golf) de Preben Gulberg et Piet Delport.

  • Ses racines résident dans %g/^\v(.*)\n\1$/d
  • Contrairement aux autres solutions, il a été encapsulé dans une fonction donc, il ne modifie pas le registre de recherche, ni le registre sans nom.
  • Et il a également été encapsulé dans une commande afin de simplifier son utilisation:
    • :Uniq(équivalent à :%Uniq),
    • :1,Uniq (du début du buffer à la ligne courante),
    • sélectionner visuellement les lignes + frapper :Uniq<cr>(développé par vim en :'<,'>Uniq)
    • etc ( :h range)

Voici le code:

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

Remarque: leurs premières tentatives ont été:

" 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'
Luc Hermitte
la source