Tworzenie tablicy z pliku tekstowego w Bash

86

Skrypt pobiera adres URL, analizuje go pod kątem wymaganych pól i przekierowuje dane wyjściowe do zapisania w pliku plik.txt . Dane wyjściowe są zapisywane w nowym wierszu za każdym razem, gdy zostanie znalezione pole.

plik.txt

A Cat
A Dog
A Mouse 
etc... 

Chcę wziąć file.txti utworzyć z niego tablicę w nowym skrypcie, w którym każda linia ma być własną zmienną łańcuchową w tablicy. Do tej pory próbowałem:

#!/bin/bash

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

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

Kiedy uruchamiam ten skrypt, białe znaki powodują, że słowa są dzielone, a zamiast pobierać

Pożądane wyjście

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

W końcu otrzymuję to:

Rzeczywista wydajność

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

Jak mogę dostosować pętlę poniżej tak, aby cały ciąg w każdym wierszu odpowiadał jeden do jednego z każdą zmienną w tablicy?

user2856414
źródło
5
O to właśnie chodzi w Bash FAQ 001 . Również ta sekcja tematu tablicy w Bash FAQ 005 .
Etan Reisner
1
Podłączyłbym to jako duplikat stackoverflow.com/questions/11393817/… , ale zaakceptowana odpowiedź jest okropna.
Charles Duffy
Etan, bardzo dziękuję za tak szybką i dokładną odpowiedź! Próbowałem przeszukać moje pytanie na forach, ale nie przyszło mi do głowy, aby znaleźć FAQ na temat stackoverflow. Polecenie mapfile dokładnie odpowiadało moim potrzebom! Jeszcze raz dziękujemy :) Odpowiedź w sekcji 2.1 .
user2856414
2
(Ustaw łącze w przeciwnym kierunku, ponieważ tutaj mamy lepszą akceptowaną odpowiedź niż tam).
Charles Duffy

Odpowiedzi:

107

Użyj mapfilepolecenia:

mapfile -t myArray < file.txt

Błąd polega na użyciu for- idiomatyczny sposób na pętlę po wierszach pliku to:

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

Więcej informacji można znaleźć w BashFAQ / 005 .

glenn jackman
źródło
5
Od tego jest promowana jako kanoniczną Q & A, można również zawierać co jest wymienione w linku: while IFS= read -r; do lines+=("$REPLY"); done <file.
fedorqui 'SO przestać krzywdzić'
10
mapfile nie istnieje w wersjach basha wcześniejszych niż 4.x
ericslaw
14
Bash 4 ma teraz około 5 lat. Aktualizacja.
glenn jackman
5
Pomimo, że bash 4 został wydany w 2009 roku, komentarz @ ericslaw pozostaje aktualny, ponieważ wiele maszyn nadal jest dostarczanych z bash 3.x (i nie zostanie zaktualizowany, o ile bash zostanie wydany na licencji GPLv3). Jeśli interesuje Cię przenośność, musisz zwrócić uwagę
De Novo
12
problemem nie jest to, że programista nie może zainstalować zaktualizowanej wersji, ale programista powinien mieć świadomość, że skrypt używający mapfilenie będzie działał zgodnie z oczekiwaniami na wielu komputerach bez dodatkowych kroków. @ericslaw macs będą nadal dostarczane z bash 3.2.57 w najbliższej przyszłości. Nowsze wersje używają licencji, która wymagałaby od Apple udostępniania lub zezwalania na rzeczy, których nie chcą udostępniać lub na które nie chcą.
De Novo
23

mapfilei readarray(które są synonimami) są dostępne w wersji Bash 4 i nowszych. Jeśli masz starszą wersję Bash, możesz użyć pętli do wczytania pliku do tablicy:

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

W przypadku, gdy plik ma niekompletną (brakującą nową linię) ostatnią linię, możesz skorzystać z tej alternatywy:

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

Związane z:

codeforester
źródło
Uważam, że muszę umieścić nawiasy IFS= read -r line || [[ "$line" ]], aby zadziałało. W przeciwnym razie działa świetnie!
Tatiana Racheva
@TatianaRacheva: czy to nie średnik, którego wcześniej brakowało do?
codeforester
9

Ty też możesz to zrobić:

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

Uwaga:

Nadal występuje rozszerzenie nazwy pliku. Na przykład, jeśli istnieje wiersz z literałem *, zostanie on rozszerzony na wszystkie pliki w bieżącym folderze. Więc używaj go tylko wtedy, gdy twój plik jest wolny od tego rodzaju scenariuszy.

Jahid
źródło
Czy istnieje sposób, aby ustawić IFStylko tymczasowo (aby odzyskał swoją pierwotną wartość po tym poleceniu), jednocześnie utrzymując przypisanie do arr?
Hugues
1
Zauważ, że nadal występuje rozwijanie nazw plików; np.IFS=$'\n' arr=($(echo 'a 1'; echo '*'; echo 'b 2')); printf "%s\n" "${arr[@]}"
Hugues
@Hugues: tak, nadal występuje rozwijanie nazw plików. Dodam trochę informacji .. thnks ..
Jahid
Przepraszam, nie zgadzam się. IFS=... commandnie zmienia się IFSw bieżącej powłoce. Jednak IFS=... other_variable=...(bez żadnego polecenia) zmienia zarówno IFSiw other_variablebieżącej powłoce.
Hugues
1
Dzięki! To działa; szkoda, że ​​nie ma prostszego sposobu, ponieważ podoba mi się arr=notacja (w porównaniu do mapfile/ readarray).
Hugues
4

Możesz po prostu odczytać każdy wiersz z pliku i przypisać go do tablicy.

#!/bin/bash
i=0
while read line 
do
        arr[$i]="$line"
        i=$((i+1))
done < file.txt
Prateek Joshi
źródło
1
Jak uzyskujesz dostęp do tablicy?
hola
4

Użyj pliku mapy lub przeczytaj -a

Zawsze sprawdzaj swój kod za pomocą shellcheck . Często daje prawidłową odpowiedź. W tym przypadku SC2207 obejmuje wczytywanie do tablicy pliku, który zawiera wartości oddzielone spacjami lub nowej linii.

Nie rób tego

array=( $(mycommand) )

Pliki z wartościami oddzielonymi znakami nowej linii

mapfile -t array < <(mycommand)

Pliki z wartościami oddzielonymi spacjami

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

Na stronie sprawdzania powłoki znajdziesz uzasadnienie, dlaczego jest to uważane za najlepszą praktykę.

Cameron Lowell Palmer
źródło
0

Ta odpowiedź mówi, aby użyć

mapfile -t myArray < file.txt

Zrobiłem podkładkę dlamapfile jeśli chcesz używać mapfilena bash <4.x z jakiegokolwiek powodu. Używa istniejącego mapfilepolecenia, jeśli używasz bash> = 4.x

Obecnie tylko opcje -d i -tdziałają. Ale to powinno wystarczyć dla powyższego polecenia. Testowałem tylko na macOS. W systemie macOS Sierra 10.12.6 bash systemowy to3.2.57(1)-release . Więc podkładka może się przydać. Możesz także zaktualizować bash za pomocą homebrew, samodzielnie zbudować bash itp.

Używa tej techniki do ustawiania zmiennych na jednym stosie wywołań.

dosentmatter
źródło