Вопрос или проблема
Я хочу создать Bash-скрипт, который выводит следующее:
Чили: 03:46am, пятница, 27 сентября, 2024
Огненная Земля: 03:46am, пятница, 27 сентября, 2024
Фолклендские острова: 03:46am, пятница, 27 сентября, 2024
Южная Георгия и Южные Сандвичевы острова: 04:46am, пятница, 27 сентября, 2024
UTC: 06:46am, пятница, 27 сентября, 2024
Южноафриканская Республика: 08:46am, пятница, 27 сентября, 2024
Порт Альфред, острова Крозе: 10:46am, пятница, 27 сентября, 2024
Западная Австралия: 14:46pm, пятница, 27 сентября, 2024
Южная Австралия: 16:16pm, пятница, 27 сентября, 2024
VIC, NSW, ACT и QLD, Австралия: 16:46pm, пятница, 27 сентября, 2024
Новая Зеландия: 18:46pm, пятница, 27 сентября, 2024
Я сумел сделать это с помощью следующего кода:
#Чили
location="America/Santiago"
label="Чили"
time=$(TZ=$location date "+%H:%M%p")
date=$(TZ=$location date "+%A, %d %B, %Y")
echo "$label: $time, $date"
… и затем просто добавляя множество блоков того же кода, меняя location и label по мере необходимости.
Это нормально, но файл состоит из 103 строк. Я хочу научиться использовать ассоциативные массивы для достижения той же задачи, только чтобы учиться.
Я создал сам массив следующим образом:
# Объявляем ассоциативный массив
declare -A locationsArray
# Добавляем элементы в ассоциативный массив
locationsArray[America/Santiago]="Чили"
locationsArray[America/Argentina/Ushuaia]="Огненная Земля"
locationsArray[Atlantic/Stanley]="Фолклендские острова"
locationsArray[Atlantic/South_Georgia]="Южная Георгия и Южные Сандвичевы острова"
locationsArray[Etc/UTC]="UTC"
locationsArray[Africa/Johannesburg]="Южноафриканская Республика"
locationsArray[Asia/Dubai]="Порт Альфред, острова Крозе"
locationsArray[Australia/Perth]="Западная Австралия, Австралия"
locationsArray[Australia/Adelaide]="Южная Австралия, Австралия"
locationsArray[Australia/Melbourne]="VIC, NSW, ACT и QLD, Австралия"
locationsArray[Pacific/Auckland]="Новая Зеландия"
Теперь мне нужно взять ключ из массива и его связанное значение (например, “America/Santiago” и “Чили”) и пройтись по всему массиву, используя вышеприведенный код в качестве шаблона.
Как я могу это сделать?
Спасибо.
Возможно, ассоциативный массив здесь не самый лучший инструмент, вы можете сделать так:
#! /bin/bash -
export TZ
while IFS=: read -r zone TZ; do
printf '%s: %(%H:%M%p, %A, %d %B, %Y)T\n' "$zone" -2
done << 'EOF'
Чили:America/Santiago
Огненная Земля:America/Argentina/Ushuaia
...
EOF
Обратите внимание, что -2
для printf
в bash для формата %T
относится ко времени, когда этот экземпляр bash был запущен. Это гарантирует, что вы получите вывод для одного и того же времени во всех зонах.
Вы можете проходить по ключам ассоциативных массивов в bash с помощью:
for key in "${!assoc[@]}"; do
value=${assoc[$key]}
...
done
Но будьте осторожны, порядок не детерминированный.
В zsh
¹ вы можете получать как ключ, так и значения одновременно с помощью:
for key value in "${(kv@)assoc}"; do
...
done
Но и там порядок не детерминированный.
Ассоциативные массивы полезны для эффективного поиска отдельных значений. Существует более высокая стоимость при присвоении, поскольку необходимо построить хеш-таблицу, но поиск осуществляется эффективно. Здесь, поскольку вы все равно хотите пройти по всем записям, это полностью нарушает цель и становится значительно хуже как с функциональной, так и с производственной точки зрения. Обычный массив будет более целесообразным.
В zsh:
list=(
'Чили' 'America/Santiago'
'Огненная Земля' 'America/Argentina/Ushuaia'
)
for zone TZ in $list; do
...
done
Bash не поддерживает итерацию с более чем одной переменной, но вы можете сделать так:
for (( i = 0; i < ${#list[@]}; i += 2 )); do
zone=${list[i]} TZ=${list[i+1]}
...
done
Вместо этого. Или:
set -- "${list[@]}"
while (( $# )); do
zone=$1 TZ=$2
...
shift 2
done
Но это более трудоемко в написании, чем подход с CSV heredoc, упомянутый выше. Вместо here doc вы также можете иметь CSV с сопоставлением в отдельном файле, который можно будет поддерживать отдельно от скрипта, и использовать < /path/to/that/file
вместо << 'EOF'...EOF
.
¹ Там, где эквивалент printf
в bash для %T
будет через встроенное strftime
или расширение запроса, как в print -rP -- "$zone %D{...}"
(хотя только для текущего времени с последним).
Ответ или решение
Чтобы использовать ассоциативные массивы в Bash для получения текущего времени в нескольких локациях, вы можете следовать следующему примеру. Этот пример упрощает ваш изначальный код и позволяет вам использовать ассоциативные массивы для хранения временных зон и соответствующих меток местоположений.
Вот как можно реализовать эту задачу:
#!/bin/bash
# Объявляем ассоциативный массив
declare -A locationsArray
# Добавляем элементы в ассоциативный массив
locationsArray[America/Santiago]="Chile"
locationsArray[America/Argentina/Ushuaia]="Tierra Del Fuego"
locationsArray[Atlantic/Stanley]="Falkland Islands"
locationsArray[Atlantic/South_Georgia]="South Georgia and the South Sandwich Islands"
locationsArray[Etc/UTC]="UTC"
locationsArray[Africa/Johannesburg]="South Africa"
locationsArray[Asia/Dubai]="Port Alfred, Iles Crozet"
locationsArray[Australia/Perth]="Western Australia, Australia"
locationsArray[Australia/Adelaide]="South Australia, Australia"
locationsArray[Australia/Melbourne]="VIC, NSW, ACT, and QLD, Australia"
locationsArray[Pacific/Auckland]="New Zealand"
# Итерация по ключам ассоциативного массива
for timezone in "${!locationsArray[@]}"; do
label="${locationsArray[$timezone]}"
# Получаем текущее время и дату для данной временной зоны
time=$(TZ="$timezone" date "+%I:%M%p")
date=$(TZ="$timezone" date "+%A, %d %B, %Y")
# Выводим результат
echo "$label: $time, $date"
done
Объяснение кода:
-
Объявление ассоциативного массива: Мы используем
declare -A
для объявления ассоциативного массиваlocationsArray
. -
Заполнение массива: В массив добавляются пары "временная зона": "метка местоположения". Это позволяет ассоциировать каждую временную зону с человеком читаемым именем.
-
Итерация по массиву: Используя
for timezone in "${!locationsArray[@]}"; do
, мы перебираем все ключи (временные зоны) массива. Мы извлекаем соответствующую метку местоположения, используяlabel="${locationsArray[$timezone]}"
. -
Получение времени и даты: Для каждой временной зоны мы используем команду
date
для получения текущего времени и даты. Мы задаем переменнойTZ
значение временной зоны перед вызовомdate
. -
Вывод результата: Выводим результат в требуемом формате.
Запуск скрипта:
Сохраните скрипт в файл, сделайте его исполняемым с помощью команды chmod +x filename.sh
, и запустите его с помощью ./filename.sh
. Вы получите текущее время и дату для каждой локации, как было указано в вашем вопросе.
Этот метод не только значительно уменьшает количество строк кода, но и делает его более читаемым и удобным для дальнейшего расширения.