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




manejo split (25)

Tengo esta cadena almacenada en una variable:

IN="[email protected];[email protected]"

Ahora me gustaría dividir las cuerdas ; Delimitador para que yo tenga:

ADDR1="[email protected]"
ADDR2="[email protected]"

No necesariamente necesito las variables ADDR1 y ADDR2 . Si son elementos de una matriz eso es aún 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="[email protected];[email protected]"

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

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

Salida:

> [[email protected]]
> [[email protected]]

Hubo una solución que involucraba la configuración de Internal_field_separator (IFS) en ; . No estoy seguro de qué sucedió con esa respuesta, ¿cómo se restablece la configuración predeterminada de IFS ?

RE: Solución IFS , probé esto y funciona, conservo el antiguo IFS y luego lo restauro:

IN="[email protected];[email protected]"

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 de $IN funciona.


Answers

puedes aplicar awk a muchas situaciones

IN="[email protected];[email protected]"
IFS=';'; set $IN; IFS=$' \t\n'

también puedes usar esto

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

Hay algunas respuestas interesantes aquí (errator esp.), Pero para algo análogo a dividir en otros idiomas, que es lo que entendí que significaba la pregunta original, decidí sobre esto:

echo "[email protected];[email protected]"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

Ahora ${a[0]} , ${a[1]} , etc., son como usted esperaría. Use ${#a[*]} para la cantidad de términos. O a iterar, por supuesto:

echo "[email protected];[email protected]"|awk -F';' '{print $1,$2}' OFS="\n"

NOTA IMPORTANTE:

Esto funciona en los casos en que no hay espacios de qué preocuparse, lo que solucionó mi problema, pero puede que no resuelva el suyo. Vaya con la (s) solución (es) de $IFS en ese caso.



IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? 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:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

Explicación: la asignación simple utilizando paréntesis () convierte la lista separada por punto y coma en una matriz, siempre que tenga el IFS correcto al hacerlo. El bucle FOR estándar maneja elementos individuales en esa matriz como de costumbre. Observe que la lista dada para la variable IN debe estar entre comillas, es decir, con tics simples.

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 por separado IFS. Gracias por "Bize" por señalarlo.


Si no te importa procesarlos inmediatamente, me gusta hacer esto:

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

Podría 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.


Respuesta compatible

Para esta pregunta de SO, ya hay muchas maneras diferentes de hacer esto en bash . Pero bash tiene muchas características especiales , llamadas bashism que funcionan bien, pero que no funcionarán en ningún otro shell .

En particular, las matrices , la matriz asociativa y la sustitución de patrones son bashismos puros y pueden no funcionar bajo otras capas .

En mi Debian GNU / Linux , hay un shell estándar llamado dash , pero conozco a muchas personas que les gusta usar ksh .

Finalmente, en una situación muy pequeña, hay una herramienta especial llamada busybox con su propio intérprete de shell ( ash ).

Cadena solicitada

La muestra de cadena en la pregunta SO es:

IN="[email protected];[email protected]"

Como esto podría ser útil con espacios en blanco y como espacios en blanco podrían modificar el resultado de la rutina, prefiero usar esta cadena de muestra:

 IN="[email protected];[email protected];Full Name <[email protected]>"

Cadena dividida basada en delimitador en bash (versión> = 4.2)

Bajo puro bash, podemos usar arreglos y IFS :

var="[email protected];[email protected];Full Name <[email protected]>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$var"

El uso de esta sintaxis en bash reciente no cambia $IFS para la sesión actual, pero solo para el comando actual:

set | grep ^IFS=
IFS=$' \t\n'

Ahora la cadena var se divide y se almacena en una matriz ( fields nombre):

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

Podríamos solicitar contenido variable con declare -p :

declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

read es la forma más rápida de realizar la división, ya que no hay forks ni recursos externos llamados.

Desde allí, puede utilizar la sintaxis que ya conoce para procesar cada campo:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

o suelte cada campo después del procesamiento (me gusta este enfoque cambiante ):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

o incluso para una impresión simple (sintaxis más corta):

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

Cadena dividida basada en delimitador en shell

Pero si escribes algo utilizable bajo muchas capas, no debes usar bashismos .

Hay una sintaxis, utilizada en muchos shells, para dividir una cadena en la primera o última aparición de una subcadena:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(La falta de esto es la razón principal de mi publicación de respuestas)

Según lo señalado por Score_Under :

# y % eliminan la cadena coincidente más corta posible, y

## y %% eliminan el mayor tiempo posible.

Este pequeño script de muestra funciona bien en bash , dash , ksh , busybox y también se probó en bash de Mac-OS:

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

¡Que te diviertas!


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

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

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

Prueba

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

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]

