¿Cómo divido una cadena en un delimitador en Bash?

Tengo esta cadena almacenada en una variable:

IN="bla@some.com;john@home.com"

Ahora me gustaría dividir las cuerdas por ; delimitador para que tenga:

ADDR1="bla@some.com"
ADDR2="john@home.com"

No necesito necesariamente el ADDR1 y ADDR2 variables. Si son elementos de una matriz, eso es incluso mejor.


Después de las sugerencias de las respuestas a continuación, terminé con lo siguiente, que es lo que buscaba:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Salida:

> [bla@some.com]
> [john@home.com]

Había una solución que involucraba la configuración Separador_de_campo_interno (IFS) a ;. No estoy seguro de lo que pasó con esa respuesta, ¿cómo se reinicia? IFS volver a los valores predeterminados?

RE: IFS solución, probé esto y funciona, mantengo el viejo IFS y luego restaurarlo:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

Por cierto, cuando lo intenté

mails2=($IN)

Solo obtuve la primera cadena al imprimirla en bucle, sin corchetes alrededor $IN funciona.

preguntado el 27 de mayo de 09 a las 23:05

Con respecto a su "Edit2": simplemente puede "desarmar IFS" y volverá al estado predeterminado. No es necesario guardarlo y restaurarlo explícitamente a menos que tenga alguna razón para esperar que ya se haya configurado en un valor no predeterminado. Además, si está haciendo esto dentro de una función (y, si no lo está, ¿por qué no?), Puede establecer IFS como una variable local y volverá a su valor anterior una vez que salga de la función. -

@BrooksMoses: (a) +1 por usar local IFS=... donde sea posible; (b) -1 para unset IFS, esto no restablece exactamente IFS a su valor predeterminado, aunque creo que un IFS no configurado se comporta igual que el valor predeterminado de IFS ($ '\ t \ n'), sin embargo, parece una mala práctica asumir ciegamente que su código nunca se invocará con IFS establecido en un valor personalizado; (c) otra idea es invocar una subcapa: (IFS=$custom; ...) cuando la subcapa sale, IFS volverá a lo que era originalmente. -

Solo quiero echar un vistazo rápido a las rutas para decidir dónde lanzar un ejecutable, así que recurrí a ejecutar ruby -e "puts ENV.fetch('PATH').split(':')". Si quieres permanecer puro, bash no ayudará, pero usar cualquier lenguaje de programación que tiene una división incorporada es más fácil. -

for x in $(IFS=';';echo $IN); do echo "> [$x]"; done -

Para guardarlo como una matriz, tuve que colocar otro paréntesis y cambiar el \n por solo un espacio. Entonces la línea final es mails=($(echo $IN | tr ";" " ")). Entonces ahora puedo verificar los elementos de mails usando la notación de matriz mails[index] o simplemente iterando en un bucle -

30 Respuestas

Puedes configurar el separador de campo interno (IFS) y luego deje que se analice en una matriz. Cuando esto sucede en un comando, entonces la asignación a IFS sólo tiene lugar en el entorno de ese único comando (para read ). Luego analiza la entrada de acuerdo con el IFS valor de la variable en una matriz, que luego podemos iterar.

Este ejemplo analizará una línea de elementos separados por ;, empujándolo en una matriz:

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
  # process "$i"
done

Este otro ejemplo es para procesar todo el contenido de $IN, cada vez una línea de entrada separada por ;:

while IFS=';' read -ra ADDR; do
  for i in "${ADDR[@]}"; do
    # process "$i"
  done
done <<< "$IN"

respondido 09 mar '21, 17:03

Probablemente esta sea la mejor manera. ¿Cuánto tiempo persistirá IFS en su valor actual, puede estropear mi código si se configura cuando no debería, y cómo puedo restablecerlo cuando termine con él? - chris lutz

ahora después de la corrección aplicada, solo dentro de la duración del comando de lectura :) - Johannes Schaub - litb

Puede leer todo a la vez sin usar un bucle while: read -r -d '' -a addr <<< "$ in" # La -d '' es la clave aquí, le dice a read que no se detenga en la primera línea nueva ( que es el valor predeterminado -d) pero continuar hasta EOF o un byte NULL (que solo ocurre en datos binarios). - lhunath

