¿Cómo recortar espacios en blanco de una variable Bash?


923

Tengo un script de shell con este código:

var=`hg st -R "$path"`
if [ -n "$var" ]; then
    echo $var
fi

Pero el código condicional siempre se ejecuta, porque hg stsiempre imprime al menos un carácter de nueva línea.

  • ¿Hay una manera simple de eliminar espacios en blanco $var(como trim()en PHP )?

o

  • ¿Existe una forma estándar de tratar este problema?

Podría usar sed o AWK , pero me gustaría pensar que hay una solución más elegante para este problema.


3
Relacionado, si desea recortar espacio en un entero y simplemente obtener el entero, ajuste con $ (($ var)), e incluso puede hacerlo cuando está entre comillas dobles. Esto se volvió importante cuando usé la declaración de fecha y con los nombres de archivo.
Volomike

"¿Existe una forma estándar de tratar este problema?" Sí, use [[en lugar de [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly

Si ayuda, al menos donde lo estoy probando en Ubuntu 16.04. Utilizando los siguientes partidos recortar en todos los sentidos: echo " This is a string of char " | xargs. Si por el contrario tiene una comilla simple en el texto se puede hacer lo siguiente: echo " This i's a string of char " | xargs -0. Tenga en cuenta que menciono el último de xargs (4.6.0)
Luis Alvarado

La condición no es cierta debido a una nueva línea, ya que los backticks se tragan la última línea. Esto no imprimirá nada test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, sin embargo, esto lo hará test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi, por lo que debe haber más que una nueva línea al final.
Mecki

A = "123 4 5 6"; B = echo $A | sed -r 's/( )+//g';
bruziuz

Respuestas:


1023

Definamos una variable que contenga espacios en blanco iniciales, finales e intermedios:

FOO=' test test test '
echo -e "FOO='${FOO}'"
# > FOO=' test test test '
echo -e "length(FOO)==${#FOO}"
# > length(FOO)==16

Cómo eliminar todos los espacios en blanco (denotado por [:space:]in tr):

FOO=' test test test '
FOO_NO_WHITESPACE="$(echo -e "${FOO}" | tr -d '[:space:]')"
echo -e "FOO_NO_WHITESPACE='${FOO_NO_WHITESPACE}'"
# > FOO_NO_WHITESPACE='testtesttest'
echo -e "length(FOO_NO_WHITESPACE)==${#FOO_NO_WHITESPACE}"
# > length(FOO_NO_WHITESPACE)==12

Cómo eliminar solo los espacios en blanco iniciales:

FOO=' test test test '
FOO_NO_LEAD_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//')"
echo -e "FOO_NO_LEAD_SPACE='${FOO_NO_LEAD_SPACE}'"
# > FOO_NO_LEAD_SPACE='test test test '
echo -e "length(FOO_NO_LEAD_SPACE)==${#FOO_NO_LEAD_SPACE}"
# > length(FOO_NO_LEAD_SPACE)==15

Cómo eliminar solo los espacios en blanco finales:

FOO=' test test test '
FOO_NO_TRAIL_SPACE="$(echo -e "${FOO}" | sed -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_TRAIL_SPACE='${FOO_NO_TRAIL_SPACE}'"
# > FOO_NO_TRAIL_SPACE=' test test test'
echo -e "length(FOO_NO_TRAIL_SPACE)==${#FOO_NO_TRAIL_SPACE}"
# > length(FOO_NO_TRAIL_SPACE)==15

Cómo eliminar los espacios iniciales y finales: encadene los seds:

FOO=' test test test '
FOO_NO_EXTERNAL_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_EXTERNAL_SPACE='${FOO_NO_EXTERNAL_SPACE}'"
# > FOO_NO_EXTERNAL_SPACE='test test test'
echo -e "length(FOO_NO_EXTERNAL_SPACE)==${#FOO_NO_EXTERNAL_SPACE}"
# > length(FOO_NO_EXTERNAL_SPACE)==14

Alternativamente, si su golpe lo admite, puede reemplazar echo -e "${FOO}" | sed ...con sed ... <<<${FOO}, al igual que (por espacios en blanco):

FOO_NO_TRAIL_SPACE="$(sed -e 's/[[:space:]]*$//' <<<${FOO})"

63
Para generalizar la solución para manejar todas las formas de espacios en blanco, reemplace el carácter de espacio en los comandos try sedcon [[:space:]]. Tenga en cuenta que el sedenfoque solo funcionará en la entrada de una sola línea . Para los enfoques que funcionan con entrada de varias líneas y también usan las funciones integradas de bash, vea las respuestas de @bashfu y @GuruM. Una versión generalizada en línea de la solución de @Nicholas Sushkin se vería así: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0

77
Si lo hace a menudo, agregar alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"a su le ~/.profilepermite usar echo $SOMEVAR | trimy cat somefile | trim.
instancia de mí

Escribí una sedsolución que sólo se utiliza una única expresión en lugar de dos: sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Recorta los espacios en blanco iniciales y finales, y captura cualquier secuencia separada por espacios en blanco de caracteres que no sean espacios en blanco en el medio. ¡Disfrutar!
Victor Zamanian

@VictorZamanian Su solución no funciona si la entrada contiene solo espacios en blanco. Las soluciones sed de dos patrones proporcionadas por MattyV y la instancia de mí funcionan bien con solo entrada de espacios en blanco.
Torben

@Torben Fair point. Supongo que la expresión única podría hacerse condicional, con el |fin de mantenerla como una sola expresión, en lugar de varias.
Victor Zamanian

966

Una respuesta simple es:

echo "   lol  " | xargs

Xargs hará el recorte por ti. Es un comando / programa, sin parámetros, devuelve la cadena recortada, ¡así de fácil!

Nota: esto no elimina todos los espacios internos, por lo que "foo bar"permanece igual; NO se hace "foobar". Sin embargo, múltiples espacios se condensarán en espacios individuales, por lo que "foo bar"se convertirán "foo bar". Además, no elimina los caracteres de final de línea.


27
Agradable. Esto funciona muy bien He decidido canalizarlo xargs echosolo para ser detallado sobre lo que estoy haciendo, pero xargs por sí solo usará echo por defecto.
Será

24
Buen truco, pero tenga cuidado, puede usarlo para una cadena de una línea pero, por diseño de xargs, no solo recortará con contenido canalizado de varias líneas. sed es tu amigo entonces.
Jocelyn delalande

22
El único problema con xargs es que introducirá una nueva línea, si desea mantenerla desactivada, le recomendaría sed 's/ *$//'como alternativa. Se puede ver la xargsnueva línea como esta: echo -n "hey thiss " | xargs | hexdump se dará cuenta de 0a73la aes la nueva línea. Si haces lo mismo con sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpverás 0073, no hay nueva línea.

8
Cuidado; esto se romperá si la cadena a xargs contiene espacios sobrantes en el medio. Como "este es un argumento". Los xargs se dividirán en cuatro.
bos

64
Esto es malo. 1. Se convertirá a<space><space>ben a<space>b. 2. Aún más: se convertirá a"b"c'd'een abcde. 3. Aún más: fallará a"b, etc.
Sasha

359

Hay una solución que solo usa los componentes integrados de Bash llamados comodines :

var="    abc    "
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"   
printf '%s' "===$var==="

Aquí está lo mismo envuelto en una función:

trim() {
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"   
    printf '%s' "$var"
}

Pasa la cadena que se recortará en forma de comillas. p.ej:

trim "   abc   "

Lo bueno de esta solución es que funcionará con cualquier shell compatible con POSIX.

Referencia


18
¡Inteligente! Esta es mi solución favorita, ya que utiliza la funcionalidad bash incorporada. ¡Gracias por publicar! @San, son dos recortes de cadena anidados. Por ejemplo, s=" 1 2 3 "; echo \""${s%1 2 3 }"\"recortaría todo desde el final, devolviendo la delantera " ". Subbing 1 2 3 with [![:space:]]*le dice que "busque el primer personaje no espacial, luego aplíquelo y todo lo que sigue". Usar en %%lugar de %hace que la operación de recorte desde el final sea codiciosa. Esto está anidado en un recorte no codicioso desde el principio, por lo que, en efecto, recorta " "desde el principio. Luego, cambie%, # y * por los espacios finales. Bam!
Mark G.

2
No he encontrado ningún efecto secundario no deseado, y el código principal también funciona con otros shells similares a POSIX. Sin embargo, en Solaris 10, no funciona con /bin/sh(solo con /usr/xpg4/bin/sh, pero esto no es lo que se usará con los scripts sh habituales).
vinc17

99
Una solución mucho mejor que usar sed, tr, etc., ya que es mucho más rápido y evita cualquier bifurcación (). En Cygwin, la diferencia de velocidad es de órdenes de magnitud.
Gene Pavlovsky

99
@San Al principio estaba perplejo porque pensaba que estas eran expresiones regulares. No son. Más bien, esta es la sintaxis de coincidencia de patrones ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ) utilizada en la eliminación de subcadenas ( tldp.org/LDP/abs /html/string-manipulation.html ). Entonces ${var%%[![:space:]]*}dice "eliminar de varsu subcadena más larga que comienza con un carácter no espacial". Eso significa que solo te quedan los espacios iniciales, que luego eliminas ${var#... La siguiente línea (final) es lo contrario.
Ohad Schneider

8
Esta es abrumadoramente la solución ideal. Bifurcan uno o más procesos externos (por ejemplo, awk, sed, tr, xargs) sólo para los espacios en blanco del ajuste de una sola cadena es fundamentalmente loco - sobre todo cuando la mayoría de las conchas (incluyendo bash) ya proporcionar cadena nativa instalaciones munging fuera de la caja.
Cecil Curry

81

Bash tiene una característica llamada expansión de parámetros , que, entre otras cosas, permite el reemplazo de cadenas basado en los llamados patrones (los patrones se parecen a expresiones regulares, pero existen diferencias y limitaciones fundamentales). [Línea original de flussence: Bash tiene expresiones regulares, pero están bien ocultas:]

A continuación se muestra cómo eliminar todo el espacio en blanco (incluso desde el interior) de un valor variable.

$ var='abc def'
$ echo "$var"
abc def
# Note: flussence's original expression was "${var/ /}", which only replaced the *first* space char., wherever it appeared.
$ echo -n "${var//[[:space:]]/}"
abcdef

2
O más bien, funciona para espacios en el medio de una var, pero no cuando intento anclarlo al final.
Paul Tomblin

¿Ayuda esto? Desde la página de manual: "$ {parámetro / patrón / cadena} [...] Si el patrón comienza con%, debe coincidir al final del valor expandido del parámetro".

@Ant, ¿entonces no son expresiones regulares, sino algo similar?
Paul Tomblin

3
Son expresiones regulares, solo un dialecto extraño.

13
${var/ /}elimina el primer caracter de espacio. ${var// /}elimina todos los caracteres de espacio. No hay forma de recortar solo los espacios en blanco iniciales y finales con solo esta construcción.
Gilles 'SO- deja de ser malvado'

60

Para eliminar todos los espacios desde el principio y el final de una cadena (incluidos los caracteres de final de línea):

echo $variable | xargs echo -n

Esto eliminará espacios duplicados también:

echo "  this string has a lot       of spaces " | xargs echo -n

Produce: 'esta cadena tiene muchos espacios'


55
Básicamente, el xargs elimina todos los delimitadores de la cadena. De manera predeterminada, utiliza el espacio como delimitador (esto podría cambiarse con la opción -d).
rkachach

44
Esta es, con mucho, la solución más limpia (tanto corta como legible).
Potherca

¿Por qué lo necesitas echo -nen absoluto? echo " my string " | xargsTiene la misma salida.
bfontaine

echo -n también elimina el final de línea
rkachach

55

Pele un espacio inicial y uno posterior

trim()
{
    local trimmed="$1"

    # Strip leading space.
    trimmed="${trimmed## }"
    # Strip trailing space.
    trimmed="${trimmed%% }"

    echo "$trimmed"
}

Por ejemplo:

test1="$(trim " one leading")"
test2="$(trim "one trailing ")"
test3="$(trim " one leading and one trailing ")"
echo "'$test1', '$test2', '$test3'"

Salida:

'one leading', 'one trailing', 'one leading and one trailing'

Desnuda todo los espacios iniciales y finales

trim()
{
    local trimmed="$1"

    # Strip leading spaces.
    while [[ $trimmed == ' '* ]]; do
       trimmed="${trimmed## }"
    done
    # Strip trailing spaces.
    while [[ $trimmed == *' ' ]]; do
        trimmed="${trimmed%% }"
    done

    echo "$trimmed"
}

Por ejemplo:

test4="$(trim "  two leading")"
test5="$(trim "two trailing  ")"
test6="$(trim "  two leading and two trailing  ")"
echo "'$test4', '$test5', '$test6'"

Salida:

'two leading', 'two trailing', 'two leading and two trailing'

99
Esto recortará solo 1 carácter de espacio. Así que el eco resulta'hello world ', 'foo bar', 'both sides '
Joe

@ Joe agregué una mejor opción.
wjandrea

42

De la sección Bash Guide sobre globbing

Para usar un extglob en una expansión de parámetros

 #Turn on extended globbing  
shopt -s extglob  
 #Trim leading and trailing whitespace from a variable  
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])}  
 #Turn off extended globbing  
shopt -u extglob  

Aquí está la misma funcionalidad envuelta en una función (NOTA: es necesario citar la cadena de entrada pasada a la función):

trim() {
    # Determine if 'extglob' is currently on.
    local extglobWasOff=1
    shopt extglob >/dev/null && extglobWasOff=0 
    (( extglobWasOff )) && shopt -s extglob # Turn 'extglob' on, if currently turned off.
    # Trim leading and trailing whitespace
    local var=$1
    var=${var##+([[:space:]])}
    var=${var%%+([[:space:]])}
    (( extglobWasOff )) && shopt -u extglob # If 'extglob' was off before, turn it back off.
    echo -n "$var"  # Output trimmed string.
}

Uso:

string="   abc def ghi  ";
#need to quote input-string to preserve internal white-space if any
trimmed=$(trim "$string");  
echo "$trimmed";

Si modificamos la función para ejecutarla en un subshell, no tenemos que preocuparnos por examinar la opción de shell actual para extglob, solo podemos configurarla sin afectar el shell actual. Esto simplifica enormemente la función. También actualizo los parámetros posicionales "en su lugar" para que ni siquiera necesite una variable local

trim() {
    shopt -s extglob
    set -- "${1##+([[:space:]])}"
    printf "%s" "${1%%+([[:space:]])}" 
}

entonces:

$ s=$'\t\n \r\tfoo  '
$ shopt -u extglob
$ shopt extglob
extglob         off
$ printf ">%q<\n" "$s" "$(trim "$s")"
>$'\t\n \r\tfoo  '<
>foo<
$ shopt extglob
extglob         off

2
como ha observado, trim () elimina solo los espacios en blanco iniciales y finales.
GuruM

Como mkelement ya ha señalado, debe pasar el parámetro de función como una cadena entre comillas, es decir, $ (trim "$ string") en lugar de $ (trim $ string). He actualizado el código para mostrar el uso correcto. Gracias.
GuruM

Por mucho que me agrada saber acerca de las opciones de shell, no creo que el resultado final es más elegante que simplemente haciendo sustituciones de patrones 2
sehe

Tenga en cuenta que (con una versión lo suficientemente reciente de Bash?), Puede simplificar el mecanismo para restaurar la opción extglob, utilizando shopt -p: simplemente escriba local restore="$(shopt -p extglob)" ; shopt -s extglobal principio de su función y eval "$restore"al final (excepto, concedido, eval es malo ...).
Maëlan

¡Gran solución! Una mejora potencial: parece que [[:space:]]podría reemplazarse con, bueno, un espacio: ${var##+( )}y ${var%%+( )}funciona bien y son más fáciles de leer.
DKroot

40

Puede recortar simplemente con echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Esto colapsa múltiples espacios contiguos en uno.
Evgeni Sergeev

77
¿Lo probaste cuando foocontiene un comodín? por ejemplo, foo=" I * have a wild card"... sorpresa! Además, esto colapsa varios espacios contiguos en uno.
gniourf_gniourf

55
Esta es una excelente solución si: 1. no desea espacios en los extremos 2. desea solo un espacio entre cada palabra 3. está trabajando con una entrada controlada sin comodines. Básicamente, convierte una lista mal formateada en una buena.
musicin3d

Buen recordatorio de los comodines @gniourf_gniourf +1. Sigue siendo una excelente solución, Vamp. +1 a ti también.
Dr Beco

25

Siempre lo he hecho con sed

  var=`hg st -R "$path" | sed -e 's/  *$//'`

Si hay una solución más elegante, espero que alguien la publique.


¿podría explicar la sintaxis sed?
farid99

2
La expresión regular coincide con todos los espacios en blanco finales y los reemplaza por nada.
Paul Tomblin

44
¿Qué hay de liderar espacios en blanco?
Qian Chen

Esto elimina todos los espacios en blanco finales sed -e 's/\s*$//'. Explicación: 's' significa búsqueda, el '\ s' significa todos los espacios en blanco, el '*' significa cero o muchos, el '$' significa hasta el final de la línea y '//' significa sustituir todas las coincidencias con una cadena vacía .
Craig

En 's / * $ //', ¿por qué hay 2 espacios antes del asterisco, en lugar de un solo espacio? ¿Es eso un error tipográfico?
Brent212

24

Puede eliminar nuevas líneas con tr :

var=`hg st -R "$path" | tr -d '\n'`
if [ -n $var ]; then
    echo $var
done

8
No quiero eliminar '\ n' del medio de la cadena, solo desde el principio o el final.
demasiado php

24

Con las características de coincidencia de patrones extendidos de Bash habilitadas (shopt -s extglob ), puede usar esto:

{trimmed##*( )}

para eliminar una cantidad arbitraria de espacios iniciales.


¡Estupendo! Creo que esta es la solución más ligera y elegante.
dubiousjim

1
Consulte la publicación de @ GuruM a continuación para obtener una solución similar, pero más genérica, que (a) trata con todas las formas de espacios en blanco y (b) también maneja los espacios en blanco finales .
mklement0

@mkelement +1 por tomarse la molestia de reescribir mi fragmento de código como una función. Gracias
GuruM

Funciona con el valor predeterminado / bin / ksh de OpenBSD también. /bin/sh -o posixfunciona también pero sospecho.
Clint Pachl

No es un mago bash aquí; lo que es trimmed? ¿Es una cosa incorporada o la variable que se está recortando?
Abhijit Sarkar

19
# Trim whitespace from both ends of specified parameter

trim () {
    read -rd '' $1 <<<"${!1}"
}

# Unit test for trim()

test_trim () {
    local foo="$1"
    trim foo
    test "$foo" = "$2"
}

test_trim hey hey &&
test_trim '  hey' hey &&
test_trim 'ho  ' ho &&
test_trim 'hey ho' 'hey ho' &&
test_trim '  hey  ho  ' 'hey  ho' &&
test_trim $'\n\n\t hey\n\t ho \t\n' $'hey\n\t ho' &&
test_trim $'\n' '' &&
test_trim '\n' '\n' &&
echo passed

2
¡Asombroso! Simple y efectivo! Claramente mi solución favorita. ¡Gracias!
xebeche

1
@CraigMcQueen es el valor de la variable, ya readque almacenará en la variable por su nombre $ 1 una versión recortada de su valor $ {! 1}
Aquarius Power

2
El parámetro de la función trim () es un nombre de variable: vea la llamada a trim () dentro de test_trim (). Dentro de trim () como se llama desde test_trim (), $ 1 se expande a foo y $ {! 1} se expande a $ foo (es decir, al contenido actual de la variable foo). Busque en el manual de bash 'indirección variable'.
flabdablet

1
¿Qué tal esta pequeña modificación, para admitir el recorte de varios vars en una sola llamada? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky

2
@AquariusPower no hay necesidad de usar echo en un subshell para la versión de una sola línea, solo read -rd '' str <<<"$str"servirá.
flabdablet

12

Hay muchas respuestas, pero todavía creo que vale la pena mencionar mi guión recién escrito porque:

  • fue probado con éxito en el shell bash / dash / busybox
  • es extremadamente pequeño
  • no depende de comandos externos y no necesita bifurcar (-> uso de recursos rápido y bajo)
  • Funciona como se esperaba:
    • lo despoja todo espacios y pestañas de principio a fin, pero no más
    • importante: no elimina nada del medio de la cadena (muchas otras respuestas lo hacen), incluso las nuevas líneas permanecerán
    • especial: "$*"une múltiples argumentos usando un espacio. si desea recortar y generar solo el primer argumento, use "$1"en su lugar
    • si no tiene ningún problema con patrones de nombre de archivo coincidentes, etc.

La secuencia de comandos:

trim() {
  local s2 s="$*"
  until s2="${s#[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  until s2="${s%[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  echo "$s"
}

Uso:

mystring="   here     is
    something    "
mystring=$(trim "$mystring")
echo ">$mystring<"

Salida:

>here     is
    something<

¡Bah en C esto sería mucho más sencillo de implementar!
Nils

Por supuesto. Desafortunadamente, esto no es C y a veces desea evitar llamar a herramientas externas
Daniel Alder

Para hacer que el código sea más legible y compatible con copiar, puede cambiar los corchetes a caracteres escapados:[\ \t]
leondepeon

@leondepeon, ¿intentaste esto? Lo intenté cuando lo escribí y lo intenté nuevamente, y su sugerencia no funciona en ninguno de bash, dash, busybox
Daniel Alder

@DanielAlder Lo hice, pero como ya es hace 3 años, no puedo encontrar el código donde lo usé. Ahora, sin embargo, probablemente usaría [[:space:]]como en una de las otras respuestas: stackoverflow.com/a/3352015/3968618
leondepeon

11

Puedes usar la vieja escuela tr. Por ejemplo, esto devuelve el número de archivos modificados en un repositorio de git, espacios en blanco despojados.

MYVAR=`git ls-files -m|wc -l|tr -d ' '`

1
Esto no recorta el espacio en blanco desde el frente y la parte posterior; elimina todo el espacio en blanco de la cadena.
Nick

11

Esto funcionó para mí:

text="   trim my edges    "

trimmed=$text
trimmed=${trimmed##+( )} #Remove longest matching series of spaces from the front
trimmed=${trimmed%%+( )} #Remove longest matching series of spaces from the back

echo "<$trimmed>" #Adding angle braces just to make it easier to confirm that all spaces are removed

#Result
<trim my edges>

Para poner eso en menos líneas para el mismo resultado:

text="    trim my edges    "
trimmed=${${text##+( )}%%+( )}

1
No funciono para mi. El primero imprimió una cadena sin recortar. El segundo lanzó mala sustitución. ¿Puedes explicar lo que está pasando aquí?
musicin3d

1
@ musicin3d: este es un sitio que uso con frecuencia que explica cómo funciona la manipulación de variables en la búsqueda de bash para ${var##Pattern}obtener más detalles. Además, este sitio explica los patrones de bash . Entonces, los ##medios eliminan el patrón dado de la parte delantera y los %%medios eliminan el patrón dado de la parte posterior. La +( )porción es el patrón y significa "una o más ocurrencias de un espacio"
gMale

Es curioso, funcionó en el indicador, pero no después de la transposición al archivo de script bash.
Dr Beco

extraño. ¿Es la misma versión bash en ambos casos?
gMale

11
# Strip leading and trailing white space (new line inclusive).
trim(){
    [[ "$1" =~ [^[:space:]](.*[^[:space:]])? ]]
    printf "%s" "$BASH_REMATCH"
}

O

# Strip leading white space (new line inclusive).
ltrim(){
    [[ "$1" =~ [^[:space:]].* ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    [[ "$1" =~ .*[^[:space:]] ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

O

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

O

# Strip leading specified characters.  ex: str=$(ltrim "$str" $'\n a')
ltrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"]) ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip trailing specified characters.  ex: str=$(rtrim "$str" $'\n a')
rtrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1" "$2")" "$2")"
}

O

Sobre la base de la expritución de moskit ...

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$"`"
}

O

# Strip leading white space (new line inclusive).
ltrim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)"`"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    printf "%s" "`expr "$1" : "^\(.*[^[:space:]]\)[[:space:]]*$"`"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

8

He visto scripts que solo usan asignación de variables para hacer el trabajo:

$ xyz=`echo -e 'foo \n bar'`
$ echo $xyz
foo bar

El espacio en blanco se fusiona y recorta automáticamente. Hay que tener cuidado con los metacaracteres de shell (riesgo potencial de inyección).

También recomendaría siempre las sustituciones variables de comillas dobles en condicionales de shell:

if [ -n "$var" ]; then

ya que algo como un -o u otro contenido en la variable podría modificar sus argumentos de prueba.


3
Es el uso sin comillas de $xyzwith lo echoque hace la fusión de espacios en blanco, no la asignación de variables. Para almacenar el valor recortado en la variable en su ejemplo, tendría que usar xyz=$(echo -n $xyz). Además, este enfoque está sujeto a la expansión de nombre de ruta potencialmente no deseada (globbing).
mklement0

esto es incorrecto, el valor en la xyzvariable NO se recorta.
caesarsol

7
var='   a b c   '
trimmed=$(echo $var)

1
Eso no funcionará si hay más de un espacio entre dos palabras. Prueba: echo $(echo "1 2 3")(con dos espacios entre 1, 2 y 3).
joshlf

7

Simplemente usaría sed:

function trim
{
    echo "$1" | sed -n '1h;1!H;${;g;s/^[ \t]*//g;s/[ \t]*$//g;p;}'
}

a) Ejemplo de uso en cadena de una sola línea

string='    wordA wordB  wordC   wordD    '
trimmed=$( trim "$string" )

echo "GIVEN STRING: |$string|"
echo "TRIMMED STRING: |$trimmed|"

Salida:

GIVEN STRING: |    wordA wordB  wordC   wordD    |
TRIMMED STRING: |wordA wordB  wordC   wordD|

b) Ejemplo de uso en cadenas de líneas múltiples

string='    wordA
   >wordB<
wordC    '
trimmed=$( trim "$string" )

echo -e "GIVEN STRING: |$string|\n"
echo "TRIMMED STRING: |$trimmed|"

Salida:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Nota final:
si no le gusta usar una función, para una cadena de una sola línea puede simplemente usar un comando "más fácil de recordar" como:

echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Ejemplo:

echo "   wordA wordB wordC   " | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Salida:

wordA wordB wordC

Usar lo anterior en cadenas de varias líneas también funcionará , pero tenga en cuenta que también cortará cualquier espacio múltiple interno posterior / principal, como GuruM notó en los comentarios

string='    wordAA
    >four spaces before<
 >one space before<    '
echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Salida:

wordAA
>four spaces before<
>one space before<

Entonces, si le importa mantener esos espacios, ¡use la función al comienzo de mi respuesta!

d) EXPLICACIÓN de la sintaxis sed "buscar y reemplazar" en cadenas de líneas múltiples utilizadas dentro de la función trim:

sed -n '
# If the first line, copy the pattern to the hold buffer
1h
# If not the first line, then append the pattern to the hold buffer
1!H
# If the last line then ...
$ {
    # Copy from the hold to the pattern buffer
    g
    # Do the search and replace
    s/^[ \t]*//g
    s/[ \t]*$//g
    # print
    p
}'

Nota: Como lo sugiere @mkelement, no funcionará para cadenas de varias líneas, aunque debería funcionar para cadenas de una sola línea.
GuruM

1
Estás equivocado: también funciona en cadenas de varias líneas. ¡Solo
pruébalo

+1 para el uso: me facilitó probar el código. Sin embargo, el código aún no funcionará para cadenas de varias líneas. Si observa cuidadosamente la salida, notará que también se eliminan los espacios internos iniciales / finales, por ejemplo, el espacio frente a "multilínea" se reemplaza por "multilínea". Simplemente intente aumentar el número de espacios iniciales / finales en cada línea.
GuruM

¡Ahora veo a qué te refieres! Gracias por el aviso, edité mi respuesta.
Luca Borrione

@ "Luca Borrione" - bienvenido :-) ¿Podría explicar la sintaxis sed que está usando en trim ()? También podría ayudar a cualquier usuario de su código a modificarlo para otros usos. También podría incluso ayudar a encontrar casos extremos para la expresión regular.
GuruM

6

Aquí hay una función trim () que recorta y normaliza los espacios en blanco

#!/bin/bash
function trim {
    echo $*
}

echo "'$(trim "  one   two    three  ")'"
# 'one two three'

Y otra variante que usa expresiones regulares.

#!/bin/bash
function trim {
    local trimmed="$@"
    if [[ "$trimmed" =~ " *([^ ].*[^ ]) *" ]]
    then 
        trimmed=${BASH_REMATCH[1]}
    fi
    echo "$trimmed"
}

echo "'$(trim "  one   two    three  ")'"
# 'one   two    three'

El primer enfoque es complicado, ya que no solo normaliza el espacio en blanco interior (reemplaza todos los espacios interiores del espacio en blanco con un solo espacio cada uno), sino que también está sujeto a glob (expansión de nombre de ruta) de modo que, por ejemplo, un *carácter en la cadena de entrada expandirse a todos los archivos y carpetas en la carpeta de trabajo actual. Finalmente, si $ IFS se establece en un valor no predeterminado, el recorte puede no funcionar (aunque eso se soluciona fácilmente agregando local IFS=$' \t\n'). El recorte se limita a las siguientes formas de espacios en blanco: espacios \ty \ncaracteres.
mklement0

1
El segundo enfoque basado en expresiones regulares es excelente y no tiene efectos secundarios, pero en su forma actual es problemático: (a) en bash v3.2 +, la coincidencia NO funcionará por defecto, porque la expresión regular debe estar sin comillas en orden para trabajar y (b) la expresión regular en sí misma no maneja el caso en el que la cadena de entrada es un solo carácter, sin espacio, rodeado de espacios. Para solucionar estos problemas, reemplace la iflínea con: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Finalmente, el enfoque solo trata con espacios, no con otras formas de espacios en blanco (ver mi próximo comentario).
mklement0

2
La función que utiliza expresiones regulares solo trata con espacios y no otras formas de espacios en blanco, pero es fácil de generalizar: Reemplace la iflínea con:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0

6

Use AWK:

echo $var | awk '{gsub(/^ +| +$/,"")}1'

Dulce que parece funcionar (ex :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1''`
rogerdpack

44
excepto que awk no está haciendo nada: hacer eco de una variable sin comillas ya ha eliminado los espacios en blanco
Glenn Jackman

6

Las asignaciones ignoran los espacios en blanco iniciales y finales y, como tales, se pueden usar para recortar:

$ var=`echo '   hello'`; echo $var
hello

8
Eso no es cierto. Es el "eco" que elimina los espacios en blanco, no la asignación. En su ejemplo, haga echo "$var"para ver el valor con espacios.
Nicholas Sushkin

2
@NicholasSushkin One podría hacerlo var=$(echo $var)pero no lo recomiendo. Se prefieren otras soluciones presentadas aquí.
xebeche

5

Esto no tiene el problema con el globbing no deseado, también, el espacio en blanco interior no se modifica (suponiendo que $IFSesté establecido en el valor predeterminado, que es' \t\n' ).

Se lee hasta la primera línea nueva (y no la incluye) o el final de la cadena, lo que ocurra primero, y elimina cualquier combinación de \tcaracteres y espacios iniciales y finales . Si desea conservar varias líneas (y también eliminar las nuevas líneas iniciales y finales), use read -r -d '' var << eofen su lugar; tenga en cuenta, sin embargo, que si su entrada contiene \neof, se cortará justo antes. (Otras formas de espacio en blanco, es decir \r, \fy \v, se no despojado, incluso si se agregan a $ IFS).

read -r var << eof
$var
eof


5

Esto eliminará todos los espacios en blanco de su cadena,

 VAR2="${VAR2//[[:space:]]/}"

/reemplaza la primera aparición y //todas las ocurrencias de espacios en blanco en la cadena. Es decir, todos los espacios en blanco se reemplazan por nada


4

Este es el método más simple que he visto. Solo usa Bash, solo unas pocas líneas, la expresión regular es simple y coincide con todas las formas de espacios en blanco:

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

Aquí hay un script de muestra para probarlo:

test=$(echo -e "\n \t Spaces and tabs and newlines be gone! \t  \n ")

echo "Let's see if this works:"
echo
echo "----------"
echo -e "Testing:${test} :Tested"  # Ugh!
echo "----------"
echo
echo "Ugh!  Let's fix that..."

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

echo
echo "----------"
echo -e "Testing:${test}:Tested"  # "Testing:Spaces and tabs and newlines be gone!"
echo "----------"
echo
echo "Ah, much better."

1
Seguramente preferible a, por ejemplo (¡dioses!), Bombardear Python. Excepto que creo que es más simple y más general manejar correctamente una cadena que contiene solo espacios. La expresión ligeramente simplificada sería:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk

4

Python tiene una función strip()que funciona de manera idéntica a la de PHP trim(), por lo que podemos hacer un poco de Python en línea para hacer una utilidad fácilmente comprensible para esto:

alias trim='python -c "import sys; sys.stdout.write(sys.stdin.read().strip())"'

Esto recortará los espacios en blanco iniciales y finales (incluidas las nuevas líneas).

$ x=`echo -e "\n\t   \n" | trim`
$ if [ -z "$x" ]; then echo hi; fi
hi

Si bien eso funciona, es posible que desee considerar ofrecer una solución que no implique el lanzamiento de un intérprete completo de Python solo para recortar una cadena. Es un desperdicio.
pdwalker

3
#!/bin/bash

function trim
{
    typeset trimVar
    eval trimVar="\${$1}"
    read trimVar << EOTtrim
    $trimVar
EOTtrim
    eval $1=\$trimVar
}

# Note that the parameter to the function is the NAME of the variable to trim, 
# not the variable contents.  However, the contents are trimmed.


# Example of use:
while read aLine
do
    trim aline
    echo "[${aline}]"
done < info.txt



# File info.txt contents:
# ------------------------------
# ok  hello there    $
#    another  line   here     $
#and yet another   $
#  only at the front$
#$



# Output:
#[ok  hello there]
#[another  line   here]
#[and yet another]
#[only at the front]
#[]

3

Descubrí que necesitaba agregar algo de código de una sdiffsalida desordenada para limpiarlo:

sdiff -s column1.txt column2.txt | grep -F '<' | cut -f1 -d"<" > c12diff.txt 
sed -n 1'p' c12diff.txt | sed 's/ *$//g' | tr -d '\n' | tr -d '\t'

Esto elimina los espacios finales y otros caracteres invisibles.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.