Durchlaufen von Arrays, Drucken von Index und Wert

183

Ich möchte so etwas machen:

foo=( )
foo[0]="bar"
foo[35]="baz"
for((i=0;i<${#foo[@]};i++))
do
    echo "$i: ${foo[$i]}"
done
# Output:
# 0: bar
# 1: 

Dann habe ich versucht, es mit for in zu durchlaufen:

foo=( )
foo[0]="bar"
foo[35]="baz"
for i in ${foo[@]}
do
    echo "?: $i"
done
# Output:
# ?: bar
# ?: naz

aber hier kenne ich den indexwert nicht.

Ich weiß, du könntest so etwas

foo=( )
foo[0]="bar"
foo[35]="baz"
declare -p foo
# Output:
# declare -a foo='([0]="bar" [35]="baz")'

Aber kannst du es nicht anders machen?

Tyilo
quelle

Antworten:

315

Sie würden die Array-Schlüssel mit "${!foo[@]}"( Referenz ) finden, also:

for i in "${!foo[@]}"; do 
  printf "%s\t%s\n" "$i" "${foo[$i]}"
done

Dies bedeutet, dass Indizes vorhanden sind, $iwährend auf die Elemente selbst zugegriffen werden muss${foo[$i]}

Glenn Jackman
quelle
6
Wichtiger Hinweis: Eine durch Leerzeichen getrennte Liste von Wörtern ist zwar iterierbar, aber kein Array. Wickeln Sie es so ein (a b c), um es in ein Array zu konvertieren.
Breedly
4
Die Verwendung von [@]und doppelten Anführungszeichen bedeutet, dass es sich nicht um eine "durch Leerzeichen getrennte Liste von Wörtern" handelt. Sie erhalten die Liste der tatsächlichen Array-Schlüssel, auch wenn die einzelnen Schlüssel Leerzeichen enthalten.
Glenn Jackman
@glennjackman können Sie dies mehr erklärenThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya
1
"${foo[@]}"Nimmt die Variable (Array) foound erweitert sie als Array, wobei die Identität ihrer Elemente beibehalten wird, dh sie nicht in Leerzeichen aufgeteilt werden. Wenn foo=(x 'y z'), dann f "${foo[@]}"ruft fmit zwei Argumenten auf, xund 'y z'. Metadatenabfragen mögen "${!foo[@]}"und "${#foo[@]}"wirken ähnlich foowie ein Array.
BallpointBen
Richtige Antwort, aber dieser Hinweis ist unergründlich. Diese Antwort erklärt nur hilfreich: " "${!foo[@]}"ist die Liste aller im Array festgelegten Indizes".
pjhsea
16

Sie können immer den Iterationsparameter verwenden:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done
Eyal Ch
quelle
5
((ITER++))in der modernen Bash
David Tonhofer
Warum nach dem Inkrementieren? Sie möchten nur, dass der Wert erhöht wird, daher ist ((++ ITER)) direkter eine Aussage darüber, was Sie tun möchten ...
MikeW
3
Nein nicht immer". Ein Hash kann "Löcher" haben, was bedeutet, dass nicht alle Zahlen ein Index sind. In Ihrem Beispiel ist $ {ITER} nicht immer der Index von $ {I}.
Marco
10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done
Aruy Aruy
quelle
Ihre Antwort ist sicherlich eine kleine Erklärung wert. Bitte beziehen Sie sich auf stackoverflow.com/help/how-to-answer . Kommentare würden helfen, durchsuchbare Inhalte zu erstellen.
J. Chomel
3
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage in Zukunft für Leser beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen. Bitte versuchen Sie auch, Ihren Code nicht mit erklärenden Kommentaren zu überfüllen. Dies verringert die Lesbarkeit sowohl des Codes als auch der Erklärungen!
Kayess
2

In Bash 4 können Sie assoziative Arrays verwenden:

declare -A foo
foo[0]="bar"
foo[35]="baz"
for key in "${!foo[@]}"
do
    echo "key: $key, value: ${foo[$key]}"
done

# output
# $ key: 0, value bar.
# $ key: 35, value baz.

In Bash 3 funktioniert dies (funktioniert auch in zsh):

map=( )
map+=("0:bar")
map+=("35:baz")

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done
mattmc3
quelle
1
users=("kamal" "jamal" "rahim" "karim" "sadia")
index=()
t=-1

for i in ${users[@]}; do
  t=$(( t + 1 ))
  if [ $t -eq 0 ]; then
    for j in ${!users[@]}; do
      index[$j]=$j
    done
  fi
  echo "${index[$t]} is $i"
done
code4mk
quelle
1
Das ist falsch! Die innere Schleife ist nutzlos und kann zu falschen Ergebnissen führen!
F. Hauri
1

Einfacher einzeiliger Trick zum Dumping des Arrays

Ich habe einen Wert mit Leerzeichen hinzugefügt:

foo=()
foo[12]="bar"
foo[42]="foo bar baz"
foo[35]="baz"

Ich, für schnell Dump Arrays oder assoziative Arrays, die ich benutze

Dieser einzeilige Befehl:

paste <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")

Wird rendern:

12  bar
35  baz
42  foo bar baz

Erklärt

  • printf "%s\n" "${!foo[@]}"druckt alle durch einen Zeilenumbruch getrennten Schlüssel ,
  • printf "%s\n" "${foo[@]}"druckt alle durch einen Zeilenumbruch getrennten Werte ,
  • paste <(cmd1) <(cmd2)führt die Ausgabe von cmd1und cmd2Zeile für Zeile zusammen.

Tunning

Dies könnte durch -dSchalter eingestellt werden:

paste -d : <(printf "%s\n" "${!foo[@]}") <(printf "%s\n" "${foo[@]}")
12:bar
35:baz
42:foo bar baz

oder auch:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[35]='baz'
foo[42]='foo bar baz'

Das assoziative Array funktioniert genauso:

declare -A bar=([foo]=snoopy [bar]=nice [baz]=cool [foo bar]='Hello world!')
paste -d = <(printf "bar[%s]\n" "${!bar[@]}") <(printf '"%s"\n' "${bar[@]}")
bar[foo bar]="Hello world!"
bar[foo]="snoopy"
bar[bar]="nice"
bar[baz]="cool"

Problem mit Zeilenumbrüchen oder Sonderzeichen

Leider gibt es mindestens eine Bedingung, die dazu führt, dass dies nicht mehr funktioniert: Wenn Variablen Zeilenumbrüche enthalten:

foo[17]=$'There is one\nnewline'

Der Befehl pastewird zeilenweise zusammengeführt, sodass die Ausgabe falsch wird:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "'%s'\n" "${foo[@]}")
foo[12]='bar'
foo[17]='There is one
foo[35]=newline'
foo[42]='baz'
='foo bar baz'

Für diese Arbeit können Sie %qanstelle des %szweiten printfBefehls (und des Whipe-Zitats) Folgendes verwenden:

paste -d = <(printf "foo[%s]\n" "${!foo[@]}") <(printf "%q\n" "${foo[@]}")

Wird perfekt machen:

foo[12]=bar
foo[17]=$'There is one\nnewline'
foo[35]=baz
foo[42]=foo\ bar\ baz

Von man bash:

          %q     causes  printf  to output the corresponding argument in a
                 format that can be reused as shell input.
F. Hauri
quelle
Meine Abstimmung für printf "%q\n" "${var[@]}" Newline war mein Problem!
Techno