Leggere un file riga per riga assegnando il valore a una variabile


753

Ho il seguente file .txt:

Marco
Paolo
Antonio

Voglio leggerlo riga per riga e per ogni riga desidero assegnare un valore di riga .txt a una variabile. Supponendo che la mia variabile sia $name, il flusso è:

  • Leggi la prima riga dal file
  • Assegna $name= "Marco"
  • Fai alcuni compiti con $name
  • Leggi la seconda riga dal file
  • Assegna $name= "Paolo"


3
Queste domande possono forse essere unite in qualche modo? Entrambi hanno delle risposte davvero buone che evidenziano diversi aspetti del problema, le risposte cattive hanno spiegazioni approfondite nei commenti che cosa è male su di loro, e al momento non puoi davvero avere una visione completa di cosa considerare, dalle risposte di una sola domanda dalla coppia. Sarebbe utile avere tutto in un punto, piuttosto che diviso in 2 pagine.
Egor Hans,

Risposte:


1357

Quanto segue legge un file passato come argomento riga per riga:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

Questo è il modulo standard per leggere le righe da un file in un ciclo. Spiegazione:

  • IFS=(o IFS='') impedisce di tagliare gli spazi iniziali / finali.
  • -r impedisce l'interpretazione delle escape di backslash.

Oppure puoi metterlo in uno script helper di file bash, contenuto di esempio:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

Se quanto sopra viene salvato in uno script con nome file readfile, può essere eseguito come segue:

chmod +x readfile
./readfile filename.txt

Se il file non è un file di testo POSIX standard (= non terminato da un carattere di nuova riga), il ciclo può essere modificato per gestire le righe parziali finali:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Qui, || [[ -n $line ]]impedisce che l'ultima riga venga ignorata se non termina con un \n(poiché readrestituisce un codice di uscita diverso da zero quando incontra EOF).

Se i comandi all'interno del ciclo leggono anche dall'input standard, il descrittore di file utilizzato da readpuò essere modificato in qualcos'altro (evitare i descrittori di file standard ), ad esempio:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Le shell non Bash potrebbero non saperlo read -u3; utilizzare read <&3invece.)


23
C'è un avvertimento con questo metodo. Se qualcosa all'interno del ciclo while è interattivo (ad esempio, legge da stdin), allora prenderà il suo input da $ 1. Non ti verrà data la possibilità di inserire i dati manualmente.
Carpie,

10
Da notare: alcuni comandi interrompono (come in, interrompono il loop) questo. Ad esempio, sshsenza la -nbandiera ti farà effettivamente scappare dal loop. Probabilmente c'è una buona ragione per questo, ma mi ci è voluto un po 'per capire cosa stava causando il fallimento del mio codice prima che lo scoprissi.
Alex,

6
come one-liner: while IFS = '' read -r line || [[-n "$ line"]]; fare eco "$ line"; done <nome file
Joseph Johnson,

8
@ OndraŽižka, causato dal ffmpegconsumo di stdin. Aggiungi </dev/nullalla tua ffmpeglinea e non sarà in grado, o utilizzare un FD alternativo per il loop. Appare l'approccio "FD alternativo" while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy,

9
grumble re: consigliare .shun'estensione. I file eseguibili su UNIX in genere non hanno affatto estensioni (non eseguite ls.elf) e avere un bash shebang (e strumenti solo bash come [[ ]]) e un'estensione che implica la compatibilità con POSIX sh è contraddittorio internamente.
Charles Duffy,

309

Ti incoraggio a usare la -rbandiera per readcui sta per:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

Sto citando man 1 read.

Un'altra cosa è prendere un nome file come argomento.

Ecco il codice aggiornato:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

4
Taglia lo spazio in testa e in coda dalla linea
barfuin,

@Thomas e cosa succede agli spazi nel mezzo? Suggerimento: esecuzione del comando non desiderata.
kmarsh

