Troque de maneira limpa todas as ocorrências de duas seqüências usando sed


13

Suponha que eu tenha um arquivo que contenha várias ocorrências de StringA e StringB. Desejo substituir todas as ocorrências de StringA por StringB e (simultaneamente) todas as ocorrências de StringB por StringA.

No momento, estou fazendo algo como

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

O problema com essa abordagem é que ela assume que StringC não ocorre no arquivo. Embora isso não seja um problema na prática, essa solução ainda parece suja - ou seja, parece uma oportunidade de aprender mais mágica do unix. :)

Respostas:


11

Se StringBe StringAnão puder aparecer na mesma linha de entrada, você poderá dizer ao sed para executar a substituição de uma maneira e tentar apenas a outra se não houver ocorrências da primeira sequência pesquisada.

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

No caso geral, não acho que exista um método fácil no sed. A propósito, observe que a especificação é ambígua se StringAe StringBpode se sobrepor. Aqui está uma solução Perl, que substitui a ocorrência mais à esquerda de qualquer sequência e se repete.

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

Se você deseja usar as ferramentas POSIX, o awk é o caminho a seguir. O Awk não possui um primitivo para substituições parametrizadas gerais, portanto, você precisa fazer o seu próprio.

<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 executo o primeiro comando, sed me diz sed: can't read s/StringB/StringA/g: No such file or directory. Parece que -e t PATTERNnão está bem entendido.
Gyscos

1
@ Gyscos Houve uma falta -eantes do segundo scomando. Eu consertei minha resposta.
Gilles 'SO- stop being evil' em

8

No momento, estou fazendo algo como
...............
O problema com essa abordagem é que ela supõe que StringC não ocorre no arquivo.

Eu acho que sua abordagem é boa, você deve usar outra coisa em vez de uma string, algo que não pode ocorrer em uma linha (no espaço do padrão). O melhor candidato é o \newline.
Normalmente, nenhuma linha de entrada no espaço do padrão conterá esse caractere, portanto, para trocar todas as ocorrências de THISe THATem um arquivo, você pode executar:

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

ou, se o seu sed também suportar \nno RHS:

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

1
Isso é lindo. Eu chorei um pouco Outra maneira de fazer as novas linhas do RHS são as variáveis ​​de shell - se o sedsuporte a determinadas fugas ou não se torna muito menos importante se você preparar algumas macros com antecedência. Como set /THIS /THAT "$(printf \\n/)"; sed "s/$2/\\$4g;s/$3$2/g;s/\\n$3/g"- meio estúpido aqui, é certo, mas faz muito mais sentido quando em outros momentos - especialmente para aulas de char e similares.
mikeserv

Bem, e quanto a isso, cara. Há até uma resposta lá sobre isso. Estava lá quando eu fiz o comentário? Acabei de ver a coisa aparecer na lista editada recentemente (talvez) e a linha superior da resposta principal estava um pouco fora (se você se importa apenas com o Linux não incorporado, eu acho) . Eu prefiro a sugestão de Gilles lá - a menos que você esteja fazendo uma longa corrida sed, o garfo constante eé um pesadelo. Em uma nota diferente - eu brinco pastepor um dia inteiro. Eu fiz um analisador de opções - columntipo. Apenas gera traços para as strings de entrada e as strings juntos.
precisa saber é o seguinte

3

Eu acho perfeitamente válido usar uma string "nonce" para trocar duas palavras. Se você deseja uma solução mais geral, pode fazer algo como:

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

Isso produz

say me say you

Observe que você precisa das duas substituições adicionais aqui para evitar a substituição x_xse tiver cadeias "x_x". Mas mesmo isso ainda parece mais simples do que a awksolução para mim.


Isso parece ser o que o Asker disse que já estava fazendo.
roaima

1
Sim, ignorei isso no início (consulte o histórico de edição), mas minha solução fornecida é diferente, pois funciona mesmo quando a cadeia de substituição (aqui "x_x") ocorre na cadeia original, portanto é mais geral.
David Ongaro

Inteligente, mas há um problema. Se StringA ou StringB contiver _, é necessário ajustar a _própria (escolha outro caractere) ou a string problemática (executá s/_/__/g-la de antemão, parece melhor). Sua solução, como é, não pode ser aplicada às cegas para trocar seqüências arbitrárias.
Kamil Maciorowski

@KamilMaciorowski Não entendo o que você quer dizer? Na verdade, aplico s/_/__/gantecipadamente. Talvez apenas mostre uma caixa de teste que falha.
David Ongaro

@KamilMaciorowski ah, acho que entendo agora. Você quer dizer se as cadeias de substituição em si contêm um _, por exemplo, substituindo y_oupor me. Sim, é verdade que é preciso estar ciente disso e y__ouexpressar a expressão. Um script que leva a substituição como parâmetros de entrada também precisa levar isso em conta.
David Ongaro
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.