Utilisez ex-command pour vérifier si deux lignes sont identiques?

9

Je regardais cette question et je me suis ensuite demandé comment je pourrais implémenter ma réponse qui utilise sed uniquement POSIX ex .

L'astuce est que même si sedje peux comparer l'espace de maintien avec l'espace de motif pour voir s'ils sont exactement équivalents (avec G;/^\(.*\)\n\1$/{do something}), je ne connais aucun moyen de faire un tel test dans ex.

Je sais que dans Vim, je pourrais Yécrire la première ligne, puis taper :2,$g/<C-r>0/dpour presque faire ce que je spécifie - mais si la première ligne contient autre chose que du texte alphanumérique très simple, cela devient vraiment chanceux, car la ligne est déversée en tant que regex , pas seulement une chaîne de comparaison. (Et si la première ligne contient une barre oblique, le reste de la ligne sera interprété comme une commande!)

Donc, si je veux supprimer toutes les lignes myfilequi sont identiques à la première ligne - mais pas supprimer la première ligne - comment pourrais-je le faire en utilisant ex? D'ailleurs, comment pourrais-je le faire en utilisant vi?

Existe-t-il un moyen POSIX de supprimer une ligne si elle correspond exactement à une autre ligne?

Peut-être quelque chose comme cette syntaxe imaginaire:

:2,$g/**lines equal to "0**/d
Caractère générique
la source
3
Vous pourriez construire la commande, mais elle aurait besoin d'un peu de vimscript et ce ne serait probablement pas une manière POSIX::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw
1
@saginaw, merci. Jusqu'à présent, la seule approche POSIX qui m'est venue à l'esprit est de simplement l'utiliser sedcomme filtre de l'intérieur exet d'exécuter toute ma sedréponse sur tout le tampon ... ce qui fonctionnerait, bien sûr (et est en fait portable contrairement à sed -i).
Wildcard
Vous avez raison et je trouve votre approche initiale <C-r>0très bonne. Je ne suis pas sûr que vous puissiez faire mieux avec uniquement les commandes Ex, car vous devez protéger les caractères spéciaux. Sans la contrainte conforme POSIX, je pense que vous utiliseriez le commutateur très nomagique \V, puis vous protégeriez la barre oblique inverse (car elle conserve sa signification spéciale même avec \V) avec la escape()fonction dont le deuxième argument est une chaîne contenant tous les caractères que vous souhaitez échapper / protéger. .
saginaw
Cependant, dans la commande précédente, j'ai oublié de protéger également la barre oblique, car elle a également une signification particulière pour la commande globale, c'est le délimiteur de modèle. Ainsi, la commande correcte serait probablement quelque chose comme: :execute '2,$g/\V' . escape(getline(1), '\/') . '/d'Ou vous pouvez utiliser un autre caractère pour le délimiteur de modèle comme un point-virgule. Dans ce cas, vous n'auriez pas besoin de protéger une barre oblique dans le motif. Cela donnerait quelque chose comme::execute '2,$g;\V' . escape(getline(1), '\') . ';d'
saginaw
1
Je trouve votre deuxième approche sedégalement très bonne. Avec Vim, vous déléguez souvent certaines tâches spéciales à d'autres programmes, et sedc'est probablement un bon exemple de cela. Soit dit en passant, vous n'avez pas à exécuter sedtout votre tampon. Si vous souhaitez l'exécuter uniquement sur une partie du tampon, vous pouvez donner une plage. Par exemple, si vous souhaitez filtrer uniquement les lignes entre 50 et 100, vous pouvez taper: :50,100!<your sed command>.
saginaw

Réponses:

3

Vigueur

Dans Vim, vous pouvez faire correspondre n'importe quel caractère, y compris la nouvelle ligne avec \_.. Vous pouvez l'utiliser pour construire un modèle qui correspond à une ligne entière, n'importe quelle quantité de choses, puis cette même ligne:

/\(^.*$\)\_.*\n\1$/

Vous souhaitez maintenant supprimer toutes les lignes d'un fichier qui correspondent à la première, sans inclure la première. La substitution pour supprimer la dernière ligne qui correspond à la première est:

:1 s/\(^.*$\)\_.*\zs\n\1$//

Vous pouvez utiliser :globalpour vous assurer que la substitution est répétée suffisamment de fois pour supprimer toutes les lignes:

:g/^/ 1s/\(^.*$\)\_.*\zs\n\1$//

POSIX ex

@saginaw montre une façon plus nette de le faire dans Vim dans un commentaire à votre question, mais nous pouvons adapter la technique ci-dessus pour POSIX ex.

