Creazione di un array da un file di testo in Bash


88

Uno script prende un URL, lo analizza per i campi richiesti e reindirizza il suo output per salvarlo in un file, file.txt . L'output viene salvato su una nuova riga ogni volta che viene trovato un campo.

file.txt

A Cat
A Dog
A Mouse 
etc... 

Voglio prendere file.txte creare un array da esso in un nuovo script, in cui ogni riga diventa la propria variabile di stringa nell'array. Finora ho provato:

#!/bin/bash

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

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

Quando eseguo questo script, gli spazi vuoti risultano in parole che vengono divise e invece di ottenere

Uscita desiderata

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

Finisco per ottenere questo:

Uscita effettiva

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

Come posso regolare il ciclo sottostante in modo che l'intera stringa su ogni riga corrisponda uno a uno con ogni variabile nell'array?


5
Questo è ciò di cui tratta Bash FAQ 001 . Anche questa sezione dell'argomento degli array nella FAQ 005 di Bash .
Etan Reisner

1
Lo collegherei come un duplicato di stackoverflow.com/questions/11393817/… , ma la risposta accettata è orribile.
Charles Duffy

Etan, grazie mille per una risposta così veloce e precisa! Avevo provato a cercare la mia domanda nei forum, ma non pensavo di cercare le FAQ su stackoverflow. Il comando mapfile ha risposto esattamente alle mie esigenze! Grazie ancora :) Risposta nella sezione 2.1 .
user2856414

2
(Imposta il collegamento nella direzione opposta, poiché qui abbiamo una risposta accettata migliore di quella che abbiamo lì).
Charles Duffy

Risposte:


110

Usa il mapfilecomando:

mapfile -t myArray < file.txt

L'errore sta usando for: il modo idiomatico per eseguire il ciclo su righe di un file è:

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

Vedi BashFAQ / 005 per maggiori dettagli.


5
Dal momento che questo viene promosso come il q canonica e una, si potrebbe anche includere quanto riportato nel link: while IFS= read -r; do lines+=("$REPLY"); done <file.
fedorqui 'SO smettila di nuocere'

10
mapfile non esiste nelle versioni bash precedenti alla 4.x
ericslaw

14
Bash 4 ha circa 5 anni ormai. Upgrade.
glenn jackman

5
Nonostante bash 4 sia stato rilasciato nel 2009, il commento di @ericslaw rimane rilevante perché molte macchine sono ancora fornite con bash 3.x (e non verranno aggiornate, a patto che bash sia rilasciato sotto GPLv3). Se sei interessato alla portabilità, è una cosa importante da notare
De Novo

12
il problema non è che uno sviluppatore non può installare una versione aggiornata, è che uno sviluppatore deve essere consapevole del fatto che uno script che utilizza mapfilenon verrà eseguito come previsto su molte macchine senza passaggi aggiuntivi. I mac @ericslaw continueranno a essere forniti con bash 3.2.57 per il prossimo futuro. Le versioni più recenti utilizzano una licenza che richiederebbe a Apple di condividere o consentire cose che non vogliono condividere o consentire.
De Novo

24

mapfilee readarray(che sono sinonimi) sono disponibili nella versione Bash 4 e successive. Se hai una versione precedente di Bash, puoi utilizzare un ciclo per leggere il file in un array:

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

Nel caso in cui il file abbia un'ultima riga incompleta (mancante di nuova riga), puoi usare questa alternativa:

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

Relazionato:


Trovo che devo mettere tra parentesi IFS= read -r line || [[ "$line" ]]perché funzioni. Altrimenti funziona benissimo!
Tatiana Racheva

@TatianaRacheva: non è che il punto e virgola che prima mancava do?
codeforester

9

Puoi farlo anche tu:

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

Nota:

L'espansione del nome file si verifica ancora. Ad esempio, se è presente una riga con un letterale *, si espanderà a tutti i file nella cartella corrente. Quindi usalo solo se il tuo file è privo di questo tipo di scenario.


C'è un modo per impostare IFSsolo temporaneamente (in modo che recuperi il suo valore originale dopo questo comando), pur mantenendo l'assegnazione a arr?
Hugues

1
Notare che l'espansione del nome file si verifica ancora; ad esempioIFS=$'\n' arr=($(echo 'a 1'; echo '*'; echo 'b 2')); printf "%s\n" "${arr[@]}"
Hugues

@ Hugues: yap, l'espansione del nome del file si verifica ancora. Aggiungerò quel po 'di informazioni .. grazie ..
Jahid

Scusa, non sono d'accordo. IFS=... commandnon cambia IFSnella shell corrente. Tuttavia, IFS=... other_variable=...(senza alcun comando) cambia sia IFSe other_variablenella shell corrente.
Hugues

1
Grazie! Questo funziona; è un peccato che non ci sia un modo più semplice dato che mi piace la arr=notazione (rispetto a mapfile/ readarray).
Hugues

4

Puoi semplicemente leggere ogni riga del file e assegnarla a un array.

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

1
Come si accede all'array?
hola

4

Usa mapfile o leggi -a

Controlla sempre il tuo codice usando shellcheck . Spesso ti darà la risposta corretta. In questo caso SC2207 copre la lettura di un file che ha valori separati da spazi o da una nuova riga in un array.

Non farlo

array=( $(mycommand) )

File con valori separati da una nuova riga

mapfile -t array < <(mycommand)

File con valori separati da spazi

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

La pagina shellcheck ti fornirà la motivazione per cui questa è considerata la migliore pratica.


0

Questa risposta dice di usare

mapfile -t myArray < file.txt

Ho creato uno shim per mapfilese vuoi usare mapfilesu bash <4.x per qualsiasi motivo. Usa il mapfilecomando esistente se sei su bash> = 4.x

Attualmente, solo opzioni -de -tlavoro. Ma dovrebbe essere sufficiente per quel comando sopra. Ho provato solo su macOS. Su macOS Sierra 10.12.6, la bash di sistema è 3.2.57(1)-release. Quindi lo spessore può tornare utile. Puoi anche aggiornare il tuo bash con homebrew, costruire bash da solo, ecc.

Usa questa tecnica per impostare le variabili su uno stack di chiamate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.