Pesquise e substitua no bash usando expressões regulares

160

Eu já vi este exemplo:

hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//[0-9]/}

O que segue esta sintaxe: ${variable//pattern/replacement}

Infelizmente, o patterncampo parece não suportar sintaxe de regex completa (se eu usar .ou \s, por exemplo, tentar corresponder aos caracteres literais).

Como posso procurar / substituir uma string usando a sintaxe regex completa?

Lanaru
fonte
Achei uma pergunta relacionada aqui: stackoverflow.com/questions/5658085/...
jheddings
2
Para sua informação, \snão faz parte da sintaxe de expressão regular definida pelo POSIX (nem BRE nem ERE); é uma extensão PCRE e, principalmente, não está disponível no shell. [[:space:]]é o equivalente mais universal.
Charles Duffy
1
\spodem ser substituídos por [[:space:]], a propósito, .por ?, e extensões extglob para a linguagem de padrão de linha de base da linha de base podem ser usadas para itens como subgrupos opcionais, grupos repetidos e similares.
Charles Duffy
Eu uso isso na versão 4.1.11 do bash no Solaris ... echo $ {hello // [0-9]} Observe a falta da barra final.
Daniel Liston

Respostas:

175

Use sed :

MYVAR=ho02123ware38384you443d34o3434ingtod38384day
echo "$MYVAR" | sed -e 's/[a-zA-Z]/X/g' -e 's/[0-9]/N/g'
# prints XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

Observe que os subsequentes -esão processados ​​em ordem. Além disso, o gsinalizador para a expressão corresponderá a todas as ocorrências na entrada.

Você também pode escolher sua ferramenta favorita usando este método, por exemplo, perl, awk, por exemplo:

echo "$MYVAR" | perl -pe 's/[a-zA-Z]/X/g and s/[0-9]/N/g'

Isso pode permitir que você faça correspondências mais criativas ... Por exemplo, no snip acima, a substituição numérica não seria usada a menos que houvesse uma correspondência na primeira expressão (devido a uma andavaliação lenta ). E, claro, você tem o suporte completo ao Perl para fazer sua licitação ...

casamentos
fonte
Isso apenas substitui uma única vez, até onde eu sei. Existe uma maneira de substituir todas as ocorrências do padrão como o código que eu publiquei?
Lanaru
Atualizei minha resposta para demonstrar várias substituições, bem como a correspondência global de padrões. Deixe-me saber se isso ajuda.
casamentos
Muito obrigado! Por curiosidade, por que você mudou de uma versão de uma linha (na sua resposta original) para uma de duas linhas?
Lanaru 24/10/12
9
O uso de sedoutras ferramentas externas é caro devido ao tempo de inicialização do processo. Eu procurei especialmente pela solução all-bash, porque achei que o uso de substituições do bash era mais do que 3x mais rápido do que chamar sedcada item do meu loop.
usar o seguinte
6
@CiroSantilli granted 事件 法轮功 纳米比亚 威 granted, concedido, essa é a sabedoria comum, mas que não a torna sábia. Sim, o bash é lento, não importa o que aconteça - mas o bash bem escrito que evita sub-conchas é literalmente ordens de magnitude mais rápidas que o bash, que chama ferramentas externas para todas as pequenas tarefas. Além disso, scripts shell bem escritos se beneficiarão de intérpretes mais rápidos (como o ksh93, que tem desempenho parecido com o awk), enquanto os mal escritos, não há nada a ser feito.
Charles Duffy
133

Na verdade, isso pode ser feito no puro bash:

hello=ho02123ware38384you443d34o3434ingtod38384day
re='(.*)[0-9]+(.*)'
while [[ $hello =~ $re ]]; do
  hello=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
echo "$hello"

...rendimentos...

howareyoudoingtodday
Charles Duffy
fonte
2
Algo me diz que você vai adorar estes: stackoverflow.com/questions/5624969/... =)
nickl-
=~É a chave. Mas um pouco desajeitado, dada a reatribuição no loop. A solução @jheddings 2 anos antes é outra boa opção - chamar sed ou perl).
Brent Faust
3
Chamar sedou perlé sensível, se estiver usando cada chamada para processar mais de uma única linha de entrada. Invocar essa ferramenta no interior de um loop, em vez de usar um loop para processar seu fluxo de saída, é imprudente.
Charles Duffy
2
Para sua informação, no zsh, é apenas em $matchvez de $BASH_REMATCH. (Você pode fazê-lo comportar-se como festa com setopt bash_rematch.)
Marian
É estranho - já que o zsh não está tentando ser um shell POSIX, é possível que esteja seguindo a carta de orientação do POSIX sobre variáveis ​​all-caps sendo usadas para fins especificados pelo POSIX (shell ou relevantes para o sistema) e variáveis ​​em minúsculas sendo reservadas para uso do aplicativo. Mas, como zsh é algo que executa aplicativos, e não um aplicativo em si, essa decisão de usar o namespace da variável do aplicativo em vez do namespace do sistema parece terrivelmente perversa.
Charles Duffy
94

Estes exemplos também funcionam no bash, sem a necessidade de usar o sed:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[a-zA-Z]/X} 
echo ${MYVAR//[0-9]/N}

você também pode usar as expressões de colchete da classe de caracteres

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[[:alpha:]]/X} 
echo ${MYVAR//[[:digit:]]/N}

resultado

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

O que o @Lanaru queria saber, no entanto, se entendi a pergunta corretamente, é por que as extensões "completa" ou PCRE \s\S\w\W\d\D etc. não funcionam como suportadas no php ruby ​​python etc. Essas extensões são de expressões regulares compatíveis com Perl (PCRE) e pode não ser compatível com outras formas de expressões regulares baseadas em shell.

Estes não funcionam:

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//\d/}


#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | sed 's/\d//g'

saída com todos os caracteres literais "d" removidos

ho02123ware38384you44334o3434ingto38384ay

mas o seguinte funciona como esperado

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | perl -pe 's/\d//g'

resultado

howareyoudoingtodday

Espero que esclareça um pouco mais as coisas, mas se você ainda não está confuso, por que não tenta isso no Mac OS X, com o sinalizador REG_ENHANCED ativado:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day;
echo $MYVAR | grep -o -E '\d'

Na maioria dos tipos de * nix, você verá apenas a seguinte saída:

d
d
d

nJoy!

nickl-
fonte
6
Perdão? não${foo//$bar/$baz} é a sintaxe POSIX.2 BRE ou ERE - é a correspondência de padrões no estilo fnmatch ().
Charles Duffy
8
... portanto, enquanto o ${hello//[[:digit:]]/}trabalho, se quiséssemos filtrar apenas os dígitos precedidos pela letra o, ${hello//o[[:digit:]]*}teria um comportamento completamente diferente do esperado (já que nos padrões fnmatch, *corresponde a todos os caracteres, em vez de modificar o item imediatamente anterior a ser 0 ou mais).
Charles Duffy
1
Veja pubs.opengroup.org/onlinepubs/9699919799/utilities/… (e tudo o que ele incorpora por referência) para obter as especificações completas do fnmatch.
22868 Charles Duffy
1
man bash: Um operador binário adicional, = ~, está disponível, com a mesma precedência que == e! =. Quando usada, a sequência à direita do operador é considerada uma expressão regular estendida e correspondida de acordo (como na regex (3)).
nickl-
1
@aderchox você está correto, para dígitos que você pode usar [0-9]ou[[:digit:]]
nickl #
13

Se você estiver fazendo chamadas repetidas e se preocupa com o desempenho, este teste revela que o método BASH é aproximadamente 15x mais rápido do que fazer bifurcação para sed e provavelmente qualquer outro processo externo.

hello=123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X

P1=$(date +%s)

for i in {1..10000}
do
   echo $hello | sed s/X//g > /dev/null
done

P2=$(date +%s)
echo $[$P2-$P1]

for i in {1..10000}
do
   echo ${hello//X/} > /dev/null
done

P3=$(date +%s)
echo $[$P3-$P2]
Josiah DeWitt
fonte
1
Se você estiver interessado em forma para reduzir garfos, procurar a palavra newConnector em esta resposta para Como definir uma variável para a saída de um comando em Bash?
F. Hauri
8

Use [[:digit:]](observe os colchetes duplos) como padrão:

$ hello=ho02123ware38384you443d34o3434ingtod38384day
$ echo ${hello//[[:digit:]]/}
howareyoudoingtodday

Só queria resumir as respostas (especialmente os https://stackoverflow.com/a/22261334/2916086 da @ nickl- ).

yegeniy
fonte