В Python я могу сделать это в одной строке:
>>> for key, value in dict(j=1, k=2).items():
... print(key, value)
...
j 1
k 2
>>>
В bash я могу сделать это:
$ map=([j]=1 [k]=2); for key in ${!map[@]}; do value=${map[$key]}; echo $key $value; done
j 1
k 2
$
Есть ли в bash способ «встроить» объявление map в for - аналогично тому, как это возможно в примере с Python - вместо того, чтобы объявлять его заранее?
Причина, по которой я ищу это, состоит в том, чтобы избежать необходимости объявлять две дополнительные переменные map и value отдельно - другими словами, чтобы сделать это короче, если это возможно, при выполнении однострочных строк в командной строке.
(...например, можно, конечно, написать функцию, которая будет определять ассоциативный массив с ключами и значениями из своей командной строки, а затем вызывать произвольную функцию с каждым ключом и значением в качестве аргументов по очереди; поэтому можно написать помощник, который соответствовало бы письму вашего запроса, но я не уверен, что это будет иметь какую-либо практическую ценность в отсутствие варианта использования).
Это может помочь объяснить вашу конечную цель и откуда берутся ваши данные (файл? аргументы командной строки? Как выглядит формат?). Для этой функции Python в bash нет прямого перевода, но, например, если ваша цель — просто перебрать пары значений, для этого есть инструменты в зависимости от того, как хранятся ваши данные.
@CharlesDuffy У меня не обязательно есть проблемы с вышеизложенным, я просто ищу более короткую версию, то есть избегаю объявления двух дополнительных переменных (map и value)
@ tjm3772 Ответил в предыдущем комментарии, но позвольте мне также добавить к вопросу.
В вопросе вам не хватает объявления ассоциативного массива declare -A map. Как написано сейчас, map=([j]=1 [k]=2) создаст числовой индексированный массив с одной записью, значением 2 с индексом 0.
В целом, я бы сказал, что приоритезация краткости в bash сама по себе является ошибкой. Наследие десятилетий обратной совместимости, требующее, чтобы расширения и исправления находились в ранее неопределенном пространстве, означает, что нужно быть очень осторожным и явным; краткий код часто является кодом с тонкими (или не такими уж тонкими) ошибками (как показывает Гленн выше).
@glennjackman Ах, должно быть, это остатки моего предыдущего тестирования, которое я проводил declare -A map, и это было во время сеанса, когда я опубликовал вопрос. Ну что ж...
Возможно, вам не хватает лучшего способа сделать то, что вам нужно, назовите это «Y», подходя к этому как «если идиома X — это то, как я подхожу к требованию Y в Python, то как мне реализовать X в bash», а не затем «как мне подойти к требованию Y в bash».
В Python dict(j=1, k=2) — это выражение, результатом которого является конкретный объект. В bash([j]=1, [k]=2) — это не выражение, а расширение синтаксиса присваивания, которое позволяет вам определить значение map без необходимости писать map[j]=1,map[k]=2. bash вообще не имеет значений массива, только специальный синтаксис, позволяющий сделать более или менее различные параметры похожими на один массив.
@EdMorton По сути, мое последнее предложение. У меня есть команда, которой нужна пара ключ-значение. У меня есть список пар ключ-значение. Мне нужно запустить эту команду для каждой пары ключ-значение из этого списка. Путь Python для меня естественен. Я хочу повторить в bash. Оно должно быть коротким/легким для ввода/легким для запоминания. Оно должно читаться естественно слева направо. Он должен быть встроенным. for kv in kvs лучше, чем kvs=...; for k in kvs; v=kvs[k] или while read k v; ...; done <<<kvs. Не все возможно в bash, и некоторые из них субъективны, но чем ближе, тем лучше.
Хорошо, тогда это вопрос типа «Я хочу, чтобы мое яблоко имело вкус моего апельсина», и вам просто не повезет, если вы не напишите свою собственную функцию, обеспечивающую нужный синтаксис.





Есть ли в bash способ «встроить» объявление карты в for — аналогично тому, как это возможно в примере с Python — вместо того, чтобы объявлять это заранее?
Нет.
Вы можете прочитать два слова построчно, но карты нет.
printf "%s %s\n" j 1 k 2 | while read -r key value; do echo "$key" "$value"; done
Проверьте свои скрипты с помощью Shellcheck.
Чтобы избежать запуска цикла while в подоболочке, перенаправьте в него вводимые данные: while read ...; done <<< $'j 1\nk 2'
Нет никакого способа обойти это
declare -A map=([j]=1 [k]=2)
Расширение параметра "${!map[@]}" требует объявленного параметра.
На самом деле это неправда. Расширение параметра удачно расширяет неустановленную переменную в пустую строку.
Вы можете удалить переменную value
for key in "${!map[@]}"; do
printf '%s %s\n' "$key" "${map[$key]}"
done
Обратите внимание, что ассоциативные массивы неупорядочены. Мой результат для этого цикла:
k 2
j 1
Если вы просто ищете быстрый способ проверить содержимое переменной, используйте declare -p
declare -A map=([j]=1 [k]=2)
declare -p map
результаты
declare -A map=([k] = "2" [j] = "1" )
Если у вас уже есть ассоциативный массив в Bash 5.2+, вы можете использовать параметр @k, который расширяется за счет чередования ключей и значений для разделения слов (см. 5.2.t в журнале изменений ), и printf потреблять два элемента одновременно :
declare -A map=([j]=1 [k]=2)
printf '%s %s\n' "${map[@]@k}"
Примечания:
@k с @K, который был добавлен в версии 5.1 (см. 5.1.hh) и расширяется только до одного словаНо если вы создали массив только для перебора пар ключ-значение, просто укажите и ключи, и значения непосредственно в printf:
printf '%s %s\n' j 1 k 2
Чтобы внести ясность, общий ответ — «нет», если вы просто спрашиваете о доступном синтаксисе; но если у вас есть практическая проблема, которую этот синтаксис позволит вам решить, описание этой проблемы может позволить найти ответ с помощью альтернативного подхода.