
Vejo isso no BASH 4.3.48 (SLES12 SP4) e no BASH 4.4.23 (OpenSUSE Leap 15.1) ao tentar remover vários espaços à direita do valor de uma variável:
~> xxx="-O -Wall "
~> echo "X${xxx%% }X" # (1)
X-O -Wall X
~> echo "X${xxx%% *}X"
X-OX
~> echo "X${xxx% }X"
X-O -Wall X
~> echo "X${xxx% *}X" # (2)
X-O -Wall X
~> echo "X${xxx%% \*}X"
X-O -Wall X
Eu sinto que (1)
ou (2)
deveria fazer o trabalho.
O manual indica para ${parameter%%word}
:
Remova o padrão de sufixo correspondente. A palavra é expandida para produzir um padrão, assim como na expansão do nome do caminho. Se o padrão corresponder a uma parte final do valor expandido do parâmetro, então o resultado da expansão será o valor expandido do parâmetro com o padrão de correspondência mais curto (o caso ``%'') ou o padrão de correspondência mais longo (o ``% %'' caso) excluído.
Como não funciona conforme documentado (ou pelo que entendi a documentação), suspeito que seja um bug (o sufixo não correspondente (" -Wall
") está sendo removido no caso de " %% *
") no BASH. Estou certo?
Responder1
Em echo "X${xxx%% }X"
, o padrão é um espaço único :. A maior parte correspondente para isso é apenas isso: um único espaço. A menor parte correspondente também é apenas isso: um único espaço.
Para mais alguma coisa, você precisa do operador globbing *
. Mas isso corresponderá a qualquer coisa, removendo o -Wall
. O globbing do Bash não suporta diretamente um equivalente da expressão regular a*
. Você precisariaglobulação estendida:
$ shopt -s extglob
$ echo "X${xxx%%+( )}X"
X-O -WallX
Responder2
Use a remoção de um prefixo dentro de uma remoção de sufixo:
$ xxx="-O -Wall "
$ echo "X${xxx%"${xxx##*[! ]}"}X"
X-O -WallX
- Remova tudo até o último caractere que não seja espaço - deixando apenas espaços à direita
- Use esses espaços como padrão para remoção de sufixos
- A expansão do parâmetro interno deve ser citada para evitar que seja interpretada como um padrão (não necessário acima, mas pode ser útil em outros casos):
$ bash -c 'xxx="-O -Wall* "; echo "X${xxx%%"${xxx##*[! *]}"}X"'
X-O -WallX
$ bash -c 'xxx="-O -Wall* "; echo "X${xxx%%${xxx##*[! *]}}X"'
XX
Um exemplo inventado, mas se a expansão interna não for citada, o asterisco que ela inclui será tratado como um padrão de casca pela expansão externa. Citado, torna-se um asterisco literal.
O comportamento que você observou não é um bug, é apenas como funcionam os padrões simples do shell:
${xxx%% }
- um único espaço é um único espaço
- a ocorrência mais longa de um único espaço é um único espaço
${xxx%% *}
- ocorrência mais longa de um único espaço seguido por qualquer coisa/nada
- qualquer coisa/nada incluirá
-Wall
${xxx% }
- a menor ocorrência de um único espaço é um único espaço
${xxx% *}
- a ocorrência mais curta de um único espaço seguido por qualquer coisa/nada é um espaço único
${xxx%% \*}
\*
é um asterisco com escape de barra invertida e será interpretado como um asterisco literal- não há espaço seguido de asterisco na variável, nenhum sufixo é removido
Responder3
read
também pode funcionar (assumindo que IFS
contém "espaço"):
xxx="-O -Wall "
read -r xxx <<EOF
$xxx
EOF
echo "X${xxx}X"
Saída:
X-O -WallX
read
divide a entrada em campos de acordo comIFS
IFS
por padrão é espaço/tab/nova linha, então isso removerá quaisquer espaços iniciais e finais- Funciona na primeira linha da variável (pode não ser adequado para vars multilinhas,
bash
poderia usarread -d ''
)
Responder4
Uma simples expansão de parâmetro é bastante limitada nos padrões que pode corresponder e remover. Para remover vários caracteres (repetidos) do final de uma string, a solução usual é primeiro remover tudo o que estánãoo caractere em questão ${xxx##*[! ]}
(todos os espaços à direita). Então, como segunda etapa, remover tudo o que resulta dessa expansão (todos os espaços à direita) do final lhe dará o que você deseja (remover espaços à direita).
$ xxx="-O -Wall "
$ echo "<${xxx%"${xxx##*[! ]}"}>"
<-O -Wall>
Como alternativa, no bash, você poderia usar globbing estendido:
$ shopt -s extglob
$ echo "<${xxx%%+( )}>"
<-O -Wall>
Ou, também, como alternativa de nível superior, você pode combinar o que quiser com uma regex:
$ regex='(.*[^ ]) +$';
$ [[ $xxx =~ $regex ]] && echo "<${BASH_REMATCH[1]}>" || echo "<$xxx>"
<-O -Wall>
Ou, como um script:
#!/bin/bash
xxx=${1:-"-O -Wall "}
regex='(.*[^ ]) +$'
if [[ $xxx =~ $regex ]] # if there are trailing spaces
then
echo "<${BASH_REMATCH[1]}>" # Print the string without spaces
else
echo "<$xxx>" # if there are no trailing spaces.
fi