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

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

file.txt

A Cat
A Dog
A Mouse 
etc... 

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

#!/bin/bash

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

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

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

Требуемый выход

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

Я получаю следующее:

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

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

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

Ответ 1

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

mapfile -t myArray < file.txt

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

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

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

Ответ 2

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

Связанные с:

Ответ 3

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

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

Примечание:

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

Ответ 4

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

#!/bin/bash
i=0
while read line 
do
        arr[$i]="$line"
        i=$((i+1))
done < file.txt

Ответ 5

Использовать файл карты или прочитать -a

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

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

array=( $(mycommand) )

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

mapfile -t array < <(mycommand)

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

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

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

Ответ 6

Этот ответ говорит, чтобы использовать

mapfile -t myArray < file.txt

Я сделал shim для 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 самостоятельно и т.д.

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