Ist es möglich, Tupel in Bash zu durchlaufen?
Als Beispiel wäre es großartig, wenn Folgendes funktionieren würde:
for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done
Gibt es eine Problemumgehung, mit der ich Tupel durchlaufen kann?
Ist es möglich, Tupel in Bash zu durchlaufen?
Als Beispiel wäre es großartig, wenn Folgendes funktionieren würde:
for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done
Gibt es eine Problemumgehung, mit der ich Tupel durchlaufen kann?
Antworten:
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5
Über diese Verwendung von set
(von man builtins
):
Alle nach der Optionsverarbeitung verbleibenden Argumente werden als Werte für die Positionsparameter behandelt und in der Reihenfolge $ 1, $ 2, ... $ n zugewiesen
Das IFS=","
setzt das Feldtrennzeichen so, dass jedes $i
in $1
und $2
korrekt segmentiert wird.
Über diesen Blog .
Bearbeiten: korrektere Version, wie von @SLACEDIAMOND vorgeschlagen:
$ 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
IFS
sollte gespeichert und auf den ursprünglichen Wert zurückgesetzt werden, wenn dies in der Befehlszeile ausgeführt wird. Außerdem kann das Neue IFS
einmal festgelegt werden, bevor die Schleife ausgeführt wird, und nicht bei jeder Iteration.
set -- $i
IFS
, setzen Sie es nur für den set
Befehl : for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done
. Bitte bearbeiten Sie Ihre Antwort: Wenn alle Leser nur eine der aufgelisteten Lösungen auswählen würden, macht es keinen Sinn, den vollständigen Entwicklungsverlauf lesen zu müssen. Danke für diesen coolen Trick!
tuples="a,1 b,2 c,3"
und IFS=','
wie in der bearbeiteten Version setze und anstatt zu c,3 e,5
verwenden $tuples
, druckt es überhaupt nicht gut. Wenn ich jedoch IFS=','
direkt nach dem do
Schlüsselwort in die for-Schleife setze , funktioniert dies sowohl bei der Verwendung $tuples
als auch bei der Verwendung von Wurfwerten. Ich dachte nur, es lohnt sich zu sagen.
IFS
zum Teilen von Iterationen verwendet wird. dh wenn Sie eine Schleife über ein Array wie arr=("c,3" "e,5")
und IFS
vor die for-Schleife stellen, ist der Wert von $i
nur c
und e
, es wird aufgeteilt 3
und wird 5
daher set
nicht korrekt analysiert, da $i
nichts zu analysieren ist. Dies bedeutet, dass, wenn die zu iterierenden Werte nicht inline sind, die Werte in IFS
die Schleife eingefügt werden sollen und der externe Wert das beabsichtigte Trennzeichen für die zu iterierende Variable berücksichtigen sollte. In den Fällen $tuples
sollte es einfach sein, IFS=
was Standard ist und auf Leerzeichen aufteilt.
Ich glaube, diese Lösung ist ein wenig sauberer als die anderen, die eingereicht wurden. H / t in diesem Bash-Styleguide, um zu veranschaulichen, wie read verwendet werden kann, um Zeichenfolgen an einem Trennzeichen zu teilen und sie einzelnen Variablen zuzuweisen.
for i in c,3 e,5; do
IFS=',' read item1 item2 <<< "${i}"
echo "${item1}" and "${item2}"
done
Basierend auf der Antwort von @ eduardo-ivanec ohne Einstellen / Zurücksetzen von IFS
könnte man einfach Folgendes tun:
for i in "c 3" "e 5"
do
set -- $i
echo $1 and $2
done
Die Ausgabe:
c and 3
e and 5
Verwenden Sie ein assoziatives Array (auch als Dictionary / HashMap bezeichnet):
declare -A pairs=(
[c]=3
[e]=5
)
for key in "${!pairs[@]}"; do
value="${pairs[$key]}"
echo "key is $key and value is $value"
done
Funktioniert für bash4.0 +.
Wenn Sie Tripel anstelle von Paaren benötigen, können Sie den allgemeineren Ansatz verwenden:
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
GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0)
Ihrer Information , dies funktionierte nicht für mich auf einem Mac mit , der über Brew installiert wurde, also YMMV.
GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)
ab Ubuntu 14.04 im Docker-Container.
-A
haben wir-a
.
declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)
usw. Ich habe es noch nicht im Terminal ausprobiert, aber ich denke, es sollte funktionieren.
c=('a' 'c')
n=(3 4 )
for i in $(seq 0 $((${#c[*]}-1)))
do
echo ${c[i]} ${n[i]}
done
Könnte manchmal praktischer sein.
Um den ugly
Teil zu erklären , wie in den Kommentaren angegeben:
seq 0 2 erzeugt die Folge von Zahlen 0 1 2. $ (cmd) ist eine Befehlssubstitution, also für dieses Beispiel die Ausgabe von seq 0 2
, die die Zahlenfolge ist. Aber was ist die Obergrenze, die $((${#c[*]}-1))
?
$ ((etwas)) ist eine arithmetische Erweiterung, also ist $ ((3 + 4)) 7 usw. Unser Ausdruck ist ${#c[*]}-1
also etwas - 1. Ziemlich einfach, wenn wir wissen, was ${#c[*]}
ist.
c ist ein Array, c [*] ist nur das gesamte Array, $ {# c [*]} ist die Größe des Arrays, in unserem Fall 2. Jetzt rollen wir alles zurück:for i in $(seq 0 $((${#c[*]}-1)))
ist for i in $(seq 0 $((2-1)))
ist for i in $(seq 0 1)
ist for i in 0 1
. Weil das letzte Element im Array einen Index hat, der der Länge des Arrays entspricht - 1.
for i in $(seq 0 $(($#c[*]}-1))); do [...]
Verwenden von GNU Parallel:
parallel echo {1} and {2} ::: c e :::+ 3 5
Oder:
parallel -N2 echo {1} and {2} ::: c 3 e 5
Oder:
parallel --colsep , echo {1} and {2} ::: c,3 e,5
gnu parallel
brew install parallel
Verwendung printf
in einer Prozesssubstitution:
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
Oben erfordert bash
. Wenn bash
es nicht verwendet wird, verwenden Sie eine einfache Pipeline:
printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done
do echo $key $value
done < file_discriptor
beispielsweise:
$ 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
Ein bisschen komplizierter, kann aber nützlich sein:
a='((c,3), (e,5))'
IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done
Was aber, wenn das Tupel größer ist als das k / v, das ein assoziatives Array enthalten kann? Was ist, wenn es 3 oder 4 Elemente sind? Man könnte dieses Konzept erweitern:
###---------------------------------------------------
### 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
Dann würde die Ausgabe ungefähr so aussehen:
$ ./assoc-array-tinkering.sh
Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4
Und die Anzahl der Elemente ist jetzt unbegrenzt. Keine Suche nach Stimmen; nur laut denken. REF1 , REF2
In Fällen, in denen meine Tupeldefinitionen komplexer sind, bevorzuge ich sie in einem Heredoc:
while IFS=", " read -ra arr; do
echo "${arr[0]} and ${arr[1]}"
done <<EOM
c, 3
e, 5
EOM
Dies kombiniert das Schleifen über Zeilen eines Heredocs mit dem Teilen der Zeilen bei einem gewünschten Trennzeichen .