Прокручуючи масиви, друкуючи як індекс, так і значення


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")'

але хіба ти не можеш це зробити по-іншому?

Відповіді:


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
Використання [@]і подвійних лапок означає, що це не "розділений пробілом список слів". Ви отримуєте список фактичних ключів масиву, навіть якщо окремі клавіші містять пробіл.
glenn jackman

@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

17

ви завжди можете використовувати параметр ітерації:

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

Ваша відповідь, безумовно, варто трохи пояснити. Будь ласка, зверніться до stackoverflow.com/help/how-to-answer . Коментарі допоможуть створити вміст, який можна шукати.
Дж. Чомель

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

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
Це неправильно! Внутрішній цикл марний і може призвести до неправильних результатів!
Ф. Хаурі

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рядок за рядком.

Налаштування

Це можна налаштувати за допомогою -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[@]}" новий рядок була моєю проблемою!
техно
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.