Цикл по массивам, печать как индекса, так и значения

184

Я хочу сделать что-то вроде этого:

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

Затем я попытался перебрать его, используя для в:

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

но здесь я не знаю значение индекса.

Я знаю, что ты мог что-то вроде

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

но вы не можете сделать это по-другому?

Tyilo
источник

Ответы:

317

Вы найдете ключи массива с "${!foo[@]}"( ссылкой ), так:

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

Это означает, что индексы будут в $iто время как сами элементы должны быть доступны через${foo[$i]}

Гленн Джекман
источник
6
Важное замечание, хотя итеративный, список слов, разделенных пробелами, не является массивом. Оберните это так, (a b c)чтобы преобразовать в массив.
Breedly
4
Использование [@]и двойных кавычек означает, что это не «список слов, разделенных пробелами». Вы получите список фактических ключей массива, даже если отдельные ключи содержат пробелы.
Гленн Джекман
@glennjackman вы можете это объяснить болееThe use of [@] and double quotes means it's not a "space separated list of words"
Kasun Siyambalapitiya
1
"${foo[@]}"берет переменную (массив) fooи расширяет ее как массив, сохраняя идентичность его элементов, т. е. не разбивая их на пробелы. Если foo=(x 'y z'), то f "${foo[@]}"вызывает fс двумя аргументами, xи 'y z'. Запросы метаданных как "${!foo[@]}"и "${#foo[@]}"аналогично действуют fooкак массив.
BallpointBen
Правильный ответ, но эта ссылка непостижима. Этот ответ услужливо объясняет: « "${!foo[@]}"это список всех индексов, установленных в массиве».
pjhsea
18

вы всегда можете использовать итерационный параметр:

ITER=0
for I in ${FOO[@]}
do  
    echo ${I} ${ITER}
    ITER=$(expr $ITER + 1)
done
Эяль Ч
источник
5
((ITER++))в современной баш
Дэвид Тонхофер
Почему постинкремент? Вам нужно только увеличить значение, следовательно ((++ ITER)) является более прямым выражением того, что вы хотите сделать ....
MikeW
3
Нет, не "ВСЕГДА". Хеш может иметь «дыры», что означает, что не все числа являются индексом. В вашем примере $ {ITER} не всегда является индексом $ {I}.
Марко
10
INDEX=0
for i in $list; do 
    echo ${INDEX}_$i
    let INDEX=${INDEX}+1
done
Aruy Aruy
источник
Ваш ответ, безусловно, заслуживает небольшого объяснения. Пожалуйста, обратитесь к stackoverflow.com/help/how-to-answer . Комментарии помогут создать контент для поиска.
J.
3
Хотя этот фрагмент кода может решить вопрос, в том числе объяснение действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин, по которым вы предлагаете код. Также постарайтесь не переполнять ваш код пояснительными комментариями, это снижает удобочитаемость кода и пояснений!
kayess
2

В bash 4 вы можете использовать ассоциативные массивы:

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.

В bash 3 это работает (также работает в zsh):

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

for keyvalue in "${map[@]}" ; do
    key=${keyvalue%%:*}
    value=${keyvalue#*:}
    echo "key: $key, value $value."
done
mattmc3
источник
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
источник
1
Это не верно! Внутренний цикл бесполезен и может привести к неверным результатам!
Ф. Хаури
1

Простой однострочный трюк для дампа массива

Я добавил одно значение с пробелами:

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

Я, для быстрого сброса массивы или ассоциативные массивы я использую

Эта однострочная команда:

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

Окажет:

12  bar
35  baz
42  foo bar baz

Разъяснения

  • printf "%s\n" "${!foo[@]}"напечатает все ключи, разделенные новой строкой ,
  • printf "%s\n" "${foo[@]}"напечатает все значения, разделенные новой строкой ,
  • paste <(cmd1) <(cmd2)объединит вывод cmd1и cmd2построчно.

Tunning

Это может быть настроено -dпереключателем:

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

или даже:

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

Ассоциативный массив будет работать так же:

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"

Проблема с переводом строки или специальными

К сожалению, есть по крайней мере одно условие, заставляющее это больше не работать: когда переменная содержит символ новой строки:

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

Команда pasteбудет объединяться строка за строкой, поэтому вывод будет неправильным:

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'

Для этой работы вы можете использовать %qвместо %sвторой printfкоманды (и кавычки):

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

Сделает идеально:

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

От man bash:

          %q     causes  printf  to output the corresponding argument in a
                 format that can be reused as shell input.
Ф. Хаури
источник
Мой голос за printf "%q\n" "${var[@]}" перевод строки был моей проблемой!
техно