Какой шаблон кода наиболее оптимален для преобразования списка значений ключа в ассоциативный массив в bash? - PullRequest
1 голос
/ 01 апреля 2020

Учитывая список ключ-значение, хранящийся в индексированном массиве, где каждый ключ и значение являются отдельными элементами:

list=(
'key$1' "value 1 line 1
value 1 line 2
"
'key$2' $'another\nmulti-line\nvalue\n'
)

Следующий код будет корректно l oop по парам ключ-значение и создаст и ассоциативный массив:

declare -A arr
for (( i=0; i<${#list[*]}; i+=2 )); do
    arr+=( ["${list[$i]}"]="${list[$((i+1))]}" );
done
$ declare -p arr
declare -A arr='([key2]="another
multi-line
value
" [key1]="value 1 line 1
value 1 line 2
" )'
$ 

Есть ли более простой или краткий способ выполнить sh это?

1 Ответ

3 голосов
/ 01 апреля 2020

Вместо arr+=([key]=value) вы можете написать arr[key]=value. Кроме того, $(()) и префикс $ для переменных не являются необходимыми внутри list[…].

declare -A arr
for (( i=0; i<"${#list[*]}"; i+=2 )); do
    arr["${list[i]}"]="${list[i+1]}"
done

В остальном сценарий выглядит нормально. Я думаю, что единственной верной альтернативой является создание командной строки и eval / declare этой. Следующая команда предполагает, что в list есть хотя бы одна пара ключ-значение:

declare -A "arr=($(printf '[%q]=%q ' "${list[@]}"))"

Эта команда declare должна быть безопасной. Также не должно быть проблем с ARG_MAX, так как используются только встроенные модули. Тем не менее, l oop кажется быстрее на одну величину, см. Следующий тест. Не стесняйтесь тестировать оба подхода самостоятельно, используя ваши фактические данные (это того стоит только при работе с очень длинными массивами).

randList() {
    # tr is necessary since the empty string cannot be used as a key
    mapfile -d '' -n "$1" list < <(tr -s \\0 < /dev/urandom)
}
testFor() { declare -A arr; for (( i=0; i<"${#list[*]}"; i+=2 )); do arr["${list[i]}"]="${list[i+1]}"; done; }
testDeclare() { declare -A "arr=($(printf '[%q]=%q ' "${list[@]}"))"; }
prettyTime() { { time "$@"; } 2>&1 | grep -Eom1 '[0-9.sm]+'; }
for size in {1,10,50}000; do
    randList "$size"
    echo "list size = $size"
    printf %s "for loop        "; prettyTime testFor
    printf %s "declare command "; prettyTime testDeclare
done

На моем ноутбуке (bash 5.0.16, intel i5 M 520) я получил эти результаты:

list size = 1000
for loop        0m0.059s
declare command 0m0.320s
list size = 10000
for loop        0m0.435s
declare command 0m2.395s
list size = 50000
for loop        0m2.540s
declare command 0m12.276s
...