1
Questo ha funzionato per me, in contrasto con la risposta accettata.
Neurotrasmettitore

3
@TranslucentCloud, se questo ha funzionato e la risposta accettata non ha funzionato, sospetto che la tua shell shnon lo fosse bash; il comando di test esteso utilizzato nella || [[ -n "$line" ]]sintassi nella risposta accettata è un basismo. Detto questo, quella sintassi ha in realtà un significato pertinente: fa sì che il ciclo continui per l'ultima riga nel file di input anche se non ha una nuova riga. Se volessi farlo in un modo conforme a POSIX, lo vorresti || [ -n "$line" ]usare [invece di [[.
Charles Duffy,

3
Detto questo, questo deve ancora essere modificato per impostare IFS=lo readspazio per evitare di tagliare gli spazi bianchi.
Charles Duffy,

132

L'uso del seguente modello di Bash dovrebbe consentire di leggere un valore alla volta da un file ed elaborarlo.

while read name; do
    # Do what you want to $name
done < filename

14
come one-liner: mentre leggi il nome; echo $ {name}; done <nome file
Joseph Johnson,

4
@CalculusKnight, ha solo "funzionato" perché non hai usato dati sufficientemente interessanti per testarlo. Prova i contenuti con barre rovesciate o con una riga che contiene solo *.
Charles Duffy,

7
@Matthias, le ipotesi che alla fine si rivelano false sono una delle maggiori fonti di bug, che incidono sulla sicurezza e in altro modo. Il più grande evento di perdita di dati che abbia mai visto è stato a causa di uno scenario che qualcuno supponeva "letteralmente non sarebbe mai arrivato" - un overflow del buffer che scaricava memoria casuale in un buffer utilizzato per denominare i file, causando uno script che faceva ipotesi su quali nomi avrebbero mai potuto sembra avere un comportamento molto, molto sfortunato.
Charles Duffy,

5
@Matthias, ... e questo è particolarmente vero qui, poiché gli esempi di codice mostrati su StackOverflow sono intesi per essere utilizzati come strumenti di insegnamento, per consentire alle persone di riutilizzare gli schemi nel proprio lavoro!
Charles Duffy,

