Работа с массивами в bash

Автор: admin от 13-06-2018, 12:05, посмотрело: 28

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



Работа с массивами в bash
шаблонных строках).



Теперь вернёмся к массивам. Оказывается, что, хотя фигурные скобки при работе с переменными обычно не нужны, они нужны для работы с массивами. Они позволяют задавать индексы для доступа к элементам массива. Например, команда вида echo ${allThreads[1]} выведет второй элемент массива. Если в вышеописанной конструкции забыть о фигурных скобках, bash будет воспринимать [1] как строку и соответствующим образом обработает то, что получится.



Как видите, массивы в bash имеют странный синтаксис, но в них, по крайней мере, нумерация элементов начинается с нуля. Это роднит их с массивами из многих других языков программирования.



Способы обращения к элементам массивов



В вышеописанном примере мы использовали в массивах целочисленные индексы, задаваемые в явном виде. Теперь рассмотрим ещё два способа работы с массивами.



Первый способ применим в том случае, если нам нужен $i-й элемент массива, где $i — это переменная, содержащая индекс нужного элемента массива. Извлечь этот элемент из массива можно с помощью конструкции вида echo ${allThreads[$i]}.



Второй способ позволяет вывести все элементы массива. Он заключается в замене числового индекса символом @ (его можно воспринимать как команду, указывающую на все элементы массива). Выглядит это так: echo ${allThreads[@]}.



Перебор элементов массивов в циклах



Вышеописанные принципы работы с элементами массивов пригодятся нам для решения задачи перебора элементов массива. В нашем случае это означает запуск исследуемой команды pipeline с каждым из значений, которое символизирует число потоков и хранится в массиве. Выглядит это так:



for t in ${allThreads[@]}; do
  ./pipeline --threads $t
done


Перебор индексов массивов в циклах



Рассмотрим теперь несколько иной подход к перебору массивов. Вместо того, чтобы перебирать элементы, мы можем перебирать индексы массива:



for i in ${!allThreads[@]}; do
  ./pipeline --threads ${allThreads[$i]}
done


Разберём то, что здесь происходит. Как мы уже видели, конструкция вида ${allThreads[@]} представляет собой все элементы массива. При добавлении сюда восклицательного знака мы превращаем эту конструкцию в ${!allThreads[@]}, что приводит к тому, что она возвращает индексы массива (от 0 до 7 в нашем случае).



Другими словами, цикл for перебирает все индексы массива, представленные в виде переменной $i, а в теле цикла обращение к элементам массива, которые служат значениями параметра --thread, выполняется с помощью конструкции ${allThreads[$i]}.



Читать этот код сложнее, чем тот, что приведён в предыдущем примере. Поэтому возникает вопрос о том, к чему все эти сложности. А нужно это нам из-за того, что в некоторых ситуациях, при обработке массивов в циклах, нужно знать и индексы и значения элементов. Скажем, если первый элемент массива нужно пропустить, перебор индексов избавит нас, например, от необходимости создания дополнительной переменной и от инкрементации её в цикле для работы с элементами массива.



Заполнение массивов



До сих пор мы исследовали систему, вызывая команду pipeline с передачей ей каждого интересующего нас значения параметра --threads. Теперь предположим, что эта команда выдаёт длительность выполнения некоего процесса в секундах. Нам хотелось бы перехватить возвращаемые ей на каждой итерации данные и сохранить в другом массиве. Это даст нам возможность работать с сохранёнными данными после того, как все испытания закончатся.



Полезные синтаксические конструкции



Прежде чем говорить о том, как добавлять данные в массивы, рассмотрим некоторые полезные синтаксические конструкции. Для начала нам нужен механизм получения данных, выводимых bash-командами. Для того чтобы захватить вывод команды, нужно использовать следующую конструкцию:



output=$( ./my_script.sh )


После выполнения этой команды то, что выведет скрипт myscript.sh, будет сохранено в переменной $output.



Вторая конструкция, которая нам очень скоро пригодится, позволяет присоединять к массивам новые данные. Выглядит это так:



myArray+=( "newElement1" "newElement2" )


Решение задачи



Теперь, если собрать вместе всё то, что мы только что изучили, можно будет создать скрипт для тестирования системы, который выполняет команду с каждым из значений параметра из массива и сохраняет в другом массиве то, что выводит эта команда.



allThreads=(1 2 4 8 16 32 64 128)
allRuntimes=()
for t in ${allThreads[@]}; do
  runtime=$(./pipeline --threads $t)
  allRuntimes+=( $runtime )
done


Что дальше?



Только что мы рассмотрели способ использования bash-массивов для перебора параметров, используемых при запуске некоей программы и для сохранения данных, которые возвращает эта программа. Однако этим сценарием варианты использования массивов не ограничиваются. Вот ещё пара примеров.



Оповещения о проблемах



В этом сценарии мы рассмотрим приложение, которое разбито на модули. У каждого из этих модулей имеется собственный лог-файл. Мы можем написать скрипт задания cron, который, при обнаружении проблем в соответствующем лог-файле, будет оповещать по электронной почте того, кто ответственен за каждый из модулей:



