Loop sobre tuplas no bash?


88

É possível fazer um loop sobre tuplas no bash?

Por exemplo, seria ótimo se o seguinte funcionasse:

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

Existe uma solução alternativa que de alguma forma me permite fazer um loop sobre as tuplas?


4
Vindo do background do python, esta é uma questão muito útil!
John Jiang

5
olhando para isso quatro anos depois, me pergunto se ainda não há maneira melhor de fazer isso. AMD.
Giszmo

Quase 8 anos depois, também me perguntei se ainda não havia maneira melhor de fazer isso. Mas esta resposta de 2018 parece muito boa para mim: stackoverflow.com/a/52228219/463994
MountainX

Respostas:


87
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5

Sobre este uso de set(de man builtins):

Quaisquer argumentos restantes após o processamento da opção são tratados como valores para os parâmetros posicionais e são atribuídos, na ordem, a $ 1, $ 2, ... $ n

O IFS=","define o separador de campo para que todos sejam $isegmentados $1e $2corretamente.

Por meio deste blog .

Edit: versão mais correta, conforme sugerido por @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

7
Bom - só quero apontar IFSdeve ser salvo e redefinido para seu valor original se for executado na linha de comando. Além disso, o novo IFSpode ser definido uma vez, antes da execução do loop, em vez de a cada iteração.
Eggxactly

1
No caso de $ i começar com um hífen, é mais seguroset -- $i
glenn jackman

1
Em vez de salvar IFSapenas configurá-lo para o setcomando: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Edite sua resposta: Se todos os leitores escolherem apenas uma das soluções listadas, não há sentido em ter que ler o histórico de desenvolvimento completo. Obrigado por este truque legal!
cfi

Se eu declaro tuples="a,1 b,2 c,3"e coloco IFS=','como na versão editada, e ao invés de c,3 e,5usar $tuplesnão imprime bem. Mas, em vez disso, se eu colocar IFS=','logo após a dopalavra - chave no loop for, ele funcionará bem ao usar $tuplesvalores literais. Achei que valia a pena dizer.
Simonlbc

@Simonlbc é porque o loop for usa IFSpara dividir iterações. ie Se você loop sobre uma matriz como arr=("c,3" "e,5")e colocar IFSantes do loop, o valor $iserá apenas ce e, ele vai se separou 3e 5por isso setnão irá analisar corretamente porque $inão vai ter nada para analisar. Isso significa que, se os valores a serem iterados não forem sequenciais, o IFSdeve ser colocado dentro do loop e o valor externo deve respeitar o separador pretendido para a variável a ser iterada. Nesse caso $tuples, deve ser simplesmente o IFS=que é padrão e se divide em espaços em branco.
desabafar em

25

Eu acredito que esta solução é um pouco mais limpa do que as outras que foram enviadas, h / t para este guia de estilo bash para ilustrar como read pode ser usado para dividir strings em um delimitador e atribuí-las a variáveis ​​individuais.

for i in c,3 e,5; do 
    IFS=',' read item1 item2 <<< "${i}"
    echo "${item1}" and "${item2}"
done

17

Baseado na resposta dada por @ eduardo-ivanec sem configurar / redefinir o IFS, pode-se simplesmente fazer:

for i in "c 3" "e 5"
do
    set -- $i
    echo $1 and $2
done

A saída:

c and 3
e and 5

Essa abordagem me parece muito mais simples do que a abordagem aceita e mais votada. Existe alguma razão para não fazer assim ao contrário do que sugeriu @Eduardo Ivanec?
spurra

@spurra essa resposta é 6 anos e meio mais recente e é baseada nela. Crédito onde é devido.
Diego

1
@Diego estou ciente disso. Está explicitamente escrito na resposta. Eu estava perguntando se há alguma razão para não usar essa abordagem em vez da resposta aceita.
spurra

2
@spurra você gostaria de usar a resposta de Eduardo se o separador padrão (espaço, tabulação ou nova linha) não funcionar para você por algum motivo ( bash.cyberciti.biz/guide/$IFS )
Diego

11

Use array associativo (também conhecido como dicionário / 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 +.


Se precisar de triplos em vez de pares, você pode usar a abordagem mais geral:

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

FYI, isso não funcionou para mim no Mac com o GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), que foi instalado via brew, então YMMV.
David,

no entanto, funcionou a GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)partir do Ubuntu 14.04 dentro do docker container.
David,

parece que as versões mais antigas do bash ou as que não oferecem suporte a esse recurso funcionam com base no índice? onde a chave é um número em vez de uma string. tldp.org/LDP/abs/html/declareref.html , e em vez de -Anós temos -a.
David,

David, parece que sim. Acho que você pode tentar índices de matriz para obter "associatividade" então. Como declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)etc. Ainda não tentei no terminal, mas acho que deve funcionar.
VasiliNovikov

7
c=('a' 'c')
n=(3    4 )

for i in $(seq 0 $((${#c[*]}-1)))
do
    echo ${c[i]} ${n[i]}
done

Às vezes pode ser mais útil.

Para explicar o ugly parte, conforme observado nos comentários:

seq 0 2 produz a sequência de números 0 1 2. $ (cmd) é a substituição do comando, portanto, para este exemplo, a saída de seq 0 2, que é a sequência numérica. Mas qual é o limite superior, o $((${#c[*]}-1))?

$ ((somthing)) é a expansão aritmética, então $ ((3 + 4)) é 7 etc. Nossa Expressão é ${#c[*]}-1, então algo - 1. Muito simples, se sabemos o que${#c[*]} é.

c é um array, c [*] é apenas o array inteiro, $ {# c [*]} é o tamanho do array que é 2 em nosso caso. Agora revertemos tudo: for i in $(seq 0 $((${#c[*]}-1)))é for i in $(seq 0 $((2-1)))é for i in $(seq 0 1)é for i in 0 1. Porque o último elemento na matriz tem um índice que é o comprimento da matriz - 1.


1
você deveria fazerfor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox de

1
Uau, isso ganhou o prêmio de “Grupo de Personagens Arbitrários Mais Feios que Eu Vi hoje”. Alguém se importa em explicar o que exatamente essa abominação faz? Eu me perdi no sinal de hash ...
koniiiik

1
@koniiiik: Explicação adicionada.
usuário desconhecido

6
$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
c and 3
e and 5

3

Usando o GNU Parallel:

parallel echo {1} and {2} ::: c e :::+ 3 5

Ou:

parallel -N2 echo {1} and {2} ::: c 3 e 5

Ou:

parallel --colsep , echo {1} and {2} ::: c,3 e,5

1
Sem amor por isso? bem me fez superar a inércia e instalargnu parallel
StephenBoesch

2
brew install parallel
StephenBoesch

2

Usando printfem uma substituição de processo:

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

Acima requer bash. Se bashnão estiver sendo usado, use pipeline simples:

printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done

1
Sim! Anubhava, seu doce gênio!
Scott

1
do echo $key $value
done < file_discriptor

por exemplo:

$ 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

0

Um pouco mais envolvido, mas pode 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

0

Mas e se a tupla for maior do que k / v que uma matriz associativa pode conter? E se forem 3 ou 4 elementos? Pode-se expandir este conceito:

###---------------------------------------------------
### 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

Então, a saída seria algo como:

$ ./assoc-array-tinkering.sh 

Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4

E o número de elementos agora é ilimitado. Não procurando votos; apenas pensando em voz alta. REF1 , REF2


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.