it-swarm.com.ru

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

Сценарий берет 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... 

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

61
user2856414

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

mapfile -t myArray < file.txt

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

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

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

72
glenn jackman

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

Связанные с:

9
codeforester

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

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

Замечания:

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

6
Jahid

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

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

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

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

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

array=( $(mycommand) )

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

mapfile -t array < <(mycommand)

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

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

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

1
Cameron Lowell Palmer

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

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

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

1
dosentmatter