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

19

Eu não consigo fazer funcionar. A documentação do GNU sed diz para escapar do tubo, mas isso não funciona, nem o uso de 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
    
por Gregg Leventhal 19.07.2014 / 04:03

4 respostas

23

Por padrão, sed usa Publicações regulares básicas POSIX , que não incluem o operador de alternância | . Muitas versões do sed , incluindo o GNU e o FreeBSD, suportam a comutação para Expressões regulares estendidas , que incluem | alternação. Como você faz isso varia: O GNU sed usa -r , enquanto FreeBSD , NetBSD , OpenBSD e OS X sed use -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 ele funcionará nesses sistemas BSD e sed -r com o GNU.

O GNU sed parece ter suporte totalmente não documentado, mas funcional para -E , portanto, se você tiver um script multiplataforma que esteja 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 BSD suportam -r como um alias não documentado também. O OS X ainda não faz hoje e as máquinas mais antigas do NetBSD e do OpenBSD que eu tenho acesso também não, mas o NetBSD 6.1 funciona. Os Unices comerciais que eu posso alcançar universalmente não. Então, 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.

    
por 19.07.2014 / 04:17
6

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

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

Dessa forma, se a linha não contiver a sequência cat e não contiver a sequência dog sed b ranchos fora do script, os autoprints serão linha atual e puxa o próximo para começar o próximo ciclo. Portanto, ele não executa a próxima instrução - que neste exemplo c trava a linha inteira para ler Bear , mas pode fazer qualquer coisa.

Provavelmente vale notar também que qualquer declaração após o comando !b nesse sed pode somente corresponder em uma linha contendo a string dog ou cat - para que você possa faça mais testes sem qualquer perigo de combinar uma linha que não o faça - o que significa que agora você pode aplicar regras apenas a uma ou a outra.

Mas isso é 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 consulta com referências anteriores.

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

É muito mais trabalhoso configurar para esse exemplo de exemplo simples, mas isso pode tornar scripts sed muito mais flexíveis no longo prazo.

Na primeira linha, eu e x change armazena espaço e espaço de padrão e insere a string <space> cat <space> dog <space> no espaço de espera antes de e x alterá-los de volta.

A partir de então, em cada linha seguinte, eu coloco G et espaço anexado ao espaço padrão, depois verifico se todos os caracteres do começo da linha até a nova linha que acabei de adicionar no final correspondem a uma string cercado por espaços depois. Se assim for, eu substituo o lote inteiro por Urso e se não houver nenhum dano feito, porque eu a próxima P rint somente até a primeira nova linha em espaço padrão então d elete tudo. / p>

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

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

printf %s\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9] \([^ ]*\) .*/Bear/;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 do Greg Os e-mails de usb do Ubben sobre o assunto quando, nos anos 90, ele descreveu como ele construiu uma calculadora grosseira a partir de uma única declaração sed s/// .

    
por 19.07.2014 / 05:37
6

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

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

Na página sed man:

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

Note 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.

    
por 19.07.2014 / 04:16
1

essa é uma pergunta bem antiga, mas no caso de alguém querer tentar, há uma maneira de esforço relativamente baixa para fazer isso em sed com arquivos sed. Cada opção pode ser listada em uma linha separada e sed irá avaliar cada uma delas. É um equivalente lógico de ou. Por exemplo, para remover linhas que contenham 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
    
por 30.04.2018 / 09:45