Scambia in modo pulito tutte le occorrenze di due stringhe usando sed


13

Supponiamo di avere un file che contiene più occorrenze di StringA e StringB. Voglio sostituire tutte le occorrenze di StringA con StringB e (contemporaneamente) tutte le occorrenze di StringB con StringA.

In questo momento, sto facendo qualcosa del genere

cat file.txt | sed 's/StringB/StringC/g' | sed 's/StringA/StringB/g' | sed 's/StringC/StringA/g'

Il problema con questo approccio è che presuppone che StringC non si verifichi nel file. Sebbene questo non sia un problema in pratica, questa soluzione sembra ancora sporca - cioè, sembra un'opportunità per imparare più magia unix. :)

Risposte:


11

Se StringBe StringAnon può apparire sulla stessa riga di input, allora puoi dire a sed di eseguire la sostituzione in un modo, e provare solo nell'altro modo se non ci sono occorrenze della prima stringa cercata.

<file.txt sed -e 's/StringA/StringB/g' -e t -e 's/StringB/StringA/g'

Nel caso generale, non penso che ci sia un metodo semplice in sed. A proposito, nota che la specifica è ambigua se StringAe StringBpuò sovrapporsi. Ecco una soluzione Perl, che sostituisce l'occorrenza più a sinistra di entrambe le stringhe e si ripete.

<file.txt perl -pe 'BEGIN {%r = ("StringA" => "StringB", "StringB" => "StringA")}
                    s/(StringA|StringB)/$r{$1}/ge'

Se vuoi restare con gli strumenti POSIX, awk è la strada da percorrere. Awk non ha una primitiva per sostituzioni parametriche generali, quindi è necessario eseguire il rollback.

<file.txt awk '{
    while (match($0, /StringA|StringB/)) {
        printf "%s", substr($0, 1, RSTART-1);
        $0 = substr($0, RSTART);
        printf "%s", /^StringA/ ? "StringB" : "StringA";
        $0 = substr($0, 1+RLENGTH)
    }
    print
}'

Quando eseguo il primo comando, mi dice sed sed: can't read s/StringB/StringA/g: No such file or directory. Sembra che -e t PATTERNnon sia ben compreso.
Gyscos

1
@Gyscos Mancava -eprima del secondo scomando. Ho corretto la mia risposta.
Gilles 'SO- smetti di essere malvagio'

8

In questo momento, sto facendo qualcosa di simile
...............
Il problema con questo approccio è che presume che StringC non si verifichi nel file.

Penso che il tuo approccio vada bene, dovresti semplicemente usare qualcos'altro al posto di una stringa, qualcosa che non può verificarsi in una linea (nello spazio del modello). Il miglior candidato è la \newline.
Normalmente, nessuna riga di input nello spazio modello conterrà quel carattere, quindi, per scambiare tutte le occorrenze di THISe THATin un file, è possibile eseguire:

sed 's/THIS/\
/g
s/THAT/THIS/g
s/\n/THAT/g' infile

o, se la tua sed supporta \nanche nell'RHS:

sed 's/THIS/\n/g;s/THAT/THIS/g;s/\n/THAT/g' infile

1
Questo è bellissimo. Ho pianto un po '. Un altro modo di eseguire le nuove righe RHS è rappresentato dalle variabili shell: se i sedsupporti per determinate escape sono o meno diventano molto meno importanti se si preparano in anticipo alcune macro. Come set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- un po 'stupido qui, è vero, ma ha molto più senso quando altre volte - specialmente per classi di char e simili.
Mikeserv,

Beh che ne dici, amico. C'è persino una risposta al riguardo. È stato lì quando ho fatto il commento? Ho appena visto la cosa apparire nella lista modificata di recente (forse) e la riga superiore della risposta principale era un po 'fuori (se ti interessa solo Linux non incorporato, immagino) . Preferisco il suggerimento di Gilles lì - a meno che tu non stia facendo un lungo periodo sed, la costante forchetta con cui sopra eè un vero incubo. Su una nota diversa: ci sto giocando da pasteun giorno intero. Ho fatto un parser di opzioni - come un columntipo di. Genera semplicemente trattini per l'input di stringhe e roba di stringhe insieme.
Mikeserv,

3

Penso che sia perfettamente valido usare una stringa "nonce" per scambiare due parole. Se vuoi una soluzione più generale puoi fare qualcosa del tipo:

sed 's/_/__/g; s/you/x_x/g; s/me/you/g; s/x_x/me/g; s/__/_/g' <<<"say you say me"

Questo cede

say me say you

Nota che qui hai bisogno di due ulteriori sostituzioni per evitare la sostituzione x_xse ti capita di avere stringhe "x_x". Ma anche quello sembra ancora più semplice della awksoluzione per me.


Questo sembra essere ciò che l'Asker ha detto che stavano già facendo.
roaima,

1
Sì, all'inizio l'ho trascurato (vedi la cronologia delle modifiche) ma la mia soluzione data è diversa poiché funziona anche quando la stringa di sostituzione (qui "x_x") si trova nella stringa originale, quindi è più generale.
David Ongaro,

Intelligente, ma c'è un problema. Se StringA o StringB contiene _, è necessario regolare _se stesso (scegliere un altro carattere) o la stringa problematica (eseguire s/_/__/gprima su di essa, sembra meglio). La tua soluzione così com'è non può essere applicata alla cieca per scambiare stringhe arbitrarie.
Kamil Maciorowski,

@KamilMaciorowski Non capisco cosa intendi? In realtà applico un s/_/__/ganticipo. Forse puoi solo mostrare una testcase che fallisce.
David Ongaro,

@KamilMaciorowski ah penso di aver capito adesso. Vuoi dire che se le stringhe di sostituzione si contengono _, così dicono sostituzione y_oucon me. Sì, è vero, bisogna esserne consapevoli e metterlo y__ounell'espressione. Uno script che prende la sostituzione come parametri di input deve anche tenerne conto.
David Ongaro,
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.