# Списки лог-файлов и заинтересованных лиц
logPaths=("api.log" "auth.log" "jenkins.log" "data.log")
logEmails=("jay@email" "emma@email" "jon@email" "sophia@email")

# Проверяем логи на предмет наличия сообщений об ошибках
for i in ${!logPaths[@]};
do
  log=${logPaths[$i]}
  stakeholder=${logEmails[$i]}
  numErrors=$( tail -n 100 "$log" | grep "ERROR" | wc -l )

  # Оповещаем заинтересованных лиц при обнаружении более 5 ошибок
  if [[ "$numErrors" -gt 5 ]];
  then
    emailRecipient="$stakeholder"
    emailSubject="WARNING: ${log} showing unusual levels of errors"
    emailBody="${numErrors} errors found in log ${log}"
    echo "$emailBody" | mailx -s "$emailSubject" "$emailRecipient"
  fi
done


Запросы к API



Предположим, вы хотите собрать сведения о том, какие пользователи комментируют ваши публикации на Medium. Так как у нас нет прямого доступа к базе данных этой площадки, SQL-запросы обсуждать мы не будем. Однако, для доступа к данным такого рода можно использовать различные API.



Для того чтобы избежать долгих разговоров об аутентификации и токенах, будем, в качестве конечной точки, использовать общедоступное API сервиса JSONPlaceholder, ориентированного на тестирование. Получив от сервиса публикацию и вытащив из её кода данные по электронным адресам комментаторов, мы можем поместить эти данные в массив:



endpoint="https://jsonplaceholder.typicode.com/comments"
allEmails=()

# Запрашиваем первые 10 публикаций
for postId in {1..10};
do
  # Выполняем обращение к API для получения электронных адресов комментаторов публикации
  response=$(curl "${endpoint}?postId=${postId}")

  # Используем jq для парсинга JSON и записываем в массив адреса комментаторов
  allEmails+=( $( jq '.[].email'  "$response" ) )
done


Обратите внимание на то, что здесь использовано средство jq, которое позволяет парсить JSON в командной строке. В подробности работы с jq мы тут вдаваться не будем, если вам этот инструмент интересен — посмотрите документацию по нему.



Bash или Python?



Массивы — возможность полезная и доступна она не только в bash. У того, кто пишет скрипты для командной строки, может возникнуть закономерный вопрос о том, в каких ситуациях стоит использовать bash, а в каких, например, Python.



На мой взгляд, ответ на этот вопрос кроется в том, насколько программист зависит от той или иной технологии. Скажем, если задачу можно решить прямо в командной строке, тогда ничто не препятствует использованию bash. Однако в том случае, если, например, интересующий вас скрипт является частью некоего проекта, написанного на Python, вы вполне можете воспользоваться Python.



Например, для решения рассмотренной здесь задачи можно воспользоваться и скриптом, написанным на Python, однако, это сведётся к написанию на Python обёртки для bash:



import subprocess

all_threads = [1, 2, 4, 8, 16, 32, 64, 128]
all_runtimes = []

# Запускаем программу с передачей ей различного числа потоков
for t in all_threads:
  cmd = './pipeline --threads {}'.format(t)

  # Используем модуль subprocess для получения того, что возвращает программа
  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
  output = p.communicate()[0]
  all_runtimes.append(output)


Пожалуй, решение этой задачи с помощью bash, без привлечения других технологий, получается короче и понятнее и здесь вполне можно обойтись без Python.



Итоги



В этом материале мы разобрали немало конструкций, использующихся для работы с массивами. Вот таблица, в которой вы найдёте то, что мы рассмотрели, и кое-что новое.


















































Синтаксическая конструкцияОписание
arr=()Создание пустого массива
arr=(1 2 3)Инициализация массива
${arr[2]}Получение третьего элемента массива
${arr[@]}Получение всех элементов массива
${!arr[@]}Получение индексов массива
${#arr[@]}Вычисление размера массива
arr[0]=3Перезапись первого элемента массива
arr+=(4)Присоединение к массиву значения
str=$(ls)Сохранение вывода команды ls в виде строки
arr=( $(ls) )Сохранение вывода команды ls в виде массива имён файлов
${arr[@]:s:n}Получение элементов массива начиная с элемента с индексом s до элемента с индексом s+(n-1)



На первый взгляд bash-массивы могут показаться довольно странными, но те возможности, которые они дают, стоят того, чтобы с этими странностями разобраться. Полагаем, что освоив bash-массивы, вы будете пользоваться ими довольно часто. Несложно представить себе бесчисленное множество сценариев, в которых эти массивы могут пригодиться.



Уважаемые читатели! Если у вас есть интересные примеры применения массивов в bash-скриптах — просим ими поделиться.



Работа с массивами в bash

Источник: Хабр / Интересные публикации

Категория: Веб-разработка

Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *