Lire un fichier ligne par ligne en affectant la valeur à une variable

753

J'ai le fichier .txt suivant:

Marco
Paolo
Antonio

Je veux le lire ligne par ligne, et pour chaque ligne, je veux attribuer une valeur de ligne .txt à une variable. En supposant que ma variable soit $name, le flux est:

  • Lire la première ligne du fichier
  • Assign $name= "Marco"
  • Faites quelques tâches avec $name
  • Lire la deuxième ligne du fichier
  • Assign $name= "Paolo"
Marco
la source
3
Ces questions peuvent-elles être fusionnées d'une manière ou d'une autre? Les deux ont de très bonnes réponses qui mettent en évidence différents aspects du problème, les mauvaises réponses ont des explications détaillées dans les commentaires ce qui est mauvais à leur sujet, et pour l'instant vous ne pouvez pas vraiment avoir un aperçu complet de ce qu'il faut considérer, à partir des réponses de une seule question de la paire. Il serait utile de tout avoir en un seul endroit, plutôt que de les répartir sur 2 pages.
Egor Hans

Réponses:

1358

Ce qui suit lit un fichier passé en argument ligne par ligne:

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

Il s'agit du formulaire standard pour lire les lignes d'un fichier dans une boucle. Explication:

  • IFS=(ou IFS='') empêche la suppression des espaces de début / fin.
  • -r empêche les échappements antislash d'être interprétés.

Ou vous pouvez le mettre dans un script d'aide de fichier bash, exemple de contenu:

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

Si ce qui précède est enregistré dans un script avec un nom de fichier readfile, il peut être exécuté comme suit:

chmod +x readfile
./readfile filename.txt

Si le fichier n'est pas un fichier texte POSIX standard (= non terminé par un caractère de nouvelle ligne), la boucle peut être modifiée pour gérer les lignes partielles de fin:

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

Ici, || [[ -n $line ]]empêche la dernière ligne d'être ignorée si elle ne se termine pas par un \n(car readretourne un code de sortie différent de zéro lorsqu'il rencontre EOF).

Si les commandes à l'intérieur de la boucle lisent également à partir de l'entrée standard, le descripteur de fichier utilisé par readpeut être remplacé par quelque chose d'autre (éviter les descripteurs de fichier standard ), par exemple:

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

(Les obus non-Bash peuvent ne pas savoir read -u3; utilisez read <&3plutôt.)

cppcoder
la source
23
Il y a une mise en garde avec cette méthode. Si quelque chose à l'intérieur de la boucle while est interactif (par exemple, lit depuis stdin), alors il prendra son entrée à partir de $ 1. Vous n'aurez pas la possibilité d'entrer des données manuellement.
carpie
10
À noter - certaines commandes rompent (comme dans, elles rompent la boucle). Par exemple, sshsans l' -nindicateur, vous échapperez effectivement à la boucle. Il y a probablement une bonne raison à cela, mais il m'a fallu un certain temps pour déterminer ce qui provoquait l'échec de mon code avant de découvrir cela.
Alex
6
comme une ligne: tandis que IFS = '' read -r line || [[-n "$ line"]]; faire écho "$ line"; fait <nom de fichier
Joseph Johnson
8
@ OndraŽižka, cela est dû à la ffmpegconsommation de stdin. Ajoutez </dev/nullà votre ffmpegligne et il ne pourra pas, ou utilisez un autre FD pour la boucle. Cette approche "FD alternative" ressemble while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Charles Duffy
9
grommeler re: conseiller une .shextension. Les exécutables sous UNIX n'ont généralement pas d'extensions du tout (vous ne les exécutez pas ls.elf), et avoir un bash shebang (et des outils bash uniquement tels que [[ ]]) et une extension impliquant la compatibilité POSIX sh est contradictoire en interne.
Charles Duffy
309

Je vous encourage à utiliser le -rdrapeau readqui signifie:

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

Je cite de man 1 read.

Une autre chose est de prendre un nom de fichier comme argument.