Configuración de @LucaBorrione IFS en la misma línea que el read sin punto y coma u otro separador, a diferencia de en un comando separado, lo ajusta a ese comando, por lo que siempre está "restaurado"; no necesita hacer nada manualmente. - Carlos Duffy

@imagineerThis Hay un error que involucra heretrings y cambios locales en IFS que requiere $IN Ser citado. El error está arreglado en bash 4.3. - Chepner

Tomado de Matriz dividida de script de shell Bash:

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
echo ${arrIN[1]}                  # Output: john@home.com

Explicación:

Esta construcción reemplaza todas las apariciones de ';' (la inicial // significa reemplazo global) en la cadena IN con ' ' (un solo espacio), luego interpreta la cadena delimitada por espacios como una matriz (eso es lo que hacen los paréntesis circundantes).

La sintaxis utilizada dentro de las llaves para reemplazar cada ';' personaje con un ' ' el personaje se llama Expansión de parámetros.

Hay algunas trampas comunes:

  1. Si la cadena original tiene espacios, deberá usar IFS:
  • IFS=':'; arrIN=($IN); unset IFS;
  1. Si la cadena original tiene espacios y el delimitador es una nueva línea, puede establecer IFS con:
  • IFS=$'\n'; arrIN=($IN); unset IFS;

Respondido el 30 de diciembre de 20 a las 10:12

Solo quiero agregar: este es el más simple de todos, puede acceder a los elementos de la matriz con $ {arrIN [1]} (comenzando desde ceros, por supuesto) - Oz123

Lo encontré: la técnica de modificar una variable dentro de un $ {} se conoce como 'expansión de parámetros'. - KomodoDave

No, no creo que esto funcione cuando también hay espacios presentes ... está convirtiendo el ',' a '' y luego construyendo una matriz separada por espacios. - Ethan

Muy conciso, pero hay advertencias para uso general: se aplica el caparazón división de palabras y expansiones a la cuerda, que puede no ser deseada; solo pruébalo con. IN="bla@some.com;john@home.com;*;broken apart". En resumen: este enfoque se romperá si sus tokens contienen espacios y / o caracteres incrustados. como * que hacen que un token coincida con los nombres de archivo en la carpeta actual. - mklement0

Este es un mal enfoque por otras razones: por ejemplo, si su cadena contiene ;*;, Entonces el * se expandirá a una lista de nombres de archivo en el directorio actual. -1 - Carlos Duffy

Si no le importa procesarlos de inmediato, me gusta hacer esto:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

Puede usar este tipo de bucle para inicializar una matriz, pero probablemente haya una forma más fácil de hacerlo. Espero que esto ayude, sin embargo.

contestado el 28 de mayo de 09 a las 03:05

Deberías haber guardado la respuesta IFS. Me enseñó algo que no sabía, y definitivamente hizo una matriz, mientras que esto solo es un sustituto barato. - chris lutz

Veo. Sí, encuentro que haciendo estos experimentos tontos, voy a aprender cosas nuevas cada vez que intento responder cosas. He editado cosas basadas en los comentarios de #bash IRC y las he recuperado :) - Johannes Schaub - litb

-1, obviamente no está al tanto de la división de palabras, porque está introduciendo dos errores en su código. uno es cuando no cita $ IN y el otro es cuando pretende que una nueva línea es el único delimitador utilizado en la división de palabras. Está iterando sobre cada WORD en IN, no cada línea, y DEFINITIVAMENTE no todos los elementos delimitados por un punto y coma, aunque puede parecer que tiene el efecto secundario de que parece que funciona. - lhunath

Puede cambiarlo para que haga eco de "$ IN" | tr ';' '\ n' | mientras lee -r ADDY; hacer # procesar "$ ADDY"; hecho para que tenga suerte, creo :) Tenga en cuenta que esto se bifurcará, y no puede cambiar las variables externas desde dentro del bucle (es por eso que usé la sintaxis <<< "$ IN") entonces - Johannes Schaub - litb

Para resumir el debate en los comentarios: Advertencias para uso general: se aplica el caparazón división de palabras y expansiones a la cuerda, que puede no ser deseada; solo pruébalo con. IN="bla@some.com;john@home.com;*;broken apart". En resumen: este enfoque se romperá si sus tokens contienen espacios y / o caracteres incrustados. como * que hacen que un token coincida con los nombres de archivo en la carpeta actual. - mklement0

Respuesta compatible

Hay muchas formas diferentes de hacer esto en .

Sin embargo, es importante tener en cuenta primero que bash tiene muchas especial características (las llamadas bashismos) que no funcionará en ningún otro .

En particular, arrays, matrices asociativas y sustitución de patrones, que se utilizan en las soluciones de esta publicación, así como otras en el hilo, son bashismos y puede que no funcione bajo otros conchas que mucha gente usa.

Por ejemplo: en mi Debian GNU / Linux, hay un estándar shell llamado ; Conozco a muchas personas a las que les gusta usar otro caparazón llamado ; y también hay una herramienta especial llamada con su propio intérprete de concha).