Dos alternativas bourne-ish donde ninguna requiere matrices de bash:

Caso 1 : Manténgalo agradable y simple: use una NewLine como el separador de registros ... por ejemplo.

IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";

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

Idea: Tal vez valga la pena usar NL extensivamente internamente y solo convertir a un RS diferente cuando se genera el resultado final externamente .

Caso 2 : Usando un ";" como un separador de registro ... por ejemplo.

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

En ambos casos, una sub-lista se puede componer dentro del bucle y 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-)}


Aquí hay un 3-liner limpio:

in="[email protected];[email protected];[email protected];[email protected]"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

donde IFS delimita palabras basadas en el separador y () se utiliza para crear una array . Luego [@] se usa para devolver cada elemento como una palabra separada.

Si tiene algún código después de eso, también debe restaurar $IFS , por ejemplo, unset IFS .


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

IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

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


Sin configurar el IFS

Si solo tienes un colon puedes hacer eso:

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

conseguirás:

b = foo
c = bar

Esta bien 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 necesitas escapar del delimitador;
  2. No tendrás problema con espacios en blanco . ¡El valor será separado apropiadamente en la matriz!

[] 's


Tomado de Bash shell script split array :

IN="[email protected];[email protected]"
arrIN=(${IN//;/ })

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 ';' el carácter con un carácter ' ' se denomina expansión de parámetros .

Hay algunas trampas comunes:

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



En Bash, una forma a prueba de balas, 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 la opción -d de read (delimitador) con un delimitador vacío, para que la read se vea obligada a leer todo lo que se alimenta. Y alimentamos la read con el contenido exacto de la variable, sin una nueva línea final gracias a printf . Tenga en cuenta que también estamos poniendo el delimitador en printf para garantizar que la cadena que se pasa a read tenga un delimitador final. Sin él, la 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]="")'

el campo vacío que se arrastra se conserva.

Actualización para Bash≥4.4

Desde Bash 4.4, el mapfile (también readarray como readarray ) admite la opción -d para especificar un delimitador. De ahí que otra forma canónica es:

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


¿Qué tal este forro, si no estás usando arreglos?

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

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 sí funciona es:

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

donde // significa reemplazo global.


Creo que AWK es el mejor y más eficiente comando para resolver su problema. AWK está incluido en Bash por defecto en casi todas las distribuciones de Linux.

echo "[email protected];[email protected]" | awk -F';' '{print $1,$2}'

daré

[email protected] [email protected]

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


He visto un par de respuestas que hacen referencia al comando de cut , pero todas se han eliminado. Es un poco extraño que nadie haya elaborado sobre 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 se puede usar el cut , y es más efectivo si desea extraer campos específicos del medio.

Ejemplo:

$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]

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 tienes un archivo de registro delimitado con filas como esta:

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

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


Esta es la forma más sencilla de hacerlo.

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

Puede establecer la variable de Internal_field_separator (IFS) y luego dejar que se analice en una matriz. Cuando esto sucede en un comando, entonces la asignación a IFS solo tiene lugar en el entorno de ese comando individual (para read ). A continuación, analiza la entrada de acuerdo con el valor de la variable IFS en una matriz, que luego podemos iterar.

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

Se analizará una línea de elementos separados por ; , empujándolo en una matriz. Cosas para procesar la totalidad 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"


$(dirname "$(readlink -f "$BASH_SOURCE")")






bash shell split scripting