¿Bucle sobre tuplas en bash?

88

¿Es posible recorrer tuplas en bash?

Como ejemplo, sería genial si lo siguiente funcionara:

for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done

¿Existe alguna solución que de alguna manera me permita recorrer tuplas?

Franco
fuente
4
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.

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
Eduardo Ivanec
fuente
7
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
Grant Humphries
fuente
17

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:

c and 3
e and 5
MZHm
fuente
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[@]}"; do
  echo "$animal ${sound[$animal]} and it is ${size[$animal]}"
done
VasiliNovikov
fuente
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)))
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)))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.

usuario desconocido
fuente
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; do echo "$i and $j"; done
c and 3
e and 5
kev
fuente
3

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
Ole Tange
fuente
1
¿Sin amor por esto? bueno me hizo superar la inercia e instalargnu parallel
StephenBoesch
2
brew install parallel
StephenBoesch
2

Usando printfen 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')

Key key1 has value: val1
Key key2 has value: val2
Key key3 has value: val3

Lo anterior requiere bash. Si bashno 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
anubhava
fuente
1
¡Si! ¡Anubhava, dulce genio!
Scott
1
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
prodriguez903
fuente
0

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
Diego Torres Milano
fuente
0

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

todd_dsm
fuente