Cadena solicitada

La cadena que se dividirá en la pregunta anterior es:

IN="bla@some.com;john@home.com"

Usaré una versión modificada de esta cadena para asegurarme de que mi solución sea robusta para cadenas que contengan espacios en blanco, lo que podría romper otras soluciones:

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

Cadena dividida según el delimitador en (versión> = 4.2)

In puro bash, podemos crear un matriz con elementos divididos por un valor temporal para IFS (El separador de campo de entrada). El IFS, entre otras cosas, dice bash qué carácter (s) debe tratar como un delimitador entre elementos al definir una matriz:

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS

En nuevas versiones de bash, anteponer un comando con una definición de IFS cambia el IFS para ese comando , solamente y lo restablece al valor anterior inmediatamente después. Esto significa que podemos hacer lo anterior en una sola línea:

IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'

Podemos ver que la cuerda IN se ha almacenado en una matriz llamada fields, dividido en punto y coma:

set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'

(También podemos mostrar el contenido de estas variables usando declare -p:)

declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")

Tenga en cuenta que read es la el más rápido manera de hacer la división porque no hay tenedores o recursos externos llamados.

Una vez que se define la matriz, puede usar un bucle simple para procesar cada campo (o, más bien, cada elemento en la matriz que ha definido):

# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
    echo "> [$x]"
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

O puede eliminar cada campo de la matriz después de procesar utilizando un cambio enfoque, que me gusta:

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    fields=("${fields[@]:1}")
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

Y si solo desea una impresión simple de la matriz, ni siquiera necesita recorrerla:

printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

Actualización: reciente > = 4.4

En nuevas versiones de bash, también puedes jugar con el comando mapfile:

mapfile -td \; fields < <(printf "%s\0" "$IN")

¡Esta sintaxis conserva caracteres especiales, nuevas líneas y campos vacíos!

Si no desea incluir campos vacíos, puede hacer lo siguiente:

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

Con mapfile, también puede omitir la declaración de una matriz e implícitamente "recorrer" los elementos delimitados, llamando a una función en cada uno:

