Utilizzare ex-comando per verificare se due linee sono identiche?


9

Stavo esaminando questa domanda e poi mi chiedevo come avrei potuto implementare la mia risposta che utilizza sed usando puramente POSIX ex .

Il trucco è che mentre sedriesco a confrontare lo spazio di mantenimento con lo spazio del modello per vedere se sono esattamente equivalenti (con G;/^\(.*\)\n\1$/{do something}), non so come fare un simile test ex.

So che in Vim potrei Yankare la prima riga e quindi digitare :2,$g/<C-r>0/dper fare quasi quello che sto specificando, ma se la prima riga contiene altro che un testo alfanumerico molto semplice, questo diventa davvero un caso, dal momento che la riga viene scaricata come regex , non solo una stringa per il confronto. (E se la prima riga contiene una barra, il resto della riga verrà interpretato come un comando!)

Quindi, se voglio eliminare tutte le righe myfileidentiche alla prima riga, ma non eliminare la prima riga, come potrei farlo usando ex? Del resto, come potrei farlo usando vi?

Esiste un modo POSIX per eliminare una riga se corrisponde esattamente a un'altra riga?

Forse qualcosa come questa sintassi immaginaria:

:2,$g/**lines equal to "0**/d

3
Potresti creare il comando, ma avrebbe bisogno di un po 'di vimscript e probabilmente non sarebbe un modo POSIX::execute '2,$g/\V' . escape(getline(1), '\') . '/d'
saginaw

1
@saginaw, grazie. Finora l'unico POSIX approccio che si è verificato a me è quello di utilizzare solo sedcome un filtro dall'interno ex, ed eseguire tutta la mia sedrisposta su tutto il buffer di ..., che avrebbe funzionato, naturalmente (e in realtà è portatile a differenza sed -i).
Wildcard il

Hai ragione e trovo il tuo approccio iniziale <C-r>0molto buono. Non sono sicuro che potresti fare di meglio con solo i comandi Ex perché devi proteggere caratteri speciali. Senza il vincolo conforme a POSIX penso che useresti l'opzione molto nomagica \Ve quindi proteggeresti la barra rovesciata (perché mantiene il suo significato speciale anche con \V) con la escape()funzione il cui secondo argomento è una stringa contenente tutti i caratteri che vuoi scappare / proteggere .
Saginaw,

Tuttavia, nel comando precedente ho dimenticato di proteggere anche la barra, poiché ha anche un significato speciale per il comando globale, è il delimitatore di pattern. Quindi il comando corretto sarebbe probabilmente qualcosa del tipo: :execute '2,$g/\V' . escape(getline(1), '\/') . '/d'O potresti usare un altro carattere per il delimitatore di pattern come un punto e virgola. In questo caso non è necessario proteggere una barra in avanti nel modello. :execute '2,$g;\V' . escape(getline(1), '\') . ';d'
Darebbe

1
Trovo che anche il tuo secondo approccio sia sedmolto valido. Con Vim, spesso deleghi determinati compiti speciali ad altri programmi, ed sedè probabilmente un buon esempio di ciò. A proposito, non è necessario eseguire sedl'intero buffer. Se si desidera eseguirlo solo su una parte del buffer, è possibile fornire un intervallo. Per esempio, se si desidera filtrare solo le linee tra 50 e 100, è possibile digitare: :50,100!<your sed command>.
Saginaw,

Risposte:


3

Vim

In Vim puoi abbinare qualsiasi personaggio incluso newline con \_.. Puoi usarlo per costruire un modello che corrisponda a un'intera linea, a qualsiasi quantità di elementi e quindi alla stessa linea:

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

Ora vuoi eliminare tutte le righe in un file che corrispondono al primo, escluso il primo. La sostituzione per eliminare l'ultima riga corrispondente alla prima è:

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

È possibile utilizzare :globalper assicurarsi che la sostituzione venga ripetuta abbastanza volte per eliminare tutte le righe:

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

POSIX ex

@saginaw mostra un modo più semplice di farlo in Vim in un commento alla tua domanda, ma possiamo adattare la tecnica sopra per POSIX ex.

Per fare ciò in modo compatibile con POSIX, è necessario impedire la corrispondenza su più righe, ma è comunque possibile utilizzare i riferimenti indietro. Ciò richiede un lavoro extra:

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

Ecco la ripartizione:

: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

Quindi se la riga corrente è un duplicato della riga 1, il registro x conterrà d. L'esecuzione eliminerà la riga corrente. Se non è un duplicato, conterrà un nonsenso con prefisso con "cui quando eseguito è un non-op, poiché " inizia un commento. Non so se questo è il modo migliore per farlo, è solo il primo che mi è venuto in mente!

Accade solo che la prima riga non può essere eliminata perché il processo di copia modifica temporaneamente la riga 1. Se così non fosse, puoi invece aggiungere :gun 2,$intervallo.

Testato in Vim ed ex-vi versione 4.0.

MODIFICARE

E un modo più semplice, che sfugge ai caratteri speciali per creare un modello di ricerca (con 'nomagic'set), crea un :globalcomando, quindi lo esegue:

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

Tuttavia, non puoi farlo come one-liner, poiché avresti un nidificato :global, il che non è consentito.


2

Sembrerebbe che l'unico modo POSIX per farlo sia usare un filtro esterno, come sed.

Ad esempio, per eliminare la diciassettesima riga del file solo se è esattamente identica alla quinta riga e in caso contrario lasciarla invariata, è possibile effettuare le seguenti operazioni:

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

(È possibile eseguire sedl'intero buffer qui, oppure è possibile eseguirlo solo sulle righe 5-17, ma nel primo caso si sta eseguendo un filtro non necessario, non è un grosso problema, e nel secondo caso è necessario utilizzare il numeri 1 e 13 nel tuo sedcomando invece di 5 e 17. Confusione.)

Poiché sedesegue solo un singolo passaggio in avanti, non esiste un modo semplice per fare il contrario ed eliminare la quinta riga solo se è identica alla diciassettesima riga. Ho provato per un po 'come un punto di curiosità ... è difficile .


Breakthrough : puoi farlo in questo modo:

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

Questo è in realtà il metodo più generale. Allo stesso modo può essere usato per dare lo stesso risultato del primo comando (ed eliminare la diciassettesima riga solo se è identica alla quinta riga) in questo modo:

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

Per usi più ampi come l'eliminazione di tutte le righe del file identiche alla riga 37, lasciando intatta la riga 37, è possibile effettuare le seguenti operazioni:

: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 conclusione qui è, per verificare se due linee sono identiche, lo strumento migliore è sed no ex. Ma come DevSolar ha accennato in un commento , questo non è un fallimento vio ex— sono progettati per funzionare con gli strumenti Unix; questa è una grande forza.


Molto, molto più difficile è: inserire una riga alla fine di un file, solo se la linea non esiste già da qualche parte nel file.
Wildcard il

Ciò dovrebbe essere fattibile con un approccio simile alla mia risposta. Non penso che sarebbe un one-liner però!
Antony,
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.