Печать ассоциативного массива BASH

Первый сценарий завершается неудачно с неожиданным токеном 'i * 1000-1000'

Что я сделал не так?

Очевидная ошибка заключается в том, что вы на самом деле не вызываете переменную $ i в своем цикле:

for i in $(seq 10)
do
    ./tmp/tmpsky/bd25Par.sh (i*1000-1000) (i*1000) &
done

Это должно быть:

for i in $(seq 10)
do
    ./tmp/tmpsky/bd25Par.sh ($i*1000-1000) ($i*1000) &
done

Таким образом; используйте $ i при использовании переменной. i сам по себе работать не будет.

Что касается вычислений в оболочке, см. Комментарий номер 2.

17
22.05.2017, 19:13
5 ответов

Я думаю, вы спрашиваете о двух разных вещах.

Есть ли способ заставить bash печатать эту информацию без цикла?

Да, но они не так хороши, как просто использование цикла.

Есть ли более чистый способ получить/распечатать только часть вывода, состоящую из ключей и значений?

Да, цикл for. Его преимущества заключаются в том, что он не требует внешних программ, прост и позволяет довольно легко контролировать точный формат вывода без сюрпризов.


Любое решение, которое пытается обработать вывод declare -p (typeset -p) должен иметь дело с а) возможностью того, что сами переменные содержат круглые скобки или квадратные скобки, б) цитирование, которое declare -p должно добавить, чтобы его вывод был действительным вводом для оболочки.

Например, ваше расширение b="${a##*(}" съедает часть значений, если какой-либо ключ/значение содержит открывающую скобку. Это потому, что вы использовали # #, который удаляет префикс самый длинный. То же самое для c="${b%% )*}". Хотя вы, конечно, могли бы более точно сопоставить шаблон, напечатанный с помощью declare, вам все равно было бы трудно, если бы вы не хотели цитировать все, что он делает.

Это выглядит не очень красиво, если только вам это не нужно.

$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'

С помощью цикла for проще выбрать формат вывода по своему усмотрению:

# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'

# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'

Отсюда также просто изменить формат вывода в противном случае (убрать скобки вокруг ключа поместите все пары ключ/значение в одну строку...).Если вам нужно цитировать что-то помимо самой оболочки, вам все равно придется сделать это самостоятельно, но, по крайней мере, у вас есть необработанные данные для работы. (Если у вас есть символы новой строки в ключах или значениях, вам, вероятно, понадобятся кавычки.)

С текущим Bash (я думаю, 4.4) вы также можете использовать printf "[%s]=% s" "${x@Q}" "${array[$x]@Q}" вместо printf "%q=%q". Он производит несколько более приятный формат кавычек, но, конечно, немного больше работы, чтобы не забыть написать. (И он заключает в кавычки угловой регистр @ как ключ массива, который %q не заключает в кавычки.)

Если цикл for кажется слишком утомительным для записи, сохраните его как функцию где-нибудь (без кавычек):

printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ;  }  

А затем просто используйте это:

$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)

Работает и с индексированными массивами:

$ b=(abba acdc)
$ printarr b
0=abba
1=acdc
25
27.01.2020, 19:47
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'

2 форка

Возможно, это:

printf "%s\n" "${!array[@]}"
a2
a1
f50
zz
b1

printf "%s\n" "${array[@]}"
2
1
abcd
Hello World
bbb

printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t
a2                              2
a1                              1
f50                             abcd
zz                              Hello World
b1                              bbb

3 форка

или это:

paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}")
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Нет форка

для сравнения с

for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Сравнение времени выполнения

В качестве последнего синтаксиса не использовать fork, они могли бы быть быстрее:

time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
      5      11      76
real    0m0.005s
user    0m0.000s
sys     0m0.000s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
      5       6      41
real    0m0.008s
user    0m0.000s
sys     0m0.000s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
      5       6      41
real    0m0.002s
user    0m0.000s
sys     0m0.001s

Но это утверждение не остается верным, если массив становится большим; если сокращение вилок эффективно для небольшого процесса, использование специальных инструментов более эффективно для более крупного процесса.

for i in {a..z}{a..z}{a..z};do array[$i]=$RANDOM;done


time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
  17581   35163  292941
real    0m0.150s
user    0m0.124s
sys     0m0.036s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
  17581   17582  169875
real    0m0.140s
user    0m0.000s
sys     0m0.004s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
  17581   17582  169875
real    0m0.312s
user    0m0.268s
sys     0m0.076s

Примечание

Поскольку оба (разветвленных) решения используют выравнивание, ни одно из них не будет работать, если какая-либо переменная содержит новую строку. В этом случае единственным способом является цикл for.

14
27.01.2020, 19:47

Поскольку typeset делает то, что вам нужно, почему бы просто не отредактировать вывод?

typeset -p array | sed s/^.*\(// | tr -d ")\'\""  | tr "[" "\n" | sed s/]=/' = '/

дает

a2 = 2  
a1 = 1  
b1 = bbb 

Where

array='([a2]="2" [a1]="1" [b1]="bbb" )'

Verbose, но довольно легко увидеть, как работает форматирование: просто выполните конвейер с последовательно увеличивающимся количеством команд sed и tr. Измените их, чтобы они соответствовали вкусу печати.

1
27.01.2020, 19:47

Если вам нужна оболочка с лучшей поддержкой ассоциативных массивов, попробуйте zsh.

В zsh (где ассоциативные массивы были добавлены в 1998 г. по сравнению с 1993 г. для ksh93 и 2009 г. для bash), $var или ${(v)var} расширяется до (непустых) значений хеша, ${(k)var} до (непустых) ключей (в том же порядке), и ${(kv)var} как для ключей, так и для значений.

Чтобы сохранить пустые значения, как и для массивов, вам нужно заключить в кавычки и использовать флаг @.

Таким образом, для вывода ключей и значений достаточно

printf '%s => %s\n' "${(@kv)var}"

Хотя для учета возможного пустого хеша вам следует сделать следующее:

(($#var)) &&  printf '%s => %s\n' "${(@kv)var}"

Также обратите внимание, что zsh использует гораздо более разумный и полезный синтаксис определения массива, чем ksh93's (скопировано bash):

typeset -A var
var=(k1 v1 k2 v2 '' empty '*' star)

Что значительно упрощает копирование или объединение ассоциативных массивов:

var2=("${(@kv)var1}")
var3+=("${(@kv)var2}")
var4=("${@kv)var4}" "${(@kv)var5}")

(вы не можете легко скопировать хэш без цикл с bash, и обратите внимание, что bash в настоящее время не поддерживает пустые ключи или ключи/значения с байтами NUL).

См. также zsh функции сжатия массива, которые обычно требуются для работы с ассоциативными массивами:

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})
2
27.01.2020, 19:47

Еще один вариант — перечислить все переменные и выполнить команду grep для нужной.

set | grep -e '^aa='

Я использую это для отладки. Я сомневаюсь, что это очень эффективно, поскольку в нем перечислены все переменные.

Если бы вы делали это часто, вы могли бы сделать это функцией, подобной этой:

aap() { set | grep -e "^$1="; }

К сожалению, когда мы проверяем производительность по времени:

$ time aap aa aa=([0]="abc") . real 0m0.014s user 0m0.003s sys 0m0.006s

Поэтому, если бы вы делали это очень часто, вам бы подошла версия NO FORKS от @F.Hauri, потому что она намного быстрее.

1
27.01.2020, 19:47

Теги

Похожие вопросы