Pour ce faire d'une manière compatible POSIX, vous devez interdire la correspondance sur plusieurs lignes, mais vous pouvez toujours utiliser des références arrières. Cela nécessite un travail supplémentaire:

:g/^/ t- | s/^/@@@/ | 1t- | s/^/"/ | j! | s/^"\(.*\)@@@\1$/d/ | d x | @x

Voici la ventilation:

:g/^/                   for each line

t- |                    copy it above

s/^/@@@/ |              prefix it with something unique (@@@)
                        (do a search in the buffer first to make
                        sure it really is unique)

1t- |                   copy the first line above this one

s/^/"/ |                prefix with "

j! |                    join those two lines (no spaces)

s/^"\(.*\)@@@\1$/d/ |   if the part after the " and before the @@@
                        matches the part after the @@@, replace the line
                        with d

d x |                   delete the line into register x

@x                      execute it

Donc, si la ligne actuelle est un doublon de la ligne 1, le registre x contiendra d. L'exécuter supprimera la ligne actuelle. S'il ne s'agit pas d'un doublon, il contiendra un non-sens préfixé avec "lequel lorsqu'il est exécuté est un no-op, car " commence un commentaire. Je ne sais pas si c'est la meilleure façon d'y parvenir, c'est juste la première qui me vienne à l'esprit!

Il se trouve que la première ligne ne peut pas être supprimée car le processus de copie modifie temporairement la ligne 1. Si cela n'avait pas été le cas, vous pourriez plutôt préfixer le :gavec une 2,$plage.

Testé dans Vim et ex-vi version 4.0.

ÉDITER

Et une manière plus simple, qui échappe les caractères spéciaux pour créer un modèle de recherche (avec 'nomagic'set), construit une :globalcommande, puis l'exécute:

:set nomagic
:1t1 | .g/^/ s#\[$^\/]#\\\&#g | s#\.\*#2,$g/^\&$/d# | d x
:@x
:set magic

Cependant, vous ne pouvez pas le faire en tant que doublure, car vous auriez un imbriqué :global, ce qui n'est pas autorisé.

Antony
la source
2

Il semblerait que la seule façon POSIX de procéder consiste à utiliser un filtre externe, tel que sed.

Par exemple, pour supprimer la 17e ligne de votre fichier uniquement si elle est exactement identique à la 5e ligne, et sinon la laisser inchangée, vous pouvez procéder comme suit:

:1,17!sed '5h;17{G;/^\(.*\)\n\1$/d;s/\n.*$//;}'

(Vous pouvez exécuter sedici tout le tampon, ou vous pouvez l'exécuter uniquement sur les lignes 5 à 17, mais dans le premier cas, vous effectuez un filtrage inutile - ce n'est pas grave - et dans le dernier cas, vous devrez utiliser le chiffres 1 et 13 dans votre sedcommande au lieu de 5 et 17. Confus.)

Comme sedil n'y a qu'une seule passe avant, il n'y a pas de moyen facile de faire l'inverse et de supprimer la 5ème ligne uniquement si elle est identique à la 17ème ligne. J'ai essayé pendant un moment par curiosité ... c'est délicat .


Percée - Vous pouvez le faire comme ceci:

:17t 5
:5,5+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Il s'agit en fait de la méthode la plus générale. Il peut également être utilisé pour donner le même résultat que la première commande (et supprimer la 17e ligne uniquement si elle est identique à la 5e ligne) comme ceci:

:5t 17
:17,17+!sed '1N;/^\(.*\)\n\1$/d;s/\n.*$//'

Pour des utilisations plus larges telles que la suppression de toutes les lignes du fichier qui sont identiques à la ligne 37, tout en laissant la ligne 37 intacte, vous pouvez procéder comme suit:

:37,$!sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//'
:37t 0
:1,37!sed '1{h;d;};G;/^\(.*\)\n\1$/d;s/\n.*$//'

La conclusion ici est que, pour vérifier si deux lignes sont identiques, le meilleur outil ne l' est sed pas ex. Mais comme DevSolar y a fait allusion dans un commentaire , ce n'est pas un échec viou ex- ils sont conçus pour fonctionner avec les outils Unix; c'est une force majeure.

Caractère générique
la source
Beaucoup plus difficile: insérer une ligne à la fin d'un fichier, uniquement si la ligne n'existe pas déjà quelque part dans le fichier.
Wildcard
Cela devrait être faisable avec une approche similaire à ma réponse. Je ne pense pas que ce serait un aller simple!
Antony