web-dev-qa-db-fra.com

Faire une boucle dans le contenu d'un fichier dans Bash

Comment parcourir toutes les lignes d'un fichier texte avec Bash ?

Avec ce script:

echo "Start!"
for p in (peptides.txt)
do
    echo "${p}"
done

Je reçois cette sortie sur l'écran:

Start!
./runPep.sh: line 3: syntax error near unexpected token `('
./runPep.sh: line 3: `for p in (peptides.txt)'

(Plus tard, je veux faire quelque chose de plus compliqué avec $p que de simplement sortir à l'écran.)


La variable d'environnement Shell est (à partir de env):

Shell=/bin/bash

/bin/bash --version sortie:

GNU bash, version 3.1.17(1)-release (x86_64-suse-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

cat /proc/version sortie:

Linux version 2.6.18.2-34-default (geeko@buildhost) (gcc version 4.1.2 20061115 (prerelease) (SUSE Linux)) #1 SMP Mon Nov 27 11:46:27 UTC 2006

Le fichier peptides.txt contient:

RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
1181
Peter Mortensen

Une façon de le faire est:

while read p; do
  echo "$p"
done <peptides.txt

Comme indiqué dans les commentaires, cela a pour effets secondaires de supprimer les espaces, de interpréter les séquences de barres obliques inverses et de sauter la dernière ligne s'il manque un saut de ligne final. Si ce sont des préoccupations, vous pouvez faire:

while IFS="" read -r p || [ -n "$p" ]
do
  printf '%s\n' "$p"
done < peptides.txt

Exceptionnellement, si le le corps de la boucle peut lire à partir de l'entrée standard , vous pouvez ouvrir le fichier en utilisant un descripteur de fichier différent:

while read -u 10 p; do
  ...
done 10<peptides.txt

Ici, 10 n’est qu’un nombre arbitraire (différent de 0, 1, 2).

1857
Bruno De Fraine
cat peptides.txt | while read line
do
   # do something with $line here
done

et la variante à une ligne:

cat peptides.txt | while read line; do_something_with_$line_here; done
365
Warren Young

Option 1a: Boucle While: Une seule ligne à la fois: Redirection d'entrée

#!/bin/bash
filename='peptides.txt'
echo Start
while read p; do 
    echo $p
done < $filename

Option 1b: Boucle While: Une seule ligne à la fois:
Ouvrez le fichier, à partir d’un descripteur de fichier (dans ce cas, le descripteur de fichier n ° 4).

#!/bin/bash
filename='peptides.txt'
exec 4<$filename
echo Start
while read -u4 p ; do
    echo $p
done

Option 2: Pour la boucle: Lire le fichier en variable unique et analyser.
Cette syntaxe analysera les "lignes" en fonction de tout espace blanc entre les jetons. Cela fonctionne toujours parce que les lignes de fichier d'entrée données sont des jetons Word uniques. S'il y avait plus d'un jeton par ligne, cette méthode ne fonctionnerait pas. De plus, lire le fichier complet dans une seule variable n'est pas une bonne stratégie pour les gros fichiers.

#!/bin/bash
filename='peptides.txt'
filelines=`cat $filename`
echo Start
for line in $filelines ; do
    echo $line
done
136
Stan Graves

Ce n'est pas meilleur que d'autres réponses, mais c'est un moyen de plus de faire le travail dans un fichier sans espaces (voir les commentaires). Je constate que j'ai souvent besoin d'un interlocuteur unique pour parcourir des listes dans des fichiers texte sans l'étape supplémentaire consistant à utiliser des fichiers de script séparés.

for Word in $(cat peptides.txt); do echo $Word; done

Ce format me permet de tout mettre dans une ligne de commande. Changez la partie "echo $ Word" en ce que vous voulez et vous pouvez émettre plusieurs commandes séparées par des points-virgules. L'exemple suivant utilise le contenu du fichier comme argument dans deux autres scripts que vous avez éventuellement écrits.

for Word in $(cat peptides.txt); do cmd_a.sh $Word; cmd_b.py $Word; done

Ou si vous avez l’intention d’utiliser cela comme un éditeur de flux (learn sed), vous pouvez vider la sortie dans un autre fichier comme suit.

for Word in $(cat peptides.txt); do cmd_a.sh $Word; cmd_b.py $Word; done > outfile.txt

J'ai utilisé ceux-ci comme indiqué ci-dessus parce que j'ai utilisé des fichiers texte dans lesquels je les ai créés avec un mot par ligne. (Voir les commentaires) Si vous avez des espaces que vous ne voulez pas séparer de vos mots/lignes, cela devient un peu plus laid, mais la même commande fonctionne toujours comme suit:

OLDIFS=$IFS; IFS=$'\n'; for line in $(cat peptides.txt); do cmd_a.sh $line; cmd_b.py $line; done > outfile.txt; IFS=$OLDIFS

Cela indique simplement au shell de se scinder uniquement sur les nouvelles lignes, et non sur les espaces, puis ramène l'environnement à ce qu'il était auparavant. À ce stade, vous voudrez peut-être envisager de tout insérer dans un script Shell plutôt que de tout compresser sur une seule ligne.

Bonne chance!

74
mightypile

Quelques autres points non couverts par d'autres réponses:

Lecture à partir d'un fichier délimité

# ':' is the delimiter here, and there are three fields on each line in the file
# IFS set below is restricted to the context of `read`, it doesn't affect any other code
while IFS=: read -r field1 field2 field3; do
  # process the fields
  # if the line has less than three fields, the missing fields will be set to an empty string
  # if the line has more than three fields, `field3` will get all the values, including the third field plus the delimiter(s)
done < input.txt

Lecture du résultat d'une autre commande en utilisant la substitution de processus

while read -r line; do
  # process the line
done < <(command ...)

Cette approche est meilleure que command ... | while read -r line; do ... parce que la boucle while s'exécute ici dans le shell actuel plutôt que dans un sous-shell comme dans le cas de ce dernier. Voir l'article associé ne variable modifiée dans une boucle while n'est pas mémorisée .

Lecture à partir d’une entrée délimitée par un zéro, par exemple find ... -print0

while read -r -d '' line; do
  # logic
  # use a second 'read ... <<< "$line"' if we need to tokenize the line
done < <(find /path/to/dir -print0)

Lecture connexe: BashFAQ/020 - Comment trouver et gérer en toute sécurité les noms de fichiers contenant des nouvelles lignes, des espaces ou les deux?

Lecture de plusieurs fichiers à la fois

while read -u 3 -r line1 && read -u 4 -r line2; do
  # process the lines
  # note that the loop will end when we reach EOF on either of the files, because of the `&&`
done 3< input1.txt 4< input2.txt

Basé sur @ chepner's répondre ici :

-u est une extension bash. Pour la compatibilité POSIX, chaque appel ressemblerait à quelque chose comme read -r X <&3.

Lecture d'un fichier entier dans un tableau (versions Bash antérieures à 4)

while read -r line; do
    my_array+=("$line")
done < my_file

Si le fichier se termine par une ligne incomplète (une nouvelle ligne est manquante à la fin), alors:

while read -r line || [[ $line ]]; do
    my_array+=("$line")
done < my_file

Lecture d'un fichier entier dans un tableau (versions Bash 4x et supérieures)

readarray -t my_array < my_file

ou

mapfile -t my_array < my_file

Puis

for line in "${my_array[@]}"; do
  # process the lines
done

Articles Similaires:

60
codeforester

Utilisez une boucle while, comme ceci:

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

Remarques:

  1. Si vous ne définissez pas correctement la IFS, vous perdrez l'indentation.

  2. Vous devriez presque toujours utiliser l'option -r avec read.

  3. Ne pas lire les lignes avec for

43
Jahid

Supposons que vous ayez ce fichier:

_$ cat /tmp/test.txt
Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR
_

Quatre éléments vont modifier la signification de la sortie du fichier lue par de nombreuses solutions Bash:

  1. La ligne vide 4;
  2. Espaces de début ou de fin sur deux lignes;
  3. Conserver la signification des lignes individuelles (c’est-à-dire que chaque ligne est un enregistrement);
  4. La ligne 6 ne se termine pas par un CR.

Si vous souhaitez que le fichier texte ligne par ligne, y compris les lignes vides et les lignes de terminaison sans CR, vous devez utiliser une boucle while et vous devez effectuer un autre test pour la dernière ligne.

Voici les méthodes qui peuvent changer le fichier (par rapport à ce que retourne cat):

1) Perdez la dernière ligne et les espaces de début et de fin:

_$ while read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
_

(Si vous faites plutôt _while IFS= read -r p; do printf "%s\n" "'$p'"; done </tmp/test.txt_, vous conservez les espaces de début et de fin tout en perdant la dernière ligne si elle n'est pas terminée par CR)

2) Utiliser la substitution de processus avec cat lit le fichier entier d'un trait et perd la signification des lignes individuelles:

_$ for p in "$(cat /tmp/test.txt)"; do printf "%s\n" "'$p'"; done
'Line 1
    Line 2 has leading space
Line 3 followed by blank line

Line 5 (follows a blank line) and has trailing space    
Line 6 has no ending CR'
_

(Si vous supprimez le _"_ de $(cat /tmp/test.txt), vous lisez le fichier Word par Word plutôt qu'un seul gulp. En outre, probablement pas ce qui est prévu ...)


La méthode la plus robuste et la plus simple pour lire un fichier ligne par ligne et conserver l’espacement est la suivante:

_$ while IFS= read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'    Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space    '
'Line 6 has no ending CR'
_

Si vous souhaitez supprimer les espaces de début et de commerce, supprimez la partie _IFS=_:

_$ while read -r line || [[ -n $line ]]; do printf "'%s'\n" "$line"; done </tmp/test.txt
'Line 1'
'Line 2 has leading space'
'Line 3 followed by blank line'
''
'Line 5 (follows a blank line) and has trailing space'
'Line 6 has no ending CR'
_

(Un fichier texte sans fin _\n_, bien qu’il soit assez courant, est considéré comme cassé sous POSIX. Si vous pouvez compter sur le _\n_ final, vous n’avez pas besoin de _|| [[ -n $line ]]_ dans le while boucle.)

Plus à la BASH FAQ

13
dawg

Si vous ne voulez pas que votre lecture soit interrompue par un caractère de nouvelle ligne, utilisez -

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "$line"
done < "$1"

Ensuite, exécutez le script avec le nom de fichier en paramètre.

13
Anjul Sharma
#!/bin/bash
#
# Change the file name from "test" to desired input file 
# (The comments in bash are prefixed with #'s)
for x in $(cat test.txt)
do
    echo $x
done
4
Sine

Voici mon exemple réel: comment boucler les lignes d’un autre programme, vérifier les sous-chaînes, supprimer les guillemets des variables, utiliser cette variable en dehors de la boucle. Je suppose que beaucoup posent ces questions tôt ou tard.

##Parse FPS from first video stream, drop quotes from fps variable
## streams.stream.0.codec_type="video"
## streams.stream.0.r_frame_rate="24000/1001"
## streams.stream.0.avg_frame_rate="24000/1001"
FPS=unknown
while read -r line; do
  if [[ $FPS == "unknown" ]] && [[ $line == *".codec_type=\"video\""* ]]; then
    echo ParseFPS $line
    FPS=parse
  fi
  if [[ $FPS == "parse" ]] && [[ $line == *".r_frame_rate="* ]]; then
    echo ParseFPS $line
    FPS=${line##*=}
    FPS="${FPS%\"}"
    FPS="${FPS#\"}"
  fi
done <<< "$(ffprobe -v quiet -print_format flat -show_format -show_streams -i "$input")"
if [ "$FPS" == "unknown" ] || [ "$FPS" == "parse" ]; then 
  echo ParseFPS Unknown frame rate
fi
echo Found $FPS

Déclarez une variable en dehors de la boucle, définissez une valeur et utilisez-la en dehors de la boucle requiert done <<< "$ (...)" syntaxe. L'application doit être exécutée dans le contexte de la console actuelle. Les guillemets autour de la commande conservent les retours à la ligne du flux de sortie.

La correspondance de boucle pour les sous-chaînes lit alors nom = valeur paire, divise la partie droite du dernier = caractère, supprime le premier guillemet, supprime le dernier guillemet, nous avons une valeur propre à être utilisé ailleurs.

3
Whome

Cela arrive assez tard, mais avec la pensée que cela peut aider quelqu'un, j'ajoute la réponse. En outre, cela peut ne pas être le meilleur moyen. La commande head peut être utilisée avec l'argument -n à lire n lignes depuis le début du fichier et De même, la commande tail peut être utilisée pour lire à partir du bas. Maintenant, pour extraire la nième ligne du fichier, nous sélectionnons n lignes , canaliser les données pour ne conserver qu'une ligne à partir des données acheminées.

   TOTAL_LINES=`wc -l $USER_FILE | cut -d " " -f1 `
   echo $TOTAL_LINES       # To validate total lines in the file

   for (( i=1 ; i <= $TOTAL_LINES; i++ ))
   do
      LINE=`head -n$i $USER_FILE | tail -n1`
      echo $LINE
   done
0
madD7

@ Peter: Cela pourrait marcher pour vous-

echo "Start!";for p in $(cat ./pep); do
echo $p
done

Cela rendrait la sortie-

Start!
RKEKNVQ
IPKKLLQK
QYFHQLEKMNVK
IPKKLLQK
GDLSTALEVAIDCYEK
QYFHQLEKMNVKIPENIYR
RKEKNVQ
VLAKHGKLQDAIN
ILGFMK
LEDVALQILL
0
Alan Jebakumar