Как использовать ассоциативный массив в Bash для получения текущего времени в нескольких местах?

Вопрос или проблема

Я хочу создать 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

Объяснение кода:

  1. Объявление ассоциативного массива: Мы используем declare -A для объявления ассоциативного массива locationsArray.

  2. Заполнение массива: В массив добавляются пары "временная зона": "метка местоположения". Это позволяет ассоциировать каждую временную зону с человеком читаемым именем.

  3. Итерация по массиву: Используя for timezone in "${!locationsArray[@]}"; do, мы перебираем все ключи (временные зоны) массива. Мы извлекаем соответствующую метку местоположения, используя label="${locationsArray[$timezone]}".

  4. Получение времени и даты: Для каждой временной зоны мы используем команду date для получения текущего времени и даты. Мы задаем переменной TZ значение временной зоны перед вызовом date.

  5. Вывод результата: Выводим результат в требуемом формате.

Запуск скрипта:

Сохраните скрипт в файл, сделайте его исполняемым с помощью команды chmod +x filename.sh, и запустите его с помощью ./filename.sh. Вы получите текущее время и дату для каждой локации, как было указано в вашем вопросе.

Этот метод не только значительно уменьшает количество строк кода, но и делает его более читаемым и удобным для дальнейшего расширения.

Оцените материал
Добавить комментарий

Капча загружается...