Создание массива из текстового файла в Bash

86

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

file.txt

A Cat
A Dog
A Mouse 
etc... 

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

#!/bin/bash

filename=file.txt
declare -a myArray
myArray=(`cat "$filename"`)

for (( i = 0 ; i < 9 ; i++))
do
  echo "Element [$i]: ${myArray[$i]}"
done

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

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

Element [0]: A Cat 
Element [1]: A Dog 
etc... 

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

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

Element [0]: A 
Element [1]: Cat 
Element [2]: A
Element [3]: Dog 
etc... 

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

user2856414
источник
5
В этом вся суть Bash FAQ 001 . Также этот раздел темы массивов в Bash FAQ 005 .
Etan Reisner
1
Я бы связал это как дубликат stackoverflow.com/questions/11393817/… , но принятый ответ ужасен.
Чарльз Даффи
Этан, большое спасибо за такой быстрый и точный ответ! Я попытался найти свой вопрос на форумах, но не подумал искать FAQ по stackoverflow. Команда mapfile точно удовлетворила мои потребности! Еще раз спасибо :) Ответ в разделе 2.1 .
user2856414
2
(Настройте ссылку в обратном направлении, так как здесь у нас есть более принятый ответ, чем там).
Чарльз Даффи

Ответы:

107

Используйте mapfileкоманду:

mapfile -t myArray < file.txt

Ошибка using for- идиоматический способ перебора строк файла:

while IFS= read -r line; do echo ">>$line<<"; done < file.txt

См. BashFAQ / 005 для более подробной информации.

Гленн Джекман
источник
5
Так как это пропагандируется в качестве канонического д & а, вы могли бы также включать в себя то , что упоминается в ссылке: while IFS= read -r; do lines+=("$REPLY"); done <file.
fedorqui 'SO, перестань причинять вред'
10
mapfile не существует в версиях bash до 4.x
ericslaw
14
Башу 4 сейчас около 5 лет. Обновить.
Гленн Джекман
5
Несмотря на то, что bash 4 был выпущен в 2009 году, комментарий @ericslaw остается актуальным, поскольку многие машины по-прежнему поставляются с bash 3.x (и не будут обновляться, если bash выпущен под GPLv3). Если вы заинтересованы в переносимости, это важно отметить
De Novo
12
Проблема не в том, что разработчик не может установить обновленную версию, а в том, что разработчик должен знать, что используемый скрипт mapfileне будет работать должным образом на многих машинах без дополнительных шагов. Macs @ericslaw продолжат поставляться с bash 3.2.57 в обозримом будущем. В более поздних версиях используется лицензия, по которой Apple должна предоставлять или разрешать то, что они не хотят делиться или разрешать.
De Novo
23

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

arr=()
while IFS= read -r line; do
  arr+=("$line")
done < file

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

arr=()
while IFS= read -r line || [[ "$line" ]]; do
  arr+=("$line")
done < file

Связанный:

Codeforester
источник
Я считаю, что мне нужно заключать скобки, IFS= read -r line || [[ "$line" ]]чтобы это работало. В остальном отлично работает!
Татьяна Рачева
@ Татьяна Рачева: разве не точка с запятой отсутствовала раньше do?
codeforester
9

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

oldIFS="$IFS"
IFS=$'\n' arr=($(<file))
IFS="$oldIFS"
echo "${arr[1]}" # It will print `A Dog`.

Заметка:

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

Джахид
источник
Есть ли способ установить IFSтолько временно (чтобы оно восстанавливало свое исходное значение после этой команды), сохраняя при этом присвоение arr?
Hugues
1
Обратите внимание, что расширение имени файла все еще происходит; напримерIFS=$'\n' arr=($(echo 'a 1'; echo '*'; echo 'b 2')); printf "%s\n" "${arr[@]}"
Hugues
@Hugues: yap, расширение имени файла все еще происходит. Я добавлю немного информации ... спасибо ..
Джахид
Извините, я не согласен. IFS=... commandне изменяется IFSв текущей оболочке. Однако IFS=... other_variable=...(без какой-либо команды) изменяется как в текущей оболочке, так IFSи other_variableв ней.
Хьюз
1
Благодарность! Это работает; К сожалению, нет более простого способа, поскольку мне нравятся arr=обозначения (по сравнению с mapfile/ readarray).
Hugues
4

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

#!/bin/bash
i=0
while read line 
do
        arr[$i]="$line"
        i=$((i+1))
done < file.txt
Пратик Джоши
источник
1
Как получить доступ к массиву?
hola
4

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

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

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

array=( $(mycommand) )

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

mapfile -t array < <(mycommand)

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

IFS=" " read -r -a array <<< "$(mycommand)"

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

Кэмерон Лоуэлл Палмер
источник
0

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

mapfile -t myArray < file.txt

Я сделал прокладку на случай,mapfile если вы mapfileпо какой-то причине хотите использовать на bash <4.x. Он использует существующую mapfileкоманду, если вы используете bash> = 4.x

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

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

дело
источник