Створення масиву з текстового файлу в 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... 

Як я можу налаштувати цикл нижче, щоб весь рядок у кожному рядку відповідав один до одного з кожною змінною в масиві?


5
Це те, що стосується Bash FAQ 001 . Також цей розділ теми масиву в Bash FAQ 005 .
Етан Рейснер

1
Я хотів би зв’язати це як копію stackoverflow.com/questions/11393817/… , але прийнята відповідь там жахлива.
Чарльз Даффі

Ітане, дякую тобі за таку швидку та точну відповідь! Я намагався шукати своє запитання на форумах, але не думав шукати поширені запитання щодо stackoverflow. Команда mapfile точно відповідала моїм потребам! Ще раз спасибі :) Відповідь у розділі 2.1 .
user2856414

2
(Встановіть посилання у зворотному напрямку, оскільки тут ми маємо кращу відповідь, ніж у нас там).
Чарльз Даффі

Відповіді:


107

Використовуйте mapfileкоманду:

mapfile -t myArray < file.txt

Помилка використовується 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 prestani шкодити'

10
mapfile не існує у версіях bash до 4.x
ericslaw

14
Bash 4 зараз близько 5 років. Оновлення.
glenn jackman

5
Незважаючи на те, що bash 4 вийшов у 2009 році, коментар @ ericslaw залишається актуальним, оскільки багато машин все ще постачаються з bash 3.x (і не оновлюватимуться, доки bash буде випущений під GPLv3). Якщо вас цікавить портативність, важливо відзначити
De Novo

12
проблема не в тому, що розробник не може встановити оновлену версію, а в тому, що розробник повинен пам’ятати, що сценарій, що використовує mapfile, не працюватиме належним чином на багатьох машинах без додаткових кроків. Маки @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

Пов’язані:


Я вважаю, що мені потрібно розставити дужки, IFS= read -r line || [[ "$line" ]]щоб це працювало. В іншому випадку це чудово працює!
Тетяна Рачева

@TatianaRacheva: хіба не крапка з комою, яка відсутня раніше 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: яп, розширення імені файлу все ще відбувається. Я додам цю частинку інформації .. думки ..
Джахід

Вибачте, я не згоден. IFS=... commandне змінюється IFSв поточній оболонці. Тим НЕ менше, IFS=... other_variable=...(без команди) не зміниться , як IFSі other_variableв поточному оболонці.
Hugues

1
Дякую! Це працює; прикро, що не існує простішого способу, оскільки мені подобається arr=позначення (порівняно з mapfile/ readarray).
Hugues

4

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

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

1
Як отримати доступ до масиву?
хола

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на баш <4.x за якою - небудь причини. Він використовує існуючу mapfileкоманду, якщо ви знаходитесь на bash> = 4.x

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

Він використовує цю техніку для встановлення змінних на один стек викликів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.