Как считать файл в массив bash

Обновлено: 03.07.2024

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

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

Массивы Bash

declare -a имя_массива

Но необязательно делать именно так, вы можете сразу начать задавать элементы массива по нужным номерам:

имя_массива[ XX ] = значение

Здесь XX обозначает индекс массива. Еще один удобный способ создавать массивы строк Bash - это просто перечислить все элементы в круглых скобках:

имя_массива=( э лемент_1, элемент_2 элемент_3 . )

Или вы можете сразу задать индекс массива для каждого из элементов:

имя_массива=( [ XX ]= значение [ XX ]= значение . . . )

Также можно прочитать значения для массива из клавиатуры или другого источника ввода:

read -a имя_массива

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

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

Примеры использования массивов Bash

Теперь рассмотрим примеры массивов bash. Сначала нам нужно создать массив, который мы будем использовать для примеров, на мой взгляд будет проще использовать синтаксис с круглыми скобками. Вообще, массивы используются в скриптах, но мы будем их применять прямо в оболочке Bash. Для начала так будет проще. Создаем массив:

array=(первый второй третий четвертый пятый)


Теперь попытаемся вывести один из элементов массива по его индексу:


Чаще всего используются массивы строк Bash, но иногда могут встречаться и цифры. Помните про нумерацию? Индексы элементов массива начинаются с нуля. Для вывода значения элемента по индексу можно использовать и немного другой синтаксис:


Вы можете вывести все элементы:


Все элементы, начиная с номера 1:


Вывести все элементы которые находятся в диапазоне от 1 до 4:


Чтобы узнать длину первого элемента выполните:


А посмотреть количество элементов массива Bash можно таким же синтаксисом:


Кроме всего прочего, вы можете заменить одни символы в определенном элементе массива или во всем массиве на другие. Для этого используйте:

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

array=(первый второй третий четвертый пятый)
for i in $
do
echo $i
done


Внутри цикла вы можете делать со значением элемента все, что вам нужно. Как я уже писал выше, вы можете прочитать значения для массива с помощью функции read:

echo "Введите элементы массива:"
read -a array
echo "Результат:"
for i in $
do
echo $i
done


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


Усложним задачу и сделаем скрипт, который будет выводить все файлы из указанной директории, которые имеют права доступа 755:

if [ ! -d $1 ]; then
echo "Каталог $1 не существует"
exit $ERR
fi

temp=( $(find $1 -maxdepth 1 -type f) )

for i in "$"
do
perm=$(ls -l $i)
if [ `expr $ : "-rwxr-xr-x"` -eq 10 ]; then
echo $
fi
done


Теперь проверим наш скрипт на папке /bin. Но перед этим нужно дать ему права на выполнение:

chmod u+x ./lsperm.sh
$ ./lsperm.sh


Как видите, все работает. Кроме номеров, в качестве индексов для массивов можно использовать строки. Такие массивы Bash называются ассоциативными и поддерживаются они начиная с четвертной версии Bash. Для создания ассоциативного массива используется declare с опцией -A:

declare -A assoc_array


Несмотря на то что Bash поддерживает только одномерные массивы, мы можем выполнять симуляцию работы с многомерными матрицами с помощью ассоциативных массивов:

declare -A matrix


Чтобы удалить массив, созданный с помощью declare используйте функцию unset:

Выводы

В этой статье мы рассмотрели как работают массивы Bash в Linux, как их создавать, а также как использовать. Если разобраться, то здесь нет ничего очень сложного, а массивы могут стать очень важным элементов ваших скриптов.

Сценарий берет URL-адрес, анализирует его на наличие обязательных полей и перенаправляет свой вывод для сохранения в файле file.txt . Выходные данные сохраняются в новой строке каждый раз, когда поле обнаруживается.

File.txt

Я хочу взять file.txt и создать из него массив в новом скрипте, где каждая строка должна быть собственной строковой переменной в массиве. Пока я пробовал:

Когда я запускаю этот скрипт, пробелы приводят к разделению слов и вместо получения

Желаемый результат

Я получаю вот что:

Фактический выход

Как я могу настроить цикл ниже так, чтобы вся строка в каждой строке соответствовала один-к-одному каждой переменной в массиве?

Ошибка заключается в использовании for - идиоматический способ перебора строк файла:

Дополнительные сведения см. В BashFAQ / 005.

В этом ответе говорится об использовании

Я сделал прокладку для mapfile , если вы хотите использовать > на bash mapfile , если вы используете bash> = 4.x

В настоящее время работают только параметры -d и -t . Но этого должно быть достаточно для приведенной выше команды. Я тестировал только на macOS. В macOS Sierra 10.12.6 системным bash является 3.2.57(1)-release . Так что прокладка может пригодиться. Вы также можете просто обновить свой bash с помощью homebrew, собрать bash самостоятельно и т. Д.

Он использует этот метод для установки переменных в один стек вызовов.

Используйте mapfile или прочтите -a

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

Не делай этого

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

Файлы со значениями, разделенными пробелами