myPubliMail() {
    printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
    # mail -s "This is not a spam..." "$2" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail

(Nota la \0 al final de la cadena de formato es inútil si no le importan los campos vacíos al final de la cadena o si no están presentes).

mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail

# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

Or podrías usar <<<, y en el cuerpo de la función incluye algún procesamiento para eliminar la nueva línea que agrega:

myPubliMail() {
    local seq=$1 dest="${2%$'\n'}"
    printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
    # mail -s "This is not a spam..." "$dest" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

# Renders the same output:
# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

Cadena dividida según el delimitador en

Si no puedes usar bash, o si desea escribir algo que se pueda usar en muchos shells diferentes, a menudo no puedes utilizan el bashismos - y esto incluye las matrices que hemos estado usando en las soluciones anteriores.

Sin embargo, no necesitamos usar matrices para recorrer los "elementos" de una cadena. Hay una sintaxis utilizada en muchos shells para eliminar subcadenas de una cadena de la en el primer or pasado ocurrencia de un patrón. Tenga en cuenta que * es un comodín que representa cero o más caracteres:

(La falta de este enfoque en cualquier solución publicada hasta ahora es la razón principal por la que estoy escribiendo esta respuesta;)

${var#*SubStr}  # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*}  # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string

Como se explica por Puntuación_menos:

# y % eliminar la subcadena coincidente más corta posible de la comienzo y final de la cadena respectivamente, y

## y %% eliminar la subcadena coincidente más larga posible.

Usando la sintaxis anterior, podemos crear un enfoque en el que extraemos "elementos" de subcadena de la cadena eliminando las subcadenas hasta o después del delimitador.

El bloque de código siguiente funciona bien en (incluidos los de Mac OS bash), , y 's :

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
    # extract the substring from start of string up to delimiter.
    # this is the first "element" of the string.
    iter=${IN%%;*}
    echo "> [$iter]"
    # if there's only one element left, set `IN` to an empty string.
    # this causes us to exit this `while` loop.
    # else, we delete the first "element" of the string from IN, and move onto the next.
    [ "$IN" = "$iter" ] && \
        IN='' || \
        IN="${IN#*;}"
  done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

¡Que se diviertan!

Respondido el 20 de junio de 20 a las 10:06

El #, ##, % y %% Las sustituciones tienen, en mi opinión, una explicación más fácil de recordar (por cuánto eliminan): # y % eliminar la cadena coincidente más corta posible, y ## y %% eliminar el mayor tiempo posible. - Puntuación_menos

El IFS=\; read -a fields <<<"$var" falla en las nuevas líneas y agrega una nueva línea al final. La otra solución elimina un campo vacío al final. - Isaac

¿Podría usarse la última alternativa con una lista de separadores de campo configurada en otro lugar? Por ejemplo, quiero usar esto como un script de shell y pasar una lista de separadores de campo como parámetro posicional. - sancho.s ReincorporarMonicaCellio

Sí, en un bucle: for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ... - F.Hauri

Esta respuesta es bastante épica. - Tylerl

He visto un par de respuestas que hacen referencia al cut comando, pero todos han sido eliminados. Es un poco extraño que nadie haya profundizado en eso, porque creo que es uno de los comandos más útiles para hacer este tipo de cosas, especialmente para analizar archivos de registro delimitados.

En el caso de dividir este ejemplo específico en una matriz de script bash, tr es probablemente más eficiente, pero cut se puede utilizar y es más eficaz si desea extraer campos específicos del medio.

Ejemplo:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

Obviamente, puede poner eso en un bucle e iterar el parámetro -f para extraer cada campo de forma independiente.

Esto se vuelve más útil cuando tiene un archivo de registro delimitado con filas como esta:

2015-04-27|12345|some action|an attribute|meta data

cut es muy útil poder cat este archivo y seleccione un campo en particular para su posterior procesamiento.

Respondido 28 Abr '15, 23:04

Felicitaciones por usar cut, ¡es la herramienta adecuada para el trabajo! Mucho más despejado que cualquiera de esos trucos de shell. - mistermiyagi

Este enfoque solo funcionará si conoce la cantidad de elementos de antemano; necesitaría programar algo más de lógica a su alrededor. También ejecuta una herramienta externa para cada elemento. - uli42

Excamente lo que estaba buscando tratando de evitar una cadena vacía en un csv. Ahora también puedo señalar el valor exacto de la 'columna'. Trabaja con IFS ya utilizado en un bucle. Mejor de lo esperado para mi situación. - Louis Loudog Trotier

Muy útil para extraer ID y PID también, es decir, milos grujic

Vale la pena desplazarse por esta respuesta más de media página :) - Gucu112

Esto funcionó para mí:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

Respondido el 24 de enero de 17 a las 02:01

Aunque solo funciona con un delimitador de un solo carácter, eso es lo que estaba buscando el OP (registros delimitados por un punto y coma). - chicopaddock

Respondido hace unos cuatro años por @Ashok, y también, hace más de un año por @DougW, que tu respuesta, con aún más información. Publique una solución diferente a la de los demás. - MAChitgarha

Creo que el AWK es el mejor y más eficaz comando para resolver su problema. AWK se incluye de forma predeterminada en casi todas las distribuciones de Linux.

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

daré

bla@some.com john@home.com

Por supuesto, puede almacenar cada dirección de correo electrónico redefiniendo el campo de impresión awk.

Respondido 03 Jul 19, 14:07

O incluso más simple: echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}' - Primavera

@Jaro Esto funcionó perfectamente para mí cuando tenía una cadena con comas y necesitaba reformatearla en líneas. Gracias. - Acuarela

Funcionó en este escenario -> "echo" $ SPLIT_0 "| awk -F 'inode =' '{print $ 1}'"! Tuve problemas al intentar usar atrings ("inode =") en lugar de caracteres (";"). $ 1, $ 2, $ 3, $ 4 se establecen como posiciones en una matriz. Si hay una forma de configurar una matriz ... ¡mejor! ¡Gracias! - eduardo lucio

@EduardoLucio, lo que estoy pensando es que quizás puedas reemplazar primero tu delimitador inode= dentro ; por ejemplo por sed -i 's/inode\=/\;/g' your_file_to_process, luego defina -F';' cuando aplicar awk, espero que pueda ayudarte. - Tong

¿Qué tal este enfoque?

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

Fuente

Respondido 20 Jul 11, 17:07

+1 ... pero no nombraría la variable "Array" ... pet peev, supongo. Buena solución. - Yzmir Ramírez

+1 ... pero el "set" y declare -a son innecesarios. También podrías haber usado solo IFS";" && Array=($IN) - ATA

+1 Solo una nota al margen: ¿no sería recomendable conservar el antiguo IFS y luego restaurarlo? (como lo muestra stefanB en su edición3) las personas que aterrizan aquí (a veces simplemente copiando y pegando una solución) podrían no pensar en esto - Luca Borrione

-1: Primero, @ata tiene razón en que la mayoría de los comandos en esto no hacen nada. En segundo lugar, utiliza la división de palabras para formar la matriz y no hace nada para inhibir la expansión glob al hacerlo (por lo tanto, si tiene caracteres glob en cualquiera de los elementos de la matriz, esos elementos se reemplazan con nombres de archivo coincidentes). - Carlos Duffy

Sugerir usar $'...': IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'. entonces echo "${Array[2]}" imprimirá una cadena con nueva línea. set -- "$IN" también es necesario en este caso. Sí, para evitar la expansión glob, la solución debe incluir set -f. - Juan_Oeste

echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

contestado el 28 de mayo de 09 a las 03:05

-1 ¿Qué pasa si la cadena contiene espacios? por ejemplo IN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) producirá una matriz de 8 elementos en este caso (un elemento para cada espacio de palabras separado), en lugar de 2 (un elemento para cada línea separada por punto y coma) Luca Borrione

@Luca No, el script sed crea exactamente dos líneas. Lo que crea las múltiples entradas para usted es cuando las coloca en una matriz bash (que se divide en espacios en blanco de forma predeterminada): Lothar

Ese es exactamente el punto: el OP necesita almacenar entradas en una matriz para recorrerla, como puede ver en sus ediciones. Creo que tu (buena) respuesta omitió mencionar para usar arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) ) para lograrlo, y consejos para cambiar IFS a IFS=$'\n' para aquellos que aterrizan aquí en el futuro y necesitan dividir una cadena que contiene espacios. (y restaurarlo después). :) - Luca Borrione

@Luca Buen punto. Sin embargo, la asignación de la matriz no estaba en la pregunta inicial cuando escribí esa respuesta. - Lothar

Esto también funciona:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

Tenga cuidado, esta solución no siempre es correcta. En caso de que solo pase "bla@some.com", lo asignará tanto a ADD1 como a ADD2.

Respondido 17 Abr '14, 02:04

Puede utilizar -s para evitar el problema mencionado: superusuario.com/questions/896800/… "-f, --fields = LIST seleccione solo estos campos; también imprima cualquier línea que no contenga un carácter delimitador, a menos que se especifique la opción -s" - Fersarr

Una toma diferente La respuesta de Darron, así es como lo hago:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

contestado el 23 de mayo de 17 a las 13:05

¡Creo que sí! Ejecute los comandos anteriores y luego "echo $ ADDR1 ... $ ADDR2" y obtengo la salida "bla@some.com ... john@home.com" - nickjb

Esto funcionó REALMENTE bien para mí ... Lo usé para iterar sobre una matriz de cadenas que contenían datos DB, SERVER, PORT separados por comas para usar mysqldump. - Nick

Diagnóstico: el IFS=";" La asignación existe solo en el $(...; echo $IN) subshell; es por eso que algunos lectores (incluyéndome a mí) inicialmente piensan que no funcionará. Supuse que ADDR1 estaba absorbiendo todo $ IN. Pero nickjb tiene razón; funciona. La razón es que echo $IN El comando analiza sus argumentos usando el valor actual de $ IFS, pero luego los repite a stdout usando un delimitador de espacio, independientemente de la configuración de $ IFS. Entonces, el efecto neto es como si uno hubiera llamado read ADDR1 ADDR2 <<< "bla@some.com john@home.com" (tenga en cuenta que la entrada está separada por espacios, no; separada). - dudoso

