Come sostituire più pattern contemporaneamente con sed?


231

Supponiamo che io abbia una stringa 'abbc' e che voglia sostituire:

  • ab -> bc
  • bc -> ab

Se provo due sostituzioni il risultato non è quello che voglio:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

Quindi quale comando sed posso usare per sostituire come di seguito?

echo abbc | sed SED_COMMAND
bcab

EDIT : In realtà il testo potrebbe avere più di 2 pattern e non so di quanti rimpiazzi avrò bisogno. Dato che c'era una risposta che diceva che sedè un editor di stream e che i suoi sostituti sono avidi, penso che dovrò usare un linguaggio di script per questo.


Devi effettuare più sostituzioni sulla stessa riga? In caso contrario, rilascia la gbandiera da entrambi i s///comandi e funzionerà.
Etan Reisner,

Ti sei perso il punto della mia domanda. Volevo dire che è necessario effettuare ogni sostituzione più di una volta sulla stessa linea. Esiste più di una corrispondenza per ab o bc nell'input originale.
Etan Reisner,

Scusa @EtanReisner ho frainteso, La risposta è sì. il testo può avere più sostituzioni.
DaniloNC,

Risposte:


342

Forse qualcosa del genere:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

Sostituisci ~con un carattere che sai non sarà nella stringa.


9
GNU sed gestisce nuls, quindi puoi usarlo \x0per ~~.
jthill,

3
È gnecessario e cosa fa?
Lee,

12
@Lee gè per globale: sostituisce tutte le istanze del modello in ogni riga, anziché solo la prima (che è il comportamento predefinito).
naught101

1
Si prega di consultare la mia risposta stackoverflow.com/a/41273117/539149 per una variazione della risposta di Ooga che può sostituire più combinazioni contemporaneamente.
Zack Morris,

3
che sai non sarà nella stringa Per il codice di produzione, non fare mai ipotesi sull'input. Per i test, beh, i test non dimostrano mai la correttezza, ma una buona idea per un test è: usare lo script stesso come input.
hagello,

33

Uso sempre più istruzioni con "-e"

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

Questo aggiungerà una '\ n' prima di tutti gli AND, i GROUP BY, i UNION e i FROM, mentre '&' significa la stringa corrispondente e '\ n &' significa che vuoi sostituire la stringa corrispondente con un '\ n' prima del 'matched '


14

Ecco una variazione sulla risposta di ooga che funziona per più ricerche e sostituisce coppie senza dover controllare come i valori potrebbero essere riutilizzati:

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

Ecco un esempio:

prima:

some text AB some more text "BC" and more text.

dopo:

some text BC some more text "CD" and more text.

Si noti che \bindica i confini delle parole, che è ciò che impedisce ________di interferire con la ricerca (sto usando GNU sed 4.2.2 su Ubuntu). Se non si utilizza una ricerca al contorno di parole, questa tecnica potrebbe non funzionare.

Si noti inoltre che ciò fornisce gli stessi risultati della rimozione s/________//ge dell'aggiunta && sed -i 's/________//g' path_to_your_files/*.txtalla fine del comando, ma non richiede di specificare il percorso due volte.

Una variazione generale su questo sarebbe quella di utilizzare \x0o _\x0_al posto di ________se sai che non appare nulla nei tuoi file, come suggerito da jthill .


Sono d'accordo con il commento di hagello sopra sul non fare ipotesi su cosa possa contenere l'input. Pertanto, personalmente ritengo che questa sia la soluzione più affidabile, a parte il sed 's/ab/xy/' | sed 's/cd/ab/' .....
sedersi

12

sedè un editor di stream. Cerca e sostituisce avidamente. L'unico modo per fare ciò che hai chiesto è usare un modello di sostituzione intermedio e cambiarlo alla fine.

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'


4

Questo potrebbe funzionare per te (GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

Questo utilizza una tabella di ricerca che viene preparata e mantenuta nello spazio di attesa (HS) e quindi aggiunta a ciascuna riga. Un marcatore unico (in questo caso\n ) viene anteposto all'inizio della linea e utilizzato come metodo per eseguire il bump lungo la ricerca per tutta la lunghezza della linea. Una volta che il marker raggiunge la fine della linea, il processo è terminato e viene stampato la tabella di ricerca e i marker scartati.

NB La tabella di ricerca viene preparata all'inizio e viene :scelto un secondo marker univoco (in questo caso ) per non scontrarsi con le stringhe di sostituzione.

Con alcuni commenti:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

La tabella funziona in questo modo:

   **   **   replacement
:abbc:bcab
 **   **     pattern

3

Può essere un approccio più semplice per la ricorrenza di un singolo modello che puoi provare come di seguito: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

La mia uscita:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

Per più occorrenze di pattern:

sed 's/\(ab\)\(bc\)/\2\1/g'

Esempio

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

Spero che questo ti aiuti !!


2

Tcl ha una funzione integrata per questo

$ tclsh
% string map {ab bc bc ab} abbc
bcab

Funziona camminando sulla stringa di un carattere alla volta facendo confronti di stringhe a partire dalla posizione corrente.

In perl:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

0

Ecco un awkbasato su oogassed

echo 'abbc' | awk '{gsub(/ab/,"xy");gsub(/bc/,"ab");gsub(/xy/,"bc")}1'
bcab
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.