5
@Matthias, non sono assolutamente d'accordo con l'affermazione secondo cui "dovresti escogitare il tuo codice solo per i dati che ti aspetti". Casi inaspettati sono dove si trovano i tuoi bug, dove sono le tue vulnerabilità di sicurezza: gestirle è la differenza tra il codice slapdash e il codice robusto. Certo, non è necessario che la gestione sia sofisticata - può essere solo "esci con un errore" - ma se non hai alcuna gestione, il tuo comportamento in casi imprevisti è indefinito.
Charles Duffy,

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
Nulla contro le altre risposte, forse sono più sofisticate, ma ho votato a favore di questa risposta perché è semplice, leggibile ed è sufficiente per quello di cui ho bisogno. Si noti che, affinché funzioni, il file di testo da leggere deve terminare con una riga vuota (ovvero è necessario premere Enterdopo l'ultima riga), altrimenti l'ultima riga verrà ignorata. Almeno questo è quello che mi è successo.
Antonio Vinicius Menezes Medei,

12
Uso inutile del gatto, shurely?
Brian Agnew,

5
E la citazione è rotta; e non dovresti usare nomi di variabili maiuscoli perché sono riservati per l'uso del sistema.
Tripleee,

7
@AntonioViniciusMenezesMedei, ... inoltre, ho visto la gente sostenere perdite finanziarie perché presumevano che questi avvertimenti non avrebbero mai avuto importanza per loro; non è riuscito a imparare le buone pratiche; e quindi ha seguito le abitudini a cui erano abituati durante la scrittura di script che gestivano i backup dei dati di fatturazione critici. Imparare a fare le cose nel modo giusto è importante.
Charles Duffy,

6
Un altro problema qui è che la pipe apre una nuova subshell, cioè tutte le variabili impostate all'interno del ciclo non possono essere lette al termine del ciclo.
mxmlnkn,

20

Molte persone hanno pubblicato una soluzione ottimizzata. Non penso che sia errato, ma umilmente penso che una soluzione meno ottimizzata sarà desiderabile per consentire a tutti di capire facilmente come funziona. Ecco la mia proposta:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"

20

Uso:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

Se hai impostato IFSdiversamente otterrai risultati strani.


34
Questo è un metodo orribile . Per favore non usarlo a meno che tu non voglia avere problemi con il globbing che avverrà prima di realizzarlo!
gniourf_gniourf,

13
@MUYBelgium hai provato con un file che contiene un singolo *su una riga? Comunque, questo è un antipasto . Non leggere le righe con per .
gniourf_gniourf,

2
@ OndraŽižka, l' readapproccio è l' approccio delle migliori pratiche per consenso della comunità . L'avvertenza che menzioni nel tuo commento è quella che si applica quando il tuo ciclo esegue comandi (come ffmpeg) che leggono da stdin, banalmente risolti usando un FD non stdin per il ciclo o reindirizzando l'input di tali comandi. Al contrario, aggirare il bug forglobbing nell'approccio -loop significa apportare (e quindi dover invertire) le modifiche delle impostazioni shell-global.
Charles Duffy,

1
@ OndraŽižka, ... inoltre, l' forapproccio del ciclo si utilizza qui significa che tutto il contenuto deve essere letto nella prima che il ciclo può iniziare l'esecuzione a tutti, il che rende del tutto inutilizzabile se si sta ciclare su gigabyte di dati, anche se si dispone di disabile globbing; il while readloop non deve archiviare più di una riga di dati alla volta, il che significa che può iniziare l'esecuzione mentre il sottoprocesso che genera contenuto è ancora in esecuzione (essendo quindi utilizzabile per scopi di streaming) e ha anche limitato il consumo di memoria.
Charles Duffy,

1
In realtà, gli whileapprocci basati su pari sembrano avere problemi con * caratteri. Vedi i commenti della risposta accettata sopra. Tuttavia, non discutere contro l'iterazione sui file essendo un antipattern.
Egor Hans,

9

Se è necessario elaborare sia il file di input sia l'input dell'utente (o qualsiasi altra cosa da stdin), utilizzare la seguente soluzione:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

Basato sulla risposta accettata e sul tutorial di reindirizzamento di bash-hacker .

Qui, apriamo il descrittore di file 3 per il file passato come argomento dello script e diciamo readdi usare questo descrittore come input ( -u 3). Pertanto, lasciamo il descrittore di input predefinito (0) collegato a un terminale o un'altra sorgente di input, in grado di leggere l'input dell'utente.


7

Per una corretta gestione degli errori:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}

Potresti per favore aggiungere qualche spiegazione?
Tyler Christian,

Purtroppo manca l'ultima riga nel file.
ungalcrys,

... e anche, a causa della mancanza di quotazioni, munge le righe che contengono caratteri jolly, come descritto in BashPitfalls # 14 .
Charles Duffy,

0

Quanto segue stamperà solo il contenuto del file:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
Questa risposta in realtà non aggiunge nulla rispetto alle risposte esistenti, non funziona a causa di un errore di battitura / bug e si interrompe in molti modi.
Konrad Rudolph,

0

Usa lo strumento IFS (separatore di campo interno) in bash, definisce il carattere usando per separare le linee in token, per impostazione predefinita include < tab > / < spazio > / < newLine >

passaggio 1 : carica i dati del file e inseriscili nell'elenco:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

passo 2 : ora itera e stampa l'output:

for line in "${array[@]}"
  do
    echo "$line"
  done

indice specifico dell'eco nell'array : accesso a una variabile nell'array:

echo "${array[0]}"
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.