O bash suporta referências anteriores na expansão de parâmetros?

12

Eu tenho uma variável chamada descr , que pode conter uma string Blah: -> r1-ae0-2 / [123] , -> s7-Gi0-0-1:1-US / Foo , etc. Eu quero pegar a parte -> r1-ae0-2 , -> s7-Gi0-0-1:1-US da string. No momento eu uso descr=$(grep -oP '\->\s*\S+' <<< "$descr" para isso. Existe uma maneira melhor de fazer isso? Também é possível fazer isso com a expansão de parâmetros?

    
por Martin 25.04.2017 / 10:46

3 respostas

18

ksh93 e zsh têm referências anteriores (ou mais precisamente 1 , referências a grupos de captura no substituto) suporte dentro de ${var/pattern/replacement} , não bash .

ksh93 :

$ var='Blah: -> r1-ae0-2 / [123]'
$ printf '%s\n' "${var/*@(->*([[:space:]])+([^[:space:]]))*/}"
-> r1-ae0-2

zsh :

$ var='Blah: -> r1-ae0-2 / [123]'
$ set -o extendedglob
$ printf '%s\n' "${var/(#b)*(->[[:space:]]#[^[:space:]]##)*/$match[1]}"
-> r1-ae0-2

( mksh man page também menciona que versões futuras o suportarão com ${KSH_MATCH[1]} para o primeiro grupo de captura. Ainda não disponível a partir de 2017-04-25).

No entanto, com bash , você pode fazer:

$ [[ $var =~ -\>[[:space:]]*[^[:space:]]+ ]] &&
  printf '%s\n' "${BASH_REMATCH[0]}"
-> r1-ae0-2

O que é melhor, pois verifica se o padrão é encontrado primeiro.

Se as expressões regulares de seu sistema suportarem \s / \S , você também poderá fazer:

re='->\s*\S+'
[[ $var =~ $re ]]

Com zsh , você pode obter todo o poder dos PCREs com:

$ set -o rematchpcre
$ [[ $var =~ '->\s*\S+' ]] && printf '%s\n' $MATCH
-> r1-ae0-2

Com zsh -o extendedglob , consulte também:

$ printf '%s\n' ${(SM)var##-\>[[:space:]]#[^[:space:]]##}
-> r1-ae0-2

Portável:

$ expr " $var" : '.*\(->[[:space:]]*[^[:space:]]\{1,\}\)'
-> r1-ae0-2

Se houver várias ocorrências do padrão na string, o comportamento variará com todas essas soluções. No entanto, nenhum deles fornecerá uma lista separada por novas linhas de todas as correspondências, como na sua solução baseada em grep do GNU.

Para fazer isso, você precisa fazer o loop manualmente. Por exemplo, com bash :

re='(->\s*\S+)(.*)'
while [[ $var =~ $re ]]; do
  printf '%s\n' "${BASH_REMATCH[1]}"
  var=${BASH_REMATCH[2]}
done

Com zsh , você pode recorrer a esse tipo de truque para armazenar todas as correspondências em uma matriz:

set -o extendedglob
matches=() n=0
: ${var//(#m)->[[:space:]]#[^[:space:]]##/${matches[++n]::=$MATCH}}
printf '%s\n' $matches
As referências anteriores 1 designam mais comumente um padrão que faz referência ao que foi correspondido por um grupo anterior. Por exemplo, a expressão regular \(.\) basic corresponde a um único caractere seguido pelo mesmo caractere (corresponde a aa , não a ab ). Esse é uma referência anterior ao grupo de captura \(.\) no mesmo padrão.

ksh93 suporta referências anteriores em seus padrões (por exemplo, ls -d -- @(?) listará os nomes de arquivos que consistem em dois caracteres idênticos), não outros shells. BREs e PCREs padrão suportam referências anteriores, mas não ERE padrão, embora algumas implementações ERE o suportem como uma extensão. bash [[ foo =~ re ]] usa EREs.

[[ aa =~ (.) ]]

não coincide, mas

re='(.)'; [[ aa =~ $re ]]

pode se os EREs do sistema o suportarem.

    
por 25.04.2017 / 11:06
9

Você deseja excluir tudo até o primeiro ␣->␣ (sem incluir a "seta") e após o último ␣/ (incluindo o espaço e a barra).

string="Blah: -> r1-ae0-2 / [123]"
string=${string/*->/->}
string=${string/ \/*}

$string será agora -> r1-ae0-2 .

As mesmas duas substituições transformam -> s7-Gi0-0-1:1-US / Foo em -> s7-Gi0-0-1:1-US .

    
por 25.04.2017 / 11:02
3

Respondendo isso definitivamente é impossível sem saber o formato exato que cada mensagem leva. No entanto, como uma abordagem geral, você pode imprimir determinados campos específicos usando cut :

$ cut -d ' ' -f 2 <<< '-> s7-Gi0-0-1:1-US / Foo'
s7-Gi0-0-1:1-US

Ou você pode imprimir cada enésima coluna usando awk :

$ awk -F' ' '{ for (i=2;i<=NF;i+=4) print $i }' <<< '-> r1-ae0-2 / [123], -> s7-Gi0-0-1:1-US / Foo'
r1-ae0-2
s7-Gi0-0-1:1-US
    
por 25.04.2017 / 11:03