Pętla nad tablicami, wypisywanie zarówno indeksu, jak i wartości


184

Chcę zrobić coś takiego:

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

Następnie próbowałem przejść przez to za pomocą for:

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

ale tutaj nie znam wartości indeksu.

Wiem, że możesz coś takiego

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

ale czy nie możesz tego zrobić w inny sposób?

Odpowiedzi:


317

Znajdziesz klucze tablicy z "${!foo[@]}"( odwołanie ), więc:

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

Co oznacza, że ​​indeksy będą dostępne, $ipodczas gdy do samych elementów należy uzyskać dostęp za pośrednictwem${foo[$i]}


6
Ważna uwaga, choć iterowalna, lista słów oddzielonych spacjami nie jest tablicą. Zawiń tak, (a b c)aby przekonwertować na tablicę.
Breedly,

4
Użycie [@]i podwójne cudzysłowy oznacza, że ​​nie jest to „lista słów oddzielona spacjami”. Otrzymasz listę rzeczywistych kluczy tablicy, nawet jeśli poszczególne klucze zawierają spacje.
glenn jackman

@glennjackman czy możesz to wyjaśnićThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya,

1
"${foo[@]}"przyjmuje zmienną (tablica) fooi rozwija ją jako tablicę, zachowując tożsamość swoich elementów, tj. nie dzieląc ich na białe znaki. Jeśli foo=(x 'y z'), to f "${foo[@]}"wywołuje fz dwoma argumentami xi 'y z'. Zapytania o metadane, takie jak "${!foo[@]}"i "${#foo[@]}"podobnie, działają foojak tablica.
BallpointBen,

Prawidłowa odpowiedź, ale to odniesienie jest nieprzeniknione. Ta odpowiedź po prostu wyjaśnia: „ "${!foo[@]}"jest listą wszystkich indeksów ustawionych w tablicy”.
pjhsea

17

zawsze możesz użyć parametru iteracji:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done

5
((ITER++))in modern bash
David Tonhofer

Po co zwiększać? Chcesz tylko zwiększyć wartość, dlatego ((++ ITER)) jest bardziej bezpośrednim stwierdzeniem tego, co chcesz zrobić ...
MikeW

3
Nie, nie „ZAWSZE”. Hash może mieć „dziury”, co oznacza, że ​​nie wszystkie liczby są indeksem. W twoim przykładzie $ {ITER} nie zawsze jest indeksem $ {I}.
Marco

10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done

Twoja odpowiedź z pewnością jest warta wyjaśnienia. Prosimy odnieść się do stackoverflow.com/help/how-to-answer . Komentarze pomogą stworzyć treść do przeszukiwania.
J. Chomel,

3
Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu. Staraj się również nie tłoczyć kodu objaśniającymi komentarzami, co zmniejsza czytelność zarówno kodu, jak i objaśnień!
kayess,

2

W bash 4 możesz używać tablic asocjacyjnych:

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.

W bash 3 to działa (działa również w Zsh):

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

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done

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

1
To jest źle! Pętla wewnętrzna jest bezużyteczna i może prowadzić do błędnych wyników!
F. Hauri

1

Prosta sztuczka z jednym wierszem do zrzucania tablicy

Dodałem jedną wartość do spacji:

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

Ja za szybki zrzut Tablice lub tablice asocjacyjne, których używam

To polecenie w jednym wierszu:

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

Wyrenderuje:

12  bar
35  baz
42  foo bar baz

Wyjaśnione

  • printf "%s\n" "${!foo[@]}"wypisze wszystkie klucze oddzielone znakiem nowej linii ,
  • printf "%s\n" "${foo[@]}"wypisze wszystkie wartości oddzielone znakiem nowej linii ,
  • paste <(cmd1) <(cmd2)scali dane wyjściowe cmd1i cmd2wiersz po wierszu.

Tuning

Można to dostroić -dprzełącznikiem:

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

lub nawet:

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

Tablica asocjacyjna będzie działać tak samo:

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 z znakami nowej linii lub znakami specjalnymi

Niestety, co najmniej jeden warunek powoduje, że to już nie działa: gdy zmienna zawiera znak nowej linii:

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

Polecenie pastebędzie łączyć wiersz po wierszu, więc dane wyjściowe staną się niepoprawne:

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'

Do tej pracy można użyć %qzamiast %sw drugim printfpoleceniu (i cytowanie):

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

Idealnie sprawdzi się:

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

Od man bash:

          %q     causes  printf  to output the corresponding argument in a
                 format that can be reused as shell input.

Mój głos na printf "%q\n" "${var[@]}" nową linię był moim problemem!
techno
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.