Voici le code mis à jour:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"
Grzegorz Wierzowiecki
la source
4
Découpe l'espace avant et arrière de la ligne
barfuin
@Thomas et qu'arrive-t-il aux espaces du milieu? Astuce: tentative d'exécution de commande indésirable.
kmarsh
1
Cela a fonctionné pour moi, contrairement à la réponse acceptée.
Neurotransmetteur
3
@TranslucentCloud, si cela fonctionnait et que la réponse acceptée ne fonctionnait pas, je soupçonne que votre shell l'était sh, non bash; la commande de test étendu utilisée dans la || [[ -n "$line" ]]syntaxe de la réponse acceptée est un bashisme. Cela dit, cette syntaxe a en fait une signification pertinente: elle fait continuer la boucle pour la dernière ligne du fichier d'entrée même si elle n'a pas de nouvelle ligne. Si vous vouliez le faire d'une manière compatible POSIX, vous voudriez || [ -n "$line" ], en utilisant [plutôt que [[.
Charles Duffy
3
Cela dit, cela doit encore être modifié pour définir IFS=la readpour empêcher le découpage des espaces blancs.
Charles Duffy
132

L'utilisation du modèle Bash suivant devrait vous permettre de lire une valeur à la fois dans un fichier et de la traiter.

while read name; do
    # Do what you want to $name
done < filename
OneWinged
la source
14
comme une ligne: en lisant le nom; faire écho $ {nom}; fait <nom du fichier
Joseph Johnson
4
@CalculusKnight, cela n'a "fonctionné" que parce que vous n'avez pas utilisé de données suffisamment intéressantes pour tester. Essayez du contenu avec des barres obliques inverses ou une ligne contenant uniquement *.
Charles Duffy
7
@Matthias, les hypothèses qui s'avèrent finalement fausses sont l'une des plus grandes sources de bogues, à la fois affectant la sécurité et autres. L'événement de perte de données le plus important que j'ai jamais vu était dû à un scénario que quelqu'un supposait ne "se produirait jamais littéralement" - un débordement de tampon déversant de la mémoire aléatoire dans un tampon utilisé pour nommer les fichiers, provoquant un script qui faisait des hypothèses sur les noms qui pourraient éventuellement jamais avoir un comportement très, très malheureux.
Charles Duffy
5
@Matthias, ... et c'est particulièrement vrai ici, car les exemples de code présentés sur StackOverflow sont destinés à être utilisés comme outils pédagogiques, pour que les gens réutilisent les modèles dans leur propre travail!
Charles Duffy
5
@Matthias, je suis totalement en désaccord avec l'affirmation selon laquelle "vous ne devez concevoir votre code que pour les données que vous attendez". Les cas inattendus sont où se trouvent vos bogues, où sont vos vulnérabilités de sécurité - les gérer est la différence entre le code slapdash et le code robuste. Certes, cette manipulation n'a pas besoin d'être sophistiquée - elle peut simplement être "quitter avec une erreur" - mais si vous n'avez aucune manipulation, votre comportement dans les cas inattendus n'est pas défini.
Charles Duffy
76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done
Gert
la source
8
Rien contre les autres réponses, peut-être qu'elles sont plus sophistiquées, mais je vote pour cette réponse car elle est simple, lisible et suffit pour ce dont j'ai besoin. Notez que pour que cela fonctionne, le fichier texte à lire doit se terminer par une ligne vierge (c'est-à-dire qu'il faut appuyer Enteraprès la dernière ligne), sinon la dernière ligne sera ignorée. C'est du moins ce qui m'est arrivé.
Antonio Vinicius Menezes Medei
12
Utilisation inutile de chat, sans vergogne?
Brian Agnew
5
Et la citation est cassée; et vous ne devez pas utiliser de noms de variables en majuscules car ceux-ci sont réservés à l'utilisation du système.
tripleee
7
@AntonioViniciusMenezesMedei, ... de plus, j'ai vu des gens subir des pertes financières parce qu'ils pensaient que ces mises en garde n'auraient jamais d'importance pour eux; n'a pas appris les bonnes pratiques; puis a suivi les habitudes auxquelles ils étaient habitués lors de l'écriture de scripts qui géraient les sauvegardes des données de facturation critiques. Apprendre à bien faire les choses est important.
Charles Duffy
6
Un autre problème ici est que le canal ouvre un nouveau sous-shell, c'est-à-dire que toutes les variables définies à l'intérieur de la boucle ne peuvent pas être lues une fois la boucle terminée.
mxmlnkn
20

De nombreuses personnes ont publié une solution sur-optimisée. Je ne pense pas que ce soit incorrect, mais je pense humblement qu'une solution moins optimisée sera souhaitable pour permettre à chacun de comprendre facilement comment cela fonctionne. Voici ma proposition:

#!/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"
Raul Luna
la source
20

Utilisation:

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

Si vous avez réglé IFSdifféremment, vous obtiendrez des résultats étranges.

user3546841
la source
34
C'est une horrible méthode . Veuillez ne pas l'utiliser sauf si vous voulez avoir des problèmes de globalisation qui auront lieu avant que vous ne vous en rendiez compte!
gniourf_gniourf
13
@MUYBelgium avez-vous essayé avec un fichier qui en contient un seul *sur une ligne? Quoi qu'il en soit, c'est un contre-modèle . Ne lisez pas les lignes avec pour .
gniourf_gniourf
2
@ OndraŽižka, l' readapproche est l' approche des meilleures pratiques par consensus communautaire . La mise en garde que vous mentionnez dans votre commentaire est celle qui s'applique lorsque votre boucle exécute des commandes (telles que ffmpeg) qui lisent à partir de stdin, trivialement résolues en utilisant un FD non stdin pour la boucle ou en redirigeant l'entrée de ces commandes. En revanche, contourner le bogue de globbing dans votre forapproche -loop signifie apporter (puis devoir inverser) les modifications des paramètres globaux du shell.
Charles Duffy
1
@ OndraŽižka, ... en outre, l' forapproche en boucle que vous utilisez ici signifie que tout le contenu doit être lu avant que la boucle puisse commencer à s'exécuter, ce qui la rend entièrement inutilisable si vous effectuez une boucle sur des gigaoctets de données même si vous avez désactivé globbing; la while readboucle n'a besoin de stocker que les données d'une seule ligne à la fois, ce qui signifie qu'elle peut commencer à s'exécuter pendant que le sous-processus générant du contenu est toujours en cours d'exécution (donc utilisable à des fins de streaming), et a également une consommation de mémoire limitée.
Charles Duffy
1
En fait, même les whileapproches basées sur des bases semblent avoir des problèmes de caractères *. Voir les commentaires de la réponse acceptée ci-dessus. Cependant, ne vous opposez pas à une for-itération sur les fichiers étant un contre-modèle.
Egor Hans
9

Si vous devez traiter à la fois le fichier d'entrée et l'entrée utilisateur (ou tout autre élément de stdin), utilisez la solution suivante:

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

Basé sur la réponse acceptée et sur le tutoriel de redirection bash-hackers .

Ici, nous ouvrons le descripteur de fichier 3 pour le fichier passé comme argument de script et disons readd'utiliser ce descripteur comme input ( -u 3). Ainsi, nous laissons le descripteur d'entrée par défaut (0) attaché à un terminal ou à une autre source d'entrée, capable de lire les entrées utilisateur.

gluk47
la source
7

Pour une gestion correcte des erreurs:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}
bviktor
la source
Pourriez-vous ajouter quelques explications?
Tyler Christian
Malheureusement, il manque la dernière ligne du fichier.
ungalcrys
... et aussi, en raison du manque de citation, des lignes munges contenant des caractères génériques - comme décrit dans BashPitfalls # 14 .
Charles Duffy
0

Les éléments suivants ne feront qu'imprimer le contenu du fichier:

cat $Path/FileName.txt

while read line;
do
echo $line     
done
Rekha Ghanate
la source
1
Cette réponse n'ajoute vraiment rien par rapport aux réponses existantes, ne fonctionne pas en raison d'une faute de frappe / bug et se casse de plusieurs façons.
Konrad Rudolph
0

Utiliser l'outil IFS (séparateur de champ interne) dans bash, définit le caractère à l'aide de séparer les lignes en jetons, comprend par défaut < tab > / < espace > / < newLine >

étape 1 : chargez les données du fichier et insérez-les dans la liste:

# 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>"

étape 2 : maintenant itérez et imprimez la sortie:

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

echo specific index in array : Accès à une variable dans le tableau:

echo "${array[0]}"
avivamg
la source