Alternação / operador Regex (foo | bar) no GNU ou BSD Sed


28

Não consigo fazê-lo funcionar. A documentação do GNU sed diz para escapar do tubo, mas isso não funciona, nem usar um tubo reto sem o escape. Adicionar parênteses não faz diferença.

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog

Respostas:


33

Por padrão,sed usa Expressões regulares regulares do POSIX , que não incluem o |operador de alternância. Muitas versões sed, incluindo GNU e FreeBSD, suportam a mudança para Expressões regulares estendidas , que incluem |alternância. Como você faz isso varia: O GNU sed usa-r , enquanto o FreeBSD , NetBSD , OpenBSD e OS X sed usam -E. Outras versões geralmente não suportam nada. Você pode usar:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

e funcionará nesses sistemas BSD e sed -rcom o GNU.


O GNU sedparece ter um suporte totalmente não documentado, mas funcionando -E, por isso, se você tiver um script de multiplataforma que está confinado ao acima, essa é sua melhor opção. Como não está documentado, você provavelmente não pode confiar nele.

Um comentário observa que as versões do BSD também suportam -rum alias não documentado. O OS X ainda não funciona hoje e as máquinas NetBSD e OpenBSD mais antigas às quais tenho acesso também não, mas o NetBSD 6.1 possui. Os escritórios comerciais que eu posso alcançar universalmente não. Portanto, com tudo isso, a questão da portabilidade está ficando bastante complicada neste momento, mas a resposta simples é mudar para,awk se você precisar, que usa EREs em todos os lugares.


Os três BSDs você mencionou todos suporte a -ropção como sinônimo de -Epara compatibilidade com GNU sed. O OpenBSD e OS X sed -Einterpretarão o tubo escapado como um tubo literal, não como um operador de alternância. Aqui está um link para a página de manual do NetBSD e aqui está um para o OpenBSD que não tem dez anos.
damien



9

Isso acontece porque (a|b)é uma expressão regular estendida, não uma Expressão Regular Básica. Use a -Eopção para lidar com isso.

echo 'cat
dog
pear
banana
cat
dog'|sed -E 's/cat|dog/Bear/g'

Na sedpágina do manual:

 -E      Interpret regular expressions as extended (modern) regular
         expressions rather than basic regular expressions (BRE's).

Observe que -ré outro sinalizador para a mesma coisa, mas -Eé mais portátil e até estará na próxima versão das especificações do POSIX.


6

A maneira portátil de fazer isso - e a maneira mais eficiente - é com endereços. Você consegue fazer isso:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

Dessa maneira, se a linha não contiver a string cat e não contiver as ranhuras para cães sed b do script, imprime automaticamente sua linha atual e puxa a próxima para iniciar o próximo ciclo. Portanto, ele não executa a próxima instrução - que neste exemplo ctrava a linha inteira para ler Bear, mas pode fazer qualquer coisa.

Provavelmente é importante notar também que qualquer declaração após a !bem que sedcomando pode única corresponder em uma linha contendo tanto uma string dogou cat- para que você possa realizar mais testes sem qualquer perigo de combinar uma linha que não faz - o que significa que agora você pode aplicar regras para apenas um ou outro também.

Mas é o próximo. Aqui está a saída do comando acima:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Você também pode implementar de forma portável uma tabela de pesquisa com referências anteriores.

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

É muito mais trabalhoso configurar para este exemplo simples, mas pode criar sedscripts muito mais flexíveis a longo prazo.

Na primeira linha, xaltero o espaço de espera e o espaço do padrão e, em seguida, insiro o cão <space>felino<space><space> no espaço de espera antes de xmudá-los de volta.

A partir de então e em todas as linhas seguintes, Gmantenha o espaço anexado ao espaço do padrão e verifique se todos os caracteres do início da linha até a nova linha que acabei de adicionar no final correspondem a uma sequência cercada por espaços após ela. Nesse caso, substituo o lote inteiro pelo Bear e, se não houver, não haverá danos, pois na próxima Petapa, somente até a primeira nova linha que ocorrer no espaço do padrão, delimino tudo.

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

E quando digo flexível, quero dizer isso. Aqui está substituindo gato por BrownBear e cachorro por BlackBear :

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

É claro que você pode expandir bastante o conteúdo da tabela de pesquisa - peguei a ideia nos e-mails de Greg Ubben sobre o assunto quando, nos anos 90, ele descreveu como construiu uma calculadora grosseira a partir de uma única sed s///declaração.


1
ufa, +1. Você tem uma propensão para pensar fora da caixa devo dizer
Iruvar

@ 1_CR - Veja minha última edição - não é minha ideia - o que não quer dizer que não aprecie isso e que considere um elogio. Mas eu gosto de dar crédito onde é devido.
mikeserv

1

Essa é uma pergunta bastante antiga, mas, se alguém quiser tentar, existe uma maneira bastante baixa de fazer isso no sed com arquivos sed. Cada opção pode ser listada em uma linha separada, e o sed avaliará cada uma. É um equivalente lógico de ou. Por exemplo, para remover linhas que contêm um determinado código:

Você pode dizer : sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

ou coloque isso no seu arquivo sed:

/^\/\*!40103.*\/;$/d
/^\/\*!40101.*\/;$/d
/^\/\*!40111.*\/;$/d

0

Aqui está uma técnica que não utiliza nenhuma opção específica de implementação para sed(por exemplo -E, -r). Em vez de descrever o padrão como uma única regex cat|dog, podemos simplesmente executar sedduas vezes:

echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat/Bear/g' | sed 's/dog/Bear/g'

É uma solução óbvia, realmente, mas vale a pena compartilhar. Geralmente se generaliza para mais de duas seqüências de padrões, embora uma cadeia muito longa sednão seja muito bonita.

Costumo usar sed -i(que funciona da mesma maneira em todas as implementações) para fazer alterações nos arquivos. Aqui, uma longa lista de seqüências de caracteres de padrão pode ser bem incorporada, pois cada resultado temporário é salvo no arquivo:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
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.