bash - extglob 2º operador "zero ou mais" não está funcionando

3

Dado:

$ shopt -s extglob
$ TEST="    z abcdefg";echo ">>${TEST#*( )z*( )}<<"
>> abcdefg<<

Por que existe um espaço antes da letra 'a'? Eu esperaria que o segundo *( ) correspondesse ao espaço, mas não parece ser necessário.

Eu esperava o equivalente a:

$ echo ">>$(echo -n "${TEST}" | perl -pe "s/^ *z *//g")<<"
>>abcdefg<<

O segundo *( ) corresponde se eu especificar o próximo caractere seguinte (que é 'a'):

$ shopt -s extglob
$ TEST="    z abcdefg";echo ">>${TEST#*( )z*( )a}<<"
>>bcdefg<<

Versão do Bash: GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

    
por Hans Deragon 27.06.2017 / 19:29

3 respostas

6

${TEST#...} corresponde à string mais curta, que é z seguida por espaços zero. Você precisa de ${TEST##...} , a correspondência mais longa.

    
por 27.06.2017 / 19:56
5

Isso se deve à interação entre a remoção de substring não-gulosa e o tipo de padrão que você está usando.

No manual do bash ,

?(pattern-list)

Matches zero or one occurrence of the given patterns.

*(pattern-list)

Matches zero or more occurrences of the given patterns.

+(pattern-list)

Matches one or more occurrences of the given patterns.

De minha explicação favorita sobre a manipulação de string do bash,

${string#substring}

Deletes shortest match of $substring from front of $string.

${string##substring}

Deletes longest match of $substring from front of $string.

Então, quando você especifica ${TEST#*( )z*( )} , você está dizendo: "Por favor, apague a subseqüência mais curta que tenha zero ou mais espaços, um z , depois zero ou mais espaços.

A correspondência no último espaço é maior do que a correspondência sem ele, portanto, o último espaço nunca é correspondido.

Para corrigir isso, você pode usar ${TEST##*( )z*( )} para corresponder ao prefixo mais longo que termina com zero ou mais espaços ou ${TEST#*( )z+( )} para corresponder ao prefixo mais curto que termina com um ou mais espaços (que é funcionalmente equivalente a corresponder ao prefixo mais curto terminando com um espaço).

    
por 27.06.2017 / 21:28
1

Como você deseja imitar é um regex (perl), use um regex não um pattern :

$ test="    z abcdefg"
$ regex='^ *z *(.*)'
$ [[ $test =~ $regex ]]
$ echo ">>${BASH_REMATCH[1]}<<"
>>abcdefg<<

A regex é uma regex estendida, não uma PCRE completa.

    
por 28.06.2017 / 04:41