Erstellen eines Arrays aus einer Textdatei in Bash

85

Ein Skript nimmt eine URL, analysiert sie nach den erforderlichen Feldern und leitet ihre Ausgabe zum Speichern in die Datei file.txt um . Die Ausgabe wird jedes Mal in einer neuen Zeile gespeichert, wenn ein Feld gefunden wurde.

file.txt

A Cat
A Dog
A Mouse 
etc... 

Ich möchte file.txtein Array daraus in einem neuen Skript erstellen, wobei jede Zeile eine eigene Zeichenfolgenvariable im Array sein soll. Bisher habe ich versucht:

#!/bin/bash

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

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

Wenn ich dieses Skript ausführe, führt ein Leerzeichen dazu, dass Wörter geteilt werden und nicht

Gewünschte Ausgabe

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

Am Ende bekomme ich Folgendes:

Tatsächliche Ausgabe

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

Wie kann ich die Schleife unten so einstellen, dass die gesamte Zeichenfolge in jeder Zeile mit jeder Variablen im Array eins zu eins übereinstimmt?

user2856414
quelle
5
Darum geht es in Bash FAQ 001 . Auch dieser Abschnitt des Array-Themas in Bash FAQ 005 .
Etan Reisner
1
Ich würde dies als Duplikat von stackoverflow.com/questions/11393817/… verlinken , aber die akzeptierte Antwort dort ist schrecklich.
Charles Duffy
Etan, vielen Dank für die schnelle und genaue Antwort! Ich hatte versucht, meine Frage in den Foren zu suchen, dachte aber nicht daran, nach den FAQ zum Stackoverflow zu suchen. Der Befehl mapfile hat genau meine Bedürfnisse erfüllt! Nochmals vielen Dank :) Antwort in Abschnitt 2.1 .
user2856414
2
(Richten Sie den Link in die entgegengesetzte Richtung ein, da wir hier eine besser akzeptierte Antwort haben als dort).
Charles Duffy

Antworten:

107

Verwenden Sie den mapfileBefehl:

mapfile -t myArray < file.txt

Der Fehler wird verwendet for- die idiomatische Methode zum Überlaufen von Zeilen einer Datei ist:

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

Weitere Informationen finden Sie unter BashFAQ / 005 .

Glenn Jackman
quelle
5
Da dies als kanonisches Q & A beworben wird, können Sie auch das einschließen, was im Link erwähnt wird : while IFS= read -r; do lines+=("$REPLY"); done <file.
Fedorqui 'SO hör auf zu schaden'
10
mapfile existiert nicht in Bash-Versionen vor 4.x
ericslaw
14
Bash 4 ist jetzt ungefähr 5 Jahre alt. Aktualisierung.
Glenn Jackman
5
Obwohl Bash 4 im Jahr 2009 veröffentlicht wurde, bleibt der Kommentar von @ ericslaw relevant, da viele Computer immer noch mit Bash 3.x ausgeliefert werden (und nicht aktualisiert werden, solange Bash unter GPLv3 veröffentlicht wird). Wenn Sie an Portabilität interessiert sind, ist es wichtig zu beachten
De Novo
12
Das Problem ist nicht, dass ein Entwickler eine aktualisierte Version nicht installieren kann, sondern dass ein Entwickler sich bewusst sein sollte, dass ein Skript, das verwendet mapfilewird, auf vielen Computern ohne zusätzliche Schritte nicht wie erwartet ausgeführt wird. @ericslaw Macs werden auf absehbare Zeit weiterhin mit Bash 3.2.57 ausgeliefert. Neuere Versionen verwenden eine Lizenz, für die Apple Dinge freigeben oder zulassen muss, die sie nicht freigeben oder zulassen möchten.
De Novo
23

mapfileund readarray(die synonym sind) sind in Bash Version 4 und höher verfügbar. Wenn Sie eine ältere Version von Bash haben, können Sie die Datei mithilfe einer Schleife in ein Array einlesen:

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

