Viniendo de los antecedentes de Python, esta es una pregunta muy útil.
John Jiang
5
Al ver esto cuatro años después, me pregunto si todavía no hay una mejor manera de hacerlo. Dios mio.
Giszmo
Casi 8 años después, también me pregunté si todavía no hay mejor manera de hacer esto. Pero esta respuesta de 2018 me parece bastante buena: stackoverflow.com/a/52228219/463994
MountainX
Respuestas:
87
$ for i in c,3 e,5; do IFS=","; set -- $i; echo$1 and $2; done
c and 3
e and 5
Acerca de este uso de set(de man builtins):
Los argumentos restantes después del procesamiento de la opción se tratan como valores para los parámetros posicionales y se asignan, en orden, a $ 1, $ 2, ... $ n
La IFS=","fija el separador de campo por lo que cada $iconsigue segmentado en $1y $2correctamente.
Agradable: solo quiero señalar que IFSdebe guardarse y restablecerse a su valor original si se ejecuta en la línea de comando. Además, el nuevo IFSse puede configurar una vez, antes de que se ejecute el ciclo, en lugar de cada iteración.
Egg Exactamente
1
En caso de que alguno de los $ i comience con un guión, es más seguroset -- $i
glenn jackman
1
En lugar de ahorro IFS, sólo se establece que para el setcomando: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Edite su respuesta: si todos los lectores eligieran solo una de las soluciones enumeradas, no tiene sentido tener que leer el historial de desarrollo completo. ¡Gracias por este genial truco!
cfi
Si declaro tuples="a,1 b,2 c,3"y coloco IFS=','como en la versión editada, y en lugar de c,3 e,5usarlo $tuples, no se imprime nada bien. Pero en cambio, si pongo IFS=','justo después de la dopalabra clave en el ciclo for, funciona bien cuando se usan $tuplesvalores además de literales. Solo pensé que valía la pena decirlo.
Simonlbc
@Simonlbc eso se debe a que el bucle for se usa IFSpara dividir iteraciones. es decir, si recorre una matriz como arr=("c,3" "e,5")y la coloca IFSantes del bucle for, el valor de $iserá solo cy e, se dividirá 3y, 5por setlo tanto , no analizará correctamente porque $ino tendrá nada que analizar. Esto significa que si los valores para iterar no están alineados, IFSdeben colocarse dentro del ciclo y el valor externo debe respetar el separador previsto para la variable sobre la que iterar. En los casos en $tuplesque debería ser simplemente IFS=cuál es el predeterminado y se divide en espacios en blanco.
hasta el
25
Creo que esta solución es un poco más limpia que las otras que se han enviado, h / t a esta guía de estilo de bash para ilustrar cómo se puede usar read para dividir cadenas en un delimitador y asignarlas a variables individuales.
for i in c,3 e,5; do
IFS=','read item1 item2 <<< "${i}"echo"${item1}" and "${item2}"done
Este enfoque me parece mucho más simple que el enfoque aceptado y más votado. ¿Hay alguna razón para no hacerlo de esta manera a diferencia de lo que sugirió @Eduardo Ivanec?
spurra
@spurra esta respuesta es 6 años y ½ más reciente, y se basa en ella. Crédito donde se debe.
Diego
1
@Diego soy consciente de eso. Está escrito explícitamente en la respuesta. Estaba preguntando si hay alguna razón para no usar este enfoque sobre la respuesta aceptada.
spurra
2
@spurra, querría usar la respuesta de Eduardo si el separador predeterminado (espacio, pestaña o nueva línea) no funciona para usted por alguna razón ( bash.cyberciti.biz/guide/$IFS )
Diego
11
Utilice una matriz asociativa (también conocida como diccionario / hashMap):
declare -A pairs=(
[c]=3
[e]=5
)
for key in"${!pairs[@]}"; do
value="${pairs[$key]}"echo"key is $key and value is $value"done
Funciona para bash4.0 +.
Si necesita triples en lugar de pares, puede utilizar el enfoque más general:
animals=(dog cat mouse)
declare -A sound=(
[dog]=barks
[cat]=purrs
[mouse]=cheeps
)
declare -A size=(
[dog]=big
[cat]=medium
[mouse]=small
)
for animal in"${animals[@]}"; doecho"$animal${sound[$animal]} and it is ${size[$animal]}"done
Para su información, esto no funcionó para mí en Mac con GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), que se instaló a través de brew, por lo que YMMV.
David
Sin embargo, funcionó GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)desde Ubuntu 14.04 dentro del contenedor Docker.
David
¿Parece que las versiones anteriores de bash o las que no admiten esta función funcionan con la base de índices? donde la clave es un número en lugar de una cadena. tldp.org/LDP/abs/html/declareref.html , y en lugar de -Anosotros tenemos -a.
David
David, eso parece. Creo que entonces puedes probar los índices de matriz para obtener "asociatividad". Me gusta, declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)etc. Aún no lo he probado en la terminal, pero creo que debería funcionar.
VasiliNovikov
7
c=('a''c')
n=(3 4 )
for i in $(seq 0 $((${#c[*]}-1)))
doecho${c[i]}${n[i]}done
A veces puede ser más útil.
Para explicar el ugly parte, como se indica en los comentarios:
seq 0 2 produce la secuencia de números 0 1 2. $ (cmd) es la sustitución de comandos, por lo que para este ejemplo la salida de seq 0 2, que es la secuencia numérica. Pero, ¿cuál es el límite superior, el $((${#c[*]}-1))?
$ ((algo)) es expansión aritmética, por lo que $ ((3 + 4)) es 7, etc. Nuestra expresión es ${#c[*]}-1, entonces algo: 1. Bastante simple, si sabemos qué${#c[*]} es.
c es una matriz, c [*] es solo la matriz completa, $ {# c [*]} es el tamaño de la matriz, que es 2 en nuestro caso. Ahora revertimos todo: for i in $(seq 0 $((${#c[*]}-1)))es for i in $(seq 0 $((2-1)))es for i in $(seq 0 1)es for i in 0 1. Porque el último elemento de la matriz tiene un índice que es la longitud de la matriz - 1.
deberías hacerfor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox
1
Vaya, esto gana el premio "El grupo más feo de personajes arbitrarios que he visto hoy". ¿Alguien quiere explicar qué hace exactamente esta abominación? Me perdí en el signo de almohadilla ...
koniiiik
1
@koniiiik: Explicación agregada.
usuario desconocido
6
$ echo'c,3;e,5;' | while IFS=','read -d';' i j; doecho"$i and $j"; done
c and 3
e and 5
Pero, ¿qué pasa si la tupla es mayor que el k / v que puede contener una matriz asociativa? ¿Y si son 3 o 4 elementos? Se podría ampliar este concepto:
###---------------------------------------------------### VARIABLES###---------------------------------------------------
myVars=(
'ya1,ya2,ya3,ya4''ye1,ye2,ye3,ye4''yo1,yo2,yo3,yo4'
)
###---------------------------------------------------### MAIN PROGRAM###---------------------------------------------------### Echo all elements in the array###---printf'\n\n%s\n'"Print all elements in the array..."for dataRow in"${myVars[@]}"; dowhile IFS=','read -r var1 var2 var3 var4; doprintf'%s\n'"$var1 - $var2 - $var3 - $var4"done <<< "$dataRow"done
Entonces la salida se vería así:
$ ./assoc-array-tinkering.sh
Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4
Y el número de elementos ahora es ilimitado. Sin buscar votos; solo pensando en voz alta. REF1 , REF2
Respuestas:
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done c and 3 e and 5
Acerca de este uso de
set
(deman builtins
):La
IFS=","
fija el separador de campo por lo que cada$i
consigue segmentado en$1
y$2
correctamente.A través de este blog .
Editar: versión más correcta, como lo sugiere @SLACEDIAMOND:
$ OLDIFS=$IFS; IFS=','; for i in c,3 e,5; do set -- $i; echo $1 and $2; done; IFS=$OLDIFS c and 3 e and 5
fuente
IFS
debe guardarse y restablecerse a su valor original si se ejecuta en la línea de comando. Además, el nuevoIFS
se puede configurar una vez, antes de que se ejecute el ciclo, en lugar de cada iteración.set -- $i
IFS
, sólo se establece que para elset
comando:for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done
. Edite su respuesta: si todos los lectores eligieran solo una de las soluciones enumeradas, no tiene sentido tener que leer el historial de desarrollo completo. ¡Gracias por este genial truco!tuples="a,1 b,2 c,3"
y colocoIFS=','
como en la versión editada, y en lugar dec,3 e,5
usarlo$tuples
, no se imprime nada bien. Pero en cambio, si pongoIFS=','
justo después de lado
palabra clave en el ciclo for, funciona bien cuando se usan$tuples
valores además de literales. Solo pensé que valía la pena decirlo.IFS
para dividir iteraciones. es decir, si recorre una matriz comoarr=("c,3" "e,5")
y la colocaIFS
antes del bucle for, el valor de$i
será soloc
ye
, se dividirá3
y,5
porset
lo tanto , no analizará correctamente porque$i
no tendrá nada que analizar. Esto significa que si los valores para iterar no están alineados,IFS
deben colocarse dentro del ciclo y el valor externo debe respetar el separador previsto para la variable sobre la que iterar. En los casos en$tuples
que debería ser simplementeIFS=
cuál es el predeterminado y se divide en espacios en blanco.Creo que esta solución es un poco más limpia que las otras que se han enviado, h / t a esta guía de estilo de bash para ilustrar cómo se puede usar read para dividir cadenas en un delimitador y asignarlas a variables individuales.
for i in c,3 e,5; do IFS=',' read item1 item2 <<< "${i}" echo "${item1}" and "${item2}" done
fuente
Según la respuesta dada por @ eduardo-ivanec sin configurar / restablecer
IFS
, uno podría simplemente hacer:for i in "c 3" "e 5" do set -- $i echo $1 and $2 done
La salida:
fuente
Utilice una matriz asociativa (también conocida como diccionario / hashMap):
declare -A pairs=( [c]=3 [e]=5 ) for key in "${!pairs[@]}"; do value="${pairs[$key]}" echo "key is $key and value is $value" done
Funciona para bash4.0 +.
Si necesita triples en lugar de pares, puede utilizar el enfoque más general:
animals=(dog cat mouse) declare -A sound=( [dog]=barks [cat]=purrs [mouse]=cheeps ) declare -A size=( [dog]=big [cat]=medium [mouse]=small ) for animal in "${animals[@]}"; do echo "$animal ${sound[$animal]} and it is ${size[$animal]}" done
fuente
GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0)
, que se instaló a través de brew, por lo que YMMV.GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)
desde Ubuntu 14.04 dentro del contenedor Docker.-A
nosotros tenemos-a
.declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)
etc. Aún no lo he probado en la terminal, pero creo que debería funcionar.c=('a' 'c') n=(3 4 ) for i in $(seq 0 $((${#c[*]}-1))) do echo ${c[i]} ${n[i]} done
A veces puede ser más útil.
Para explicar el
ugly
parte, como se indica en los comentarios:seq 0 2 produce la secuencia de números 0 1 2. $ (cmd) es la sustitución de comandos, por lo que para este ejemplo la salida de
seq 0 2
, que es la secuencia numérica. Pero, ¿cuál es el límite superior, el$((${#c[*]}-1))
?$ ((algo)) es expansión aritmética, por lo que $ ((3 + 4)) es 7, etc. Nuestra expresión es
${#c[*]}-1
, entonces algo: 1. Bastante simple, si sabemos qué${#c[*]}
es.c es una matriz, c [*] es solo la matriz completa, $ {# c [*]} es el tamaño de la matriz, que es 2 en nuestro caso. Ahora revertimos todo:
for i in $(seq 0 $((${#c[*]}-1)))
esfor i in $(seq 0 $((2-1)))
esfor i in $(seq 0 1)
esfor i in 0 1
. Porque el último elemento de la matriz tiene un índice que es la longitud de la matriz - 1.fuente
for i in $(seq 0 $(($#c[*]}-1))); do [...]
$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done c and 3 e and 5
fuente
Usando GNU Parallel:
parallel echo {1} and {2} ::: c e :::+ 3 5
O:
parallel -N2 echo {1} and {2} ::: c 3 e 5
O:
parallel --colsep , echo {1} and {2} ::: c,3 e,5
fuente
gnu parallel
brew install parallel
Usando
printf
en una sustitución de proceso:while read -r k v; do echo "Key $k has value: $v" done < <(printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3')
Lo anterior requiere
bash
. Sibash
no se está utilizando, utilice una canalización simple:printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' | while read -r k v; do echo "Key $k has value: $v"; done
fuente
do echo $key $value done < file_discriptor
por ejemplo:
$ while read key value; do echo $key $value ;done <<EOF > c 3 > e 5 > EOF c 3 e 5 $ echo -e 'c 3\ne 5' > file $ while read key value; do echo $key $value ;done <file c 3 e 5 $ echo -e 'c,3\ne,5' > file $ while IFS=, read key value; do echo $key $value ;done <file c 3 e 5
fuente
Un poco más complicado, pero puede ser útil:
a='((c,3), (e,5))' IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done
fuente
Pero, ¿qué pasa si la tupla es mayor que el k / v que puede contener una matriz asociativa? ¿Y si son 3 o 4 elementos? Se podría ampliar este concepto:
###--------------------------------------------------- ### VARIABLES ###--------------------------------------------------- myVars=( 'ya1,ya2,ya3,ya4' 'ye1,ye2,ye3,ye4' 'yo1,yo2,yo3,yo4' ) ###--------------------------------------------------- ### MAIN PROGRAM ###--------------------------------------------------- ### Echo all elements in the array ###--- printf '\n\n%s\n' "Print all elements in the array..." for dataRow in "${myVars[@]}"; do while IFS=',' read -r var1 var2 var3 var4; do printf '%s\n' "$var1 - $var2 - $var3 - $var4" done <<< "$dataRow" done
Entonces la salida se vería así:
$ ./assoc-array-tinkering.sh Print all elements in the array... ya1 - ya2 - ya3 - ya4 ye1 - ye2 - ye3 - ye4 yo1 - yo2 - yo3 - yo4
Y el número de elementos ahora es ilimitado. Sin buscar votos; solo pensando en voz alta. REF1 , REF2
fuente
En los casos en que mis definiciones de tupla son más complejas, prefiero tenerlas en un heredoc:
while IFS=", " read -ra arr; do echo "${arr[0]} and ${arr[1]}" done <<EOM c, 3 e, 5 EOM
Esto combina bucle sobre las líneas de un heredoc con dividir las líneas en algún carácter de separación deseado .
fuente