
Veo esto en BASH 4.3.48 (SLES12 SP4) y BASH 4.4.23 (OpenSUSE Leap 15.1) cuando intento eliminar múltiples espacios finales del valor de una variable:
~> 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
Siento que cualquiera (1)
de los dos (2)
debería hacer el trabajo.
El manual indica para ${parameter%%word}
:
Elimine el patrón de sufijo coincidente. La palabra se expande para producir un patrón tal como en la expansión del nombre de ruta. Si el patrón coincide con una porción final del valor expandido del parámetro, entonces el resultado de la expansión es el valor expandido del parámetro con el patrón coincidente más corto (el caso ``%'') o el patrón coincidente más largo (el caso ``%''). %'' caso) eliminado.
Como no funciona como está documentado (o como yo entiendo la documentación), sospecho que se trata de un error (el sufijo no coincidente (" -Wall
") se elimina en el caso de " %% *
") en BASH. ¿Estoy en lo cierto?
Respuesta1
En echo "X${xxx%% }X"
, el patrón es un solo espacio: . La parte coincidente más larga para eso es solo eso: un solo espacio. La porción coincidente más corta también es solo eso: un solo espacio.
Para cualquier otra cosa, necesita el operador global *
. Pero eso coincidirá con cualquier cosa, eliminando el archivo -Wall
. Bash globbing no admite directamente un equivalente de la expresión regular a*
. necesitaríasglobo extendido:
$ shopt -s extglob
$ echo "X${xxx%%+( )}X"
X-O -WallX
Respuesta2
Utilice una eliminación de un prefijo dentro de una eliminación de sufijo:
$ xxx="-O -Wall "
$ echo "X${xxx%"${xxx##*[! ]}"}X"
X-O -WallX
- Elimine todo hasta el último carácter que no sea un espacio, dejando solo espacios finales
- Utilice esos espacios como patrón para eliminar el sufijo
- La expansión del parámetro interno debe citarse para evitar que se interprete como un patrón (no es necesario arriba, pero puede ser útil en otros 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
Un ejemplo artificial, pero si no se cita la expansión interna, la expansión externa tratará el asterisco que incluye como un patrón de caparazón. Citado, se convierte literalmente en un asterisco.
El comportamiento que observó no es un error, es simplemente cómo funcionan los patrones de shell simples:
${xxx%% }
- un solo espacio es un solo espacio
- la ocurrencia más larga de un solo espacio es un solo espacio
${xxx%% *}
- aparición más larga de un solo espacio seguido de cualquier cosa/nada
- nada/nada incluirá
-Wall
${xxx% }
- la aparición más corta de un solo espacio es un solo espacio
${xxx% *}
- la aparición más corta de un solo espacio seguido de cualquier cosa/nada es un solo espacio
${xxx%% \*}
\*
es un asterisco de barra invertida y se interpretará como un asterisco literal- no hay espacio seguido de asterisco en la variable, no se elimina ningún sufijo
Respuesta3
read
También puede funcionar (suponiendo que IFS
contenga "espacio"):
xxx="-O -Wall "
read -r xxx <<EOF
$xxx
EOF
echo "X${xxx}X"
Producción:
X-O -WallX
read
divide la entrada en campos segúnIFS
IFS
por defecto es espacio/tabulación/nueva línea, por lo que esto eliminará los espacios iniciales y finales- Funciona en la primera línea de la variable (puede que no sea adecuado para variables multilínea,
bash
podría usarseread -d ''
)
Respuesta4
Una simple expansión de parámetros está bastante limitada en cuanto a los patrones que podría igualar y eliminar. Para eliminar varios caracteres (repetidos) del final de una cadena, la solución habitual es eliminar primero todo lo que estánoel personaje en cuestión ${xxx##*[! ]}
(todos los espacios finales). Luego, como segundo paso, eliminar todo lo que resulte de esa expansión (todos los espacios finales) del final le dará lo que desea (eliminar los espacios finales).
$ xxx="-O -Wall "
$ echo "<${xxx%"${xxx##*[! ]}"}>"
<-O -Wall>
Como alternativa, en bash, puedes usar globbing extendido:
$ shopt -s extglob
$ echo "<${xxx%%+( )}>"
<-O -Wall>
O, también, como alternativa de nivel superior, puedes hacer coincidir lo que quieras con una expresión regular:
$ regex='(.*[^ ]) +$';
$ [[ $xxx =~ $regex ]] && echo "<${BASH_REMATCH[1]}>" || echo "<$xxx>"
<-O -Wall>
O, como guión:
#!/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