Respuesta compatible
Hay muchas formas diferentes de hacer esto en golpetazo.
Sin embargo, es importante tener en cuenta que bash
tiene muchas características especiales (los llamados bashismos ) que no funcionarán en ningún otrocáscara.
En particular, las matrices , las matrices asociativas y la sustitución de patrones , que se usan en las soluciones en esta publicación, así como en otras en el hilo, son bashismos y pueden no funcionar bajo otras capas que muchas personas usan.
Por ejemplo: en mi Debian GNU / Linux , hay un shell estándar llamadoguión; Conozco a muchas personas que les gusta usar otro shell llamadoksh; y también hay una herramienta especial llamadabusybox con su propio intérprete de shell (ceniza)
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 las cadenas que contienen espacios en blanco, lo que podría romper otras soluciones:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
División de cadena basada en delimitador en golpetazo (versión> = 4.2)
En puro bash
, podemos crear una matriz con elementos divididos por un valor temporal para IFS (el separador de campo de entrada ). El IFS, entre otras cosas, le 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 las versiones más recientes de bash
, el prefijo de 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 cadena IN
se ha almacenado en una matriz llamada fields
, dividida 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 forma más rápida de hacer la división porque no se llaman tenedores o recursos externos.
Una vez que se define la matriz, puede usar un bucle simple para procesar cada campo (o, más bien, cada elemento de la matriz que ha definido ahora):
# `"${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 bien, puede eliminar cada campo de la matriz después de procesar utilizando un enfoque de desplazamiento , 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 golpetazo > = 4.4
En las versiones más recientes de bash
, también puedes jugar con el comando mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
¡Esta sintaxis conserva caracteres especiales, líneas nuevas 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 y "bucle" implícitamente sobre 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: el \0
final de la cadena de formato es inútil si no le interesan los campos vacíos al final de la cadena o 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.
O podría usar <<<
, y en el cuerpo de la función incluir 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.
División de cadena basada en delimitador en cáscara
Si no puede usar bash
, o si desea escribir algo que pueda usarse en muchos shells diferentes, a menudo no puede usar bashisms , y esto incluye los arreglos 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 desde la primera o la última aparición de un patrón. Tenga en cuenta que *
es un comodín que significa 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 escribo 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
Según lo explicado por Score_Under :
#
y %
elimine la subcadena coincidente más corta posible desde el inicio y el final de la cadena respectivamente, y
##
y %%
elimine la subcadena coincidente más larga posible.
Usando la sintaxis anterior, podemos crear un enfoque donde extraemos "elementos" de la subcadena de la cadena eliminando las subcadenas hasta o después del delimitador.
El siguiente bloque de código funciona bien en golpetazo(incluidos los Mac OS bash
),guión, kshy busybox's ceniza:
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 te diviertas!