¿Cómo dividir una cadena en varias cadenas separadas por al menos un espacio en bash shell?


Tengo una cadena que contiene muchas palabras con al menos un espacio entre cada dos. ¿Cómo puedo dividir la cadena en palabras individuales para que pueda hacer un bucle a través de ellas?

La cadena se pasa como argumento. Por ejemplo, ${2} == "cat cat file". ¿Cómo puedo recorrerlo?

Además, ¿cómo puedo comprobar si una cadena contiene espacios?

Author: Zaz, 2009-09-24

7 answers

¿Intentaste simplemente pasar la variable string a un bucle for? Bash, por ejemplo, se dividirá en espacios en blanco automáticamente.

sentence="This is   a sentence."
for word in $sentence
do
    echo $word
done

 

This
is
a
sentence.
 207
Author: mob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-24 05:13:46

Me gusta la conversión a un array, para poder acceder a elementos individuales:

    sentence="this is a story"
    stringarray=($sentence)

Ahora puede acceder directamente a elementos individuales (comienza con 0):

    echo ${stringarray[0]}

O convertir de nuevo a cadena para hacer un bucle:

    for i in "${stringarray[@]}"
    do
      :
      # do whatever on $i
    done

Por supuesto, el bucle a través de la cadena directamente se respondió antes, pero esa respuesta tenía la desventaja de no realizar un seguimiento de los elementos individuales para su uso posterior:

    for i in $sentence
    do
      :
      # do whatever on $i
    done

Véase también Referencia del Array Bash

 223
Author: Highwind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-15 17:24:39

Simplemente usa los shells "set" integrados. Por ejemplo,

set $text

Después de eso, las palabras individuales en text text estarán en $1, 2 2, etc 3, etc. Para la robustez, uno generalmente hace

set -- junk $text
shift

Para manejar el caso donde text text está vacío o comienza con un guión. Por ejemplo:

text="This is          a              test"
set -- junk $text
shift
for word; do
  echo "[$word]"
done

Esto imprime

[This]
[is]
[a]
[test]
 75
Author: Idelic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-25 16:15:31

La forma probablemente más fácil y segura en BASH 3 y superior es:

var="string    to  split"
read -ra arr <<<"$var"

(donde arr es la matriz que toma las partes divididas de la cadena) o, si puede haber nuevas líneas en la entrada y desea más que solo la primera línea:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(tenga en cuenta el espacio en -d '', no se puede dejar de lado), pero esto podría darle una nueva línea inesperada de <<<"$var" (ya que esto agrega implícitamente un LF al final).

Ejemplo:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

Produce la esperado

[*]
[a]
[*]

Ya que esta solución (en contraste con todas las soluciones anteriores aquí) no es propensa a un desprendimiento inesperado y a menudo incontrolable de la cáscara.

También esto te da todo el poder de IFS como probablemente quieras: {[28]]}

Ejemplo:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

Produce algo como:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

Como puedes ver, los espacios también se pueden conservar de esta manera:{[28]]}

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

Salidas

[ split  ]
[   this    ]

Tenga en cuenta que el manejo de IFS en BASH es un tema en sí mismo, así que haga sus pruebas, algunos temas interesantes sobre esto:

  • unset IFS: Ignora las ejecuciones de SPC, TAB, NL y on line comienza y termina
  • IFS='': No hay separación de campo, solo lee todo
  • IFS=' ': Corridas de SPC (y solo SPC)

Algún último ejemplo

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

Salidas

1 [this is]
2 [a test]

Mientras que

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

Salidas

1 [this]
2 [is]
3 [a]
4 [test]

POR cierto:

  • Si usted no está acostumbrado a $'ANSI-ESCAPED-STRING' acostumbrarse a ella, es un ahorrador de tiempo.

  • Si no incluye -r (como en read -a arr <<<"$var") entonces read hace escapes de barra invertida. Se deja como ejercicio para el lector.


Para la segunda pregunta:

Para probar algo en una cadena, generalmente me atengo a case, ya que esto puede verificar varios casos a la vez (nota: case solo ejecuta la primera coincidencia, si necesita usar sentencias multiplce case), y esta necesidad es a menudo el caso (juego de palabras previsto):

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

Así que puede establecer el valor devuelto para comprobar el SPC de esta manera:

case "$var" in (*' '*) true;; (*) false;; esac

¿Por qué case? Debido a que por lo general es un poco más legible que las secuencias de expresiones regulares, y gracias a los metacaracteres de Shell maneja muy bien el 99% de todas las necesidades.

 42
Author: Tino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-14 13:49:30
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

Para comprobar los espacios, utilice grep:

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1
 30
Author: DVK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-24 20:02:39

(A) Para dividir una oración en sus palabras (separadas por espacio) simplemente puede usar el IFS predeterminado usando

array=( $string )


Ejemplo ejecutando el siguiente fragmento

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

Producirá

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

Como puede ver, también puede usar comillas simples o dobles sin ningún problema

Notas:
-- esto es básicamente lo mismo que la respuesta de mob, pero de esta manera se almacena el array para cualquier necesidad posterior. Si solo necesitas un solo bucle, puedes usar su respuesta, que es una línea más corta:)
-- por favor refiérase a esta pregunta para métodos alternativos para dividir una cadena basada en delimitador.


(B) Para comprobar si hay un carácter en una cadena, también puede usar una expresión regular match.
Ejemplo para comprobar la presencia de un carácter de espacio se puede utilizar:

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi
 10
Author: Luca Borrione,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:26

Para comprobar espacios solo con bash:

[[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"
 4
Author: glenn jackman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-25 03:14:12