Eseguire il loop delle tuple in bash?


89

È possibile eseguire il ciclo su tuple in bash?

Ad esempio, sarebbe fantastico se funzionasse quanto segue:

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

Esiste una soluzione alternativa che in qualche modo mi consenta di eseguire il ciclo su tuple?


4
Venendo dallo sfondo di Python, questa è davvero una domanda molto utile!
— John Jiang

5
guardando questo quattro anni dopo mi chiedo se non ci sia ancora modo migliore per farlo. Oh mio Dio.
— Giszmo

Quasi 8 anni dopo mi sono anche chiesto se non ci fosse ancora modo migliore per farlo. Ma questa risposta del 2018 mi sembra piuttosto buona: stackoverflow.com/a/52228219/463994
— MountainX

Risposte:


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

A proposito di questo uso di set(da man builtins):

Tutti gli argomenti rimanenti dopo l'elaborazione delle opzioni vengono trattati come valori per i parametri posizionali e vengono assegnati, nell'ordine, a $ 1, $ 2, ... $ n

Gli IFS=","insiemi il separatore di campo così ogni $iviene segmentato in $1e$2 correttamente.

Tramite questo blog .

Modifica: versione più corretta, come suggerito da @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
Bello: voglio solo sottolineare che IFSdovrebbe essere salvato e ripristinato al suo valore originale se questo viene eseguito sulla riga di comando. Inoltre, il nuovo IFSpuò essere impostato una volta, prima che il ciclo venga eseguito, anziché ogni iterazione.
— Eggx esattamente il

1
Nel caso in cui uno qualsiasi dei $ i inizi con un trattino, è più sicuroset -- $i
— glenn jackman,

1
Invece di risparmio IFS, impostare solo per il setcomando: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Modifica la tua risposta: se tutti i lettori scegliessero solo una delle soluzioni elencate, non ha senso dover leggere l'intera cronologia dello sviluppo. Grazie per questo fantastico trucco!
— cfi

Se dichiaro tuples="a,1 b,2 c,3"e metto IFS=','come nella versione modificata, e invece di c,3 e,5usarlo $tuplesnon stampa affatto bene. Ma invece se metto IFS=','subito dopo la doparola chiave nel ciclo for, funziona bene quando si utilizzano $tuplesvalori così come letterali. Ho solo pensato che valesse la pena dirlo.
— Simonlbc

@ Simonlbc è perché il ciclo for utilizza IFSper dividere le iterazioni. Ad esempio, se esegui un ciclo su un array come arr=("c,3" "e,5")e lo metti IFSprima del ciclo for, il valore di $isarà solo ce e, verrà suddiviso 3e 5quindi setnon verrà analizzato correttamente perché $inon avrà nulla da analizzare. Ciò significa che se i valori da iterare non sono inline, il IFSdeve essere inserito nel ciclo e il valore esterno deve rispettare il separatore previsto per la variabile su cui iterare. In questi casi $tuplesdovrebbe essere semplicemente il IFS=valore predefinito e suddiviso in spazi bianchi.
— untore

26

Credo che questa soluzione sia un po 'più pulita delle altre che sono state presentate, h / t a questa guida di stile bash per illustrare come read può essere usato per dividere le stringhe in un delimitatore e assegnarle a singole variabili.

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

20

Sulla base della risposta data da @ eduardo-ivanec senza impostare / resettare IFS, si potrebbe semplicemente fare:

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

Il risultato:

c and 3
e and 5

Questo approccio mi sembra molto più semplice dell'approccio accettato e più votato. C'è qualche motivo per non farlo in questo modo rispetto a quanto suggerito da @Eduardo Ivanec?
— spurra

@spurra questa risposta è di 6 anni e mezzo più recente e si basa su di essa. Credito dove è dovuto.
— Diego

1
@Diego ne sono consapevole. È scritto esplicitamente nella risposta. Chiedevo se ci fosse qualche ragione per non utilizzare questo approccio rispetto alla risposta accettata.
— spurra

2
@spurra vorresti usare la risposta di Eduardo se il separatore predefinito (spazio, tabulazione o nuova riga) non funziona per te per qualche motivo ( bash.cyberciti.biz/guide/$IFS )
— Diego

13

Usa array associativo (noto anche come dizionario / 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

Funziona per bash4.0 +.


Se hai bisogno di triple invece delle coppie, puoi utilizzare l'approccio più generale:

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

Cordiali saluti, questo non ha funzionato per me su Mac con GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), che è stato installato tramite brew, quindi YMMV.
— David

tuttavia ha funzionato GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)da Ubuntu 14.04 all'interno del contenitore docker.
— David

sembra che le versioni precedenti di bash o quelle che non supportano questa funzionalità funzionino con l'indice? dove la chiave è un numero anziché una stringa. tldp.org/LDP/abs/html/declareref.html , e invece -Aabbiamo -a.
— David

David, sembra così. Penso che puoi provare gli indici degli array per ottenere "associatività" allora. Come declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)ecc. Non l'ho ancora provato nel terminale, ma penso che dovrebbe funzionare.
— VasiliNovikov

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

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

A volte potrebbe essere più utile.

Per spiegare la uglyparte, come notato nei commenti:

seq 0 2 produce la sequenza di numeri 0 1 2. $ (cmd) è la sostituzione del comando, quindi per questo esempio l'output di seq 0 2, che è la sequenza numerica. Ma qual è il limite superiore, il $((${#c[*]}-1))?

$ ((qualcosa)) è l'espansione aritmetica, quindi $ ((3 + 4)) è 7 ecc. La nostra espressione è ${#c[*]}-1, quindi qualcosa - 1. Abbastanza semplice, se sappiamo cos'è ${#c[*]}.

c è un array, c [*] è solo l'intero array, $ {# c [*]} è la dimensione dell'array che è 2 nel nostro caso. Ora ripristiniamo tutto: 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. Perché l'ultimo elemento dell'array ha un indice che è la lunghezza dell'array - 1.


1
dovresti farlofor i in $(seq 0 $(($#c[*]}-1))); do [...]
— reox

1
Wow, questo vince il premio "Il più brutto mazzo di personaggi arbitrari che abbia mai visto oggi". Qualcuno vuole spiegare cosa fa esattamente questo abominio? Mi sono perso al cancelletto ...
— koniiiik

1
@koniiiik: spiegazione aggiunta.
— utente sconosciuto

"Neu-Perl: The Revenge" Penso che in realtà sarebbe più chiaro in perl. :-)
— Buzz Moschetti

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

Utilizzo di 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

1
Nessun amore per questo? ben mi ha fatto superare l'inerzia e installaregnu parallel
— StephenBoesch

2
brew install parallel
— StephenBoesch

2

Utilizzo printfin una sostituzione di 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

Sopra richiede bash. Se bashnon viene utilizzato, utilizzare la pipeline semplice:

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

1
Sì! Anubhava, dolce genio!
— Scott

1
do echo $key $value
done < file_discriptor

per esempio:

$ 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

Un po 'più complicato, ma può essere utile:

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

Ma cosa succede se la tupla è maggiore del k / v che può contenere un array associativo? E se fossero 3 o 4 elementi? Si potrebbe espandere questo concetto:

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

Quindi l'output sarebbe simile a:

$ ./assoc-array-tinkering.sh 

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

E il numero di elementi ora è illimitato. Non sto cercando voti; solo pensando ad alta voce. RIF1 , RIF2


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.