Falls die Datei eine unvollständige (fehlende neue Zeile) letzte Zeile enthält, können Sie diese Alternative verwenden:

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

Verbunden:

Codeforester
quelle
Ich finde, dass ich Klammern setzen muss, IFS= read -r line || [[ "$line" ]]damit es funktioniert. Ansonsten funktioniert es super!
Tatiana Racheva
@TatianaRacheva: Ist es nicht das Semikolon, das vorher fehlte do?
Codeforester
9

Sie können dies auch tun:

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

Hinweis:

Die Dateinamenerweiterung erfolgt weiterhin. Wenn beispielsweise eine Zeile mit einem Literal vorhanden ist *, wird diese auf alle Dateien im aktuellen Ordner erweitert. Verwenden Sie es also nur, wenn Ihre Datei frei von solchen Szenarien ist.

Jahid
quelle
Gibt es eine Möglichkeit, IFSnur vorübergehend festzulegen (damit der ursprüngliche Wert nach diesem Befehl wiederhergestellt wird), während die Zuweisung an beibehalten wird arr?
Hugues
1
Beachten Sie, dass die Dateinamenerweiterung weiterhin erfolgt. zBIFS=$'\n' arr=($(echo 'a 1'; echo '*'; echo 'b 2')); printf "%s\n" "${arr[@]}"
Hugues
@Hugues: yap, Dateinamenerweiterung tritt immer noch auf. Ich werde diese Informationen hinzufügen..thnks ..
Jahid
Entschuldigung, ich bin anderer Meinung. IFS=... commandändert sich nicht IFSin der aktuellen Shell. Jedoch IFS=... other_variable=...(ohne Befehl) nicht beide ändern IFSund other_variablein dem aktuell Shell.
Hugues
1
Vielen Dank! Das funktioniert; Es ist bedauerlich, dass es keinen einfacheren Weg gibt, da mir die arr=Notation gefällt (im Vergleich zu mapfile/ readarray).
Hugues
4

Sie können einfach jede Zeile aus der Datei lesen und einem Array zuweisen.

#!/bin/bash
i=0
while read line 
do
        arr[$i]="$line"
        i=$((i+1))
done < file.txt
Prateek Joshi
quelle
1
Wie greifen Sie auf das Array zu?
hola
3

Verwenden Sie Mapfile oder lesen Sie -a

Überprüfen Sie Ihren Code immer mit Shellcheck . Es wird Ihnen oft die richtige Antwort geben. In diesem Fall behandelt SC2207 das Lesen einer Datei, deren Werte entweder durch Leerzeichen oder durch Zeilenumbrüche getrennt sind, in ein Array.

Tu das nicht

array=( $(mycommand) )

Dateien mit durch Zeilenumbrüche getrennten Werten

mapfile -t array < <(mycommand)

Dateien mit durch Leerzeichen getrennten Werten

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

Auf der Shellcheck-Seite erfahren Sie, warum dies als bewährte Methode angesehen wird.

Cameron Lowell Palmer
quelle
0

Diese Antwort sagt zu verwenden

mapfile -t myArray < file.txt

Ich habe eine Unterlegscheibe gemacht, mapfilewenn Sie mapfileBash <4.x aus irgendeinem Grund verwenden möchten . Es verwendet den vorhandenen mapfileBefehl, wenn Sie sich auf bash> = 4.x befinden

Derzeit nur Optionen -dund -tArbeit. Aber das sollte für diesen Befehl oben ausreichen. Ich habe nur unter macOS getestet. Unter macOS Sierra 10.12.6 ist die System-Bash 3.2.57(1)-release. So kann die Unterlegscheibe nützlich sein. Sie können Ihre Bash auch einfach mit Homebrew aktualisieren, Bash selbst erstellen usw.

Mit dieser Technik werden Variablen für einen Aufrufstapel eingerichtet.

dosentmatter
quelle