Esto falla en espacios y líneas nuevas, y también expande comodines * en la echo $IN con una expansión variable sin comillas. - Isaac

Me gusta mucho esta solución. Una descripción de por qué funciona sería muy útil y lo convertiría en una mejor respuesta general. - Michael Gaskill

¿Qué tal este trazador de líneas, si no está utilizando matrices?

IFS=';' read ADDR1 ADDR2 <<<$IN

Respondido el 13 de Septiembre de 10 a las 21:09

Considera usar read -r ... para asegurarse de que, por ejemplo, los dos caracteres "\ t" en la entrada terminen como los mismos dos caracteres en sus variables (en lugar de un solo carácter de tabulación). - dudoso

-1 Esto no funciona aquí (ubuntu 12.04). Añadiendo echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2" a su fragmento saldrá ADDR1 bla@some.com john@home.com\nADDR2 (\ n es nueva línea) - Luca Borrione

Esto probablemente se deba a un error que involucra IFS y aquí cadenas que se fijaron en bash 4.3. Citando $IN debería arreglarlo. (En teoria, $IN no está sujeto a la división de palabras ni a la agrupación después de que se expande, lo que significa que las comillas deben ser innecesarias. Sin embargo, incluso en 4.3, queda al menos un error, informado y programado para ser reparado, por lo que citar sigue siendo una buena idea). Chepner

Esto se rompe si $ in contiene nuevas líneas incluso si se cotiza $ IN. Y agrega una nueva línea al final. - Isaac

Un problema con esto, y muchas otras soluciones, es también que asume que hay EXACTAMENTE DOS elementos en $ IN - O que está dispuesto a que el segundo y los siguientes elementos se rompan en ADDR2. Entiendo que esto cumple con la pregunta, pero es una bomba de tiempo. - Steven el Fácilmente Divertido

En Bash, una forma a prueba de balas, que funcionará incluso si su variable contiene nuevas líneas:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

Mira:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

El truco para que esto funcione es usar el -d opción de read (delimitador) con un delimitador vacío, de modo que read se ve obligado a leer todo lo que se alimenta. Y nos alimentamos read con exactamente el contenido de la variable in, sin nueva línea final gracias a printf. Tenga en cuenta que también estamos poniendo el delimitador en printf para asegurarse de que la cadena pasa a read tiene un delimitador final. Sin ello, read recortaría los posibles campos vacíos finales:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

se conserva el campo vacío final.


Actualización para Bash≥4.4

Desde Bash 4.4, el builtin mapfile (también llamado readarray) apoya el -d opción para especificar un delimitador. Por lo tanto, otra forma canónica es:

mapfile -d ';' -t array < <(printf '%s;' "$in")

Respondido el 20 de junio de 20 a las 10:06

Lo encontré como la solución rara en esa lista que funciona correctamente con \n, espacios y * simultaneamente. Además, no hay bucles; La variable de matriz es accesible en el shell después de la ejecución (contrariamente a la respuesta más votada). Nota, in=$'...', no funciona con comillas dobles. Creo que necesita más votos a favor. - Juan_Oeste

Sin configurar el IFS

Si solo tiene dos puntos, puede hacer eso:

a="foo:bar"
b=${a%:*}
c=${a##*:}

Conseguirás:

b = foo
c = bar

Respondido 01 ago 16, 14:08

Aquí hay un 3-liner limpio:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

sin que importe IFS delimitar palabras basadas en el separador y () se utiliza para crear un matriz. entonces [@] se utiliza para devolver cada artículo como una palabra separada.

Si tiene algún código después de eso, también necesita restaurar $IFS, p.ej unset IFS.

Respondido 26 Oct 16, 11:10

El uso de $in unquoted permite expandir los comodines. - Isaac

La siguiente función Bash / zsh divide su primer argumento en el delimitador dado por el segundo argumento:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

Por ejemplo, el comando

$ split 'a;b;c' ';'

los rendimientos

a
b
c

Esta salida puede, por ejemplo, conectarse a otros comandos. Ejemplo:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

En comparación con las otras soluciones dadas, esta tiene las siguientes ventajas:

  • IFS no se anula: debido al alcance dinámico de incluso las variables locales, anulando IFS sobre un bucle hace que el nuevo valor se filtre en las llamadas de función realizadas desde dentro del bucle.

  • Las matrices no se utilizan: leer una cadena en una matriz utilizando read requiere la bandera -a en Bash y -A en zsh.

Si lo desea, la función se puede poner en un script de la siguiente manera:

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

Respondido el 13 de junio de 17 a las 19:06

No parece funcionar con delimitadores de más de 1 carácter: split = $ (split "$ content" "file: //") - locos

Verdadero - de help read: -d delim continue until the first character of DELIM is read, rather than newline - halle knast

puedes aplicar awk a muchas situaciones

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

también puedes usar esto

echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"

Respondido el 21 de enero de 18 a las 11:01

Hay una forma simple e inteligente como esta:

echo "add:sfff" | xargs -d: -i  echo {}

Pero debe usar gnu xargs, BSD xargs no admite -d delim. Si usa Apple Mac como yo. Puede instalar gnu xargs:

brew install findutils

luego

echo "add:sfff" | gxargs -d: -i  echo {}

Respondido el 16 de Septiembre de 15 a las 04:09

Esta es la forma más sencilla de hacerlo.

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

Respondido 28 Feb 12, 08:02

Si no hay espacio, ¿por qué no esto?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

Respondido 24 Abr '13, 14:04

Hay algunas respuestas geniales aquí (errator especialmente), pero para que algo análogo se divida en otros idiomas, que es lo que entendí que significaba la pregunta original, me decidí por esto:

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";

Actualmente ${a[0]}, ${a[1]}, etc., son los esperados. Usar ${#a[*]} por número de términos. O para iterar, por supuesto:

for i in ${a[*]}; do echo $i; done

NOTA IMPORTANTE:

Esto funciona en los casos en los que no hay espacios de los que preocuparse, lo que resolvió mi problema, pero puede que no resuelva el tuyo. Ir con el $IFS solución (es) en ese caso.

Respondido el 21 de enero de 17 a las 20:01

No funciona cuando IN contiene más de dos direcciones de correo electrónico. Consulte la misma idea (pero fijada) en la respuesta de palindrom - oho

Mejor uso ${IN//;/ } (doble barra) para que también funcione con más de dos valores. Tenga en cuenta que cualquier comodín (*?[) se ampliará. Y se descartará un campo vacío al final. - Isaac

IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

Salida

bla@some.com
john@home.com

Sistema: Ubuntu 12.04.1

Respondido 25 Oct 16, 13:10

IFS no se establece en el contexto específico de read aquí y por lo tanto puede alterar el resto del código, si lo hay. - códigoforester

Use el set incorporado para cargar el $@ formación:

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

Entonces, que comience la fiesta:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

Respondido 30 Abr '13, 04:04

Mejor uso set -- $IN para evitar algunos problemas con "$ IN" que comienzan con un guión. Aún así, la expansión no citada de $IN expandirá los comodines*?[). - Isaac

Dos alternativas bourne-ish donde ninguna requiere matrices bash:

Caso 1: Manténgalo agradable y simple: use una línea nueva como separador de registros ... ej.

IN="bla@some.com
john@home.com"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

Nota: en este primer caso, no se bifurca ningún subproceso para ayudar con la manipulación de la lista.

Idea: tal vez valga la pena usar NL extensamente internamente, y solo se convierte a un RS diferente al generar el resultado final externamente.

Caso 2: Usando un ";" como un separador de registros ... ej.

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

En ambos casos, se puede componer una sublista dentro del bucle que es persistente después de que el bucle se haya completado. Esto es útil cuando se manipulan listas en la memoria, en lugar de almacenar listas en archivos. {ps mantén la calma y continúa B-)}

Respondido el 02 de Septiembre de 13 a las 07:09

Aparte de las fantásticas respuestas que ya se proporcionaron, si solo se trata de imprimir los datos, puede considerar utilizar awk:

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

Esto establece el separador de campo en ;, para que pueda recorrer los campos con un for bucle e imprima en consecuencia.

Pruebatest

$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]

Con otra entrada:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

Respondido el 08 de enero de 15 a las 10:01

En el shell de Android, la mayoría de los métodos propuestos simplemente no funcionan:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

Lo que funciona es:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

sin que importe // significa reemplazo global.

Respondido 19 Abr '15, 23:04

Falla si alguna parte de $ PATH contiene espacios (o líneas nuevas). También expande los comodines (asterisco *, signo de interrogación? Y llaves […]). - Isaac

IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

Salida:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

Explicación: La asignación simple usando paréntesis () convierte la lista separada por punto y coma en una matriz siempre que tenga el IFS correcto mientras lo hace. El bucle FOR estándar maneja elementos individuales en esa matriz como de costumbre. Observe que la lista proporcionada para la variable IN debe estar entre comillas "estrictas", es decir, con tics únicos.

IFS debe guardarse y restaurarse ya que Bash no trata una asignación de la misma manera que un comando. Una solución alternativa es envolver la asignación dentro de una función y llamar a esa función con un IFS modificado. En ese caso, no es necesario guardar / restaurar IFS por separado. Gracias por "Bize" por señalar eso.

Respondido 19 Abr '15, 23:04

!"#$%&/()[]{}*? are no problem bueno ... no del todo: []*? son personajes glob. Entonces, ¿qué hay de la creación de este directorio y archivo: `mkdir '!" # $% &'; Touch '! "# $% & / () [] {} Te tengo jajajaja - no hay problema' y ejecutas tu comando? lo simple puede ser hermoso, pero cuando está roto, está roto. - gniourf_gniourf

@gniourf_gniourf La cadena se almacena en una variable. Consulte la pregunta original. - ajaaskel

@ajaaskel, no entendiste completamente mi comentario. Vaya a un directorio temporal y emita estos comandos: mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'. Solo crearán un directorio y un archivo, con nombres extraños, debo admitir. Luego ejecute sus comandos con la exacta IN tu diste: IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'. Verá que no obtendrá el resultado que espera. Porque está utilizando un método sujeto a expansiones de nombre de ruta para dividir su cadena. - gniourf_gniourf

Esto es para demostrar que los personajes *, ?, [...] e incluso, si extglob Está establecido, !(...), @(...), ?(...), +(...) se encuentran las problemas con este método! - gniourf_gniourf

@gniourf_gniourf Gracias por los comentarios detallados sobre el globbing. Ajusté el código para que se apagara el globo. Sin embargo, mi punto era solo mostrar que una asignación bastante simple puede hacer el trabajo de división. - ajaaskel

Una línea para dividir una cadena separada por ';' en una matriz es:

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

Esto solo configura IFS en una subcapa, por lo que no tiene que preocuparse por guardar y restaurar su valor.

respondido 29 nov., 14:22

-1 esto no funciona aquí (ubuntu 12.04). imprime solo el primer eco con todo el valor $ IN en él, mientras que el segundo está vacío. puedes verlo si pones echo "0:" $ {ADDRS [0]} \ n echo "1:" $ {ADDRS [1]} la salida es0: bla@some.com;john@home.com\n 1: (\ n es una nueva línea) - Luca Borrione

consulte la respuesta de nickjb en para obtener una alternativa funcional a esta idea stackoverflow.com/a/6583589/1032370 - Luca Borrione

-1, 1. IFS no se está configurando en esa subcapa (se está pasando al entorno de "echo", que es incorporado, por lo que no sucede nada de todos modos). 2. $IN se cotiza por lo que no está sujeto a la división de IFS. 3. La sustitución del proceso se divide por espacios en blanco, pero esto puede dañar los datos originales. - Puntuación_menos

Ok chicos!

¡Aquí está mi respuesta!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

¿Por qué este enfoque es "el mejor" para mí?

Por dos razones:

  1. no es necesario escapar el delimitador;
  2. Usted no tendrá que problema con los espacios en blanco. ¡El valor se separará correctamente en la matriz!

[]'s

Respondido 04 Abr '16, 21:04

FYI, /etc/os-release y /etc/lsb-release están destinados a ser obtenidos, no analizados. Entonces tu método es realmente incorrecto. Además, no está respondiendo la pregunta sobre dividir una cadena en un delimitador. - gniourf_gniourf

Quizás no sea la solución más elegante, pero funciona con * y espacios:

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

Salidas

> [bla@so me.com]
> [*]
> [john@home.com]

Otro ejemplo (delimitadores al principio y al final):

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []

Básicamente, elimina todos los caracteres que no sean ; fabricación delims p.ej. ;;;. Entonces lo hace for bucle de 1 a number-of-delimiters contado por ${#delims}. El último paso es obtener la $ila parte usando cut.

Respondido 26 Feb 16, 12:02

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.