Вы можете просто прочитать каждую строку из файла и назначить ее массиву.

Ты тоже можешь это сделать:

Расширение имени файла все еще происходит. Например, если есть строка с литералом * , она расширится до всех файлов в текущей папке. Поэтому используйте его только в том случае, если в вашем файле нет такого сценария.

mapfile и readarray (которые являются синонимами) доступны в Bash версии 4 и выше. Если у вас более старая версия Bash, вы можете использовать цикл для чтения файла в массив:

Если в файле есть неполная последняя строка (отсутствует новая строка), вы можете использовать эту альтернативу:

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

image

Реальная задача, в которой пригодятся bash-массивы

Писать о bash — занятие неоднозначное. Дело в том, что статьи о bash нередко превращаются в руководства пользователя, которые посвящены рассказам о синтаксических особенностях рассматриваемых команд. Эта статья написана иначе, надеемся, вам она не покажется очередным «руководством пользователя».

Учитывая вышесказанное, представим себе реальный сценарий использования массивов в bash. Предположим, перед вами стоит задача оценить и оптимизировать утилиту из нового внутреннего набора инструментов, используемого в вашей компании. На первом шаге этого исследования вам нужно испытать её с разными наборами параметров. Испытание направлено на изучение того, как новый набор инструментов ведёт себя при использовании им разного количества потоков. Для простоты изложения будем считать, что «набор инструментов» — это скомпилированный из C++-кода «чёрный ящик». При его использовании единственным параметром, на который мы можем влиять, является число потоков, зарезервированных для обработки данных. Вызов исследуемой системы из командной строки выглядит так:

Основы

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


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


Как и в случае с другими переменными bash, обратите внимание на то, чтобы вокруг знака = не было бы пробелов. В противном случае bash сочтёт имя переменной именем программы, которую ему нужно выполнить, а = — её первым аргументом!

Теперь, когда мы инициализировали массив, давайте извлечём из него несколько элементов. Тут можно заметить, например, что команда echo $allThreads выведет лишь первый элемент массива.

Для того чтобы понять причины такого поведения, немного отвлечёмся от массивов и вспомним, как работать с переменными в bash. Рассмотрим следующий пример:


Предположим, что имеется переменная $type , которая содержит строку, представляющую собой имя существительное. После этого слова надо добавить букву s . Однако нельзя просто добавить эту букву в конец имени переменной, так как это превратит команду обращения к переменной в $types , то есть, работать мы уже будем с совершенно другой переменной. В данной ситуации можно воспользоваться конструкцией вида echo "Found 42 "$type"s" . Но лучше всего решить эту задачу с использованием фигурных скобок: echo "Found 42 $s" , что позволит нам сообщить bash о том, где начинается и заканчивается имя переменной (что интересно, тот же синтаксис используется в JavaScript ES6 для внедрения переменных в выражения в шаблонных строках).

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

Что дальше?

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

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

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

Запросы к API

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

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


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

Bash или Python?

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

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

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


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

Итоги

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

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

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

скрипт берет URL, анализирует его для требуемых полей и перенаправляет его вывод для сохранения в файл, .txt. Выходные данные сохраняются в новой строке при каждом обнаружении поля.

Я хочу взять file.txt и создайте массив из него в новом скрипте, где каждая строка будет собственной строковой переменной в массиве. До сих пор я пробовал:

когда я запускаю этот скрипт, пробелы приводят к слова разделяются и вместо того, чтобы получить

нужный выход

Я в конечном итоге получить это:

фактический объем производства

как я могу настроить цикл ниже так, чтобы вся строка в каждой строке соответствовала один к одному с каждой переменной в массиве?

ошибки, используя for -- идиоматический способ зацикливания строки из файла:

посмотреть BashFAQ/005 для получения более подробной информации.

Вы можете сделать это:

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

mapfile и readarray (которые являются синонимами) доступны в версии Bash 4 и выше. Если у вас есть более старая версия Bash, вы можете использовать цикл для чтения файла в массив:

в случае, если файл имеет неполную (отсутствующую новую строку) последнюю строку, вы можете использовать эту альтернативу:

вы можете просто читать каждую строку из файла и присвоить его массиву.

используйте mapfile или read-a

всегда проверяйте свой код с помощью shellcheck. Он часто дает вам правильный ответ. В этом случае SC2207 охватывает чтение файла с разделенными пробелами или значениями, разделенными новой строкой, в массив.

не делай этого

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

файлы со значениями, разделенными пробелами

в shellcheck страница даст вам обоснование, почему это считается лучшей практикой.

ответ что надо использовать

Я сим на mapfile Если вы хотите использовать mapfile на bash mapfile команда, если вы находитесь на bash >= 4.x

в настоящее время, только варианты -d и -t работа. Но этого должно быть достаточно для команды выше. Я тестировал только на macOS. На macOS Sierra 10.12.6 системным bash является 3.2.57(1)-release . Так что прокладка может пригодиться. Вы также можете просто обновите свой bash с помощью homebrew, создайте bash самостоятельно и т. д.

Читайте также: