← retour aux snippets

bash: lire un fichier ligne par ligne sans pièges

Lire chaque ligne exactement telle qu'écrite (espaces, backslashes) sans subshell ni pertes.


objectif

Lire un fichier ligne par ligne en Bash sans casser les espaces, sans interpréter les backslashes, et sans perdre les variables du shell à cause d’un pipe qui crée un subshell.

code minimal

# boucle sûre: pas de subshell, pas de split/globbing accidentel
while IFS= read -r line; do
  printf '-> %s\n' "$line"
done < "./liste.txt"

utilisation

# accumuler dans un tableau (readarray/mapfile) sans sauts de ligne finaux
mapfile -t LIGNES < "./liste.txt"
printf 'count=%d first=%s\n' "${#LIGNES[@]}" "${LIGNES[0]}"

# parcourir la sortie d'une commande SANS subshell (process substitution)
count=0
while IFS= read -r user; do
  ((count++))
done < <(cut -d: -f1 /etc/passwd)
echo "users: $count"

# lire des lignes qui peuvent commencer par des espaces ou contenir des backslashes
while IFS= read -r line; do
  case "$line" in
    ''|'#'*) continue ;;  # ignorer vides et commentaires
  esac
  printf '%s\n' "$line"
done < "./config.list"

# traiter un fichier potentiellement sans newline final (boucle robuste)
# (le "|| [ -n "$line" ]" garantit la dernière ligne)
while IFS= read -r line || [ -n "$line" ]; do
  echo "$line"
done < "./data.txt"

variante(s) utile(s)

# traiter des chemins avec NUL (find -print0) en toute sécurité
while IFS= read -r -d '' path; do
  printf 'file: %s\n' "$path"
done < <(find . -type f -name "*.log" -print0)

# lire deux colonnes séparées par tabulation, même si les colonnes contiennent des espaces
while IFS=$'\t' read -r col1 col2; do
  printf '1=%s 2=%s\n' "$col1" "$col2"
done < "./data.tsv"

# limiter le nombre de caractères lus (ex: clés fixes)
while IFS= read -r -n 16 key; do
  printf 'key=%s\n' "$key"
done < "./keys.bin"

# normaliser CRLF -> LF "à la volée" (fichiers Windows)
while IFS= read -r line; do
  line="${line%$'\r'}"
  printf '%s\n' "$line"
done < "./win.txt"

# mapfile avec délimiteur NUL (flux -print0)
mapfile -d $'\0' -t FILES < <(find . -type f -name "*.json" -print0)
printf 'json files: %d\n' "${#FILES[@]}"

notes

  • utilisez toujours IFS= et read -r pour empêcher le découpage sur espaces et l’interprétation des backslashes.
  • évitez cat fichier | while read ...; do ...; done qui exécute la boucle dans un subshell (variables perdues). Préférez la redirection < fichier ou la substitution de processus < <(commande).
  • pour des flux NUL-terminés (sorties -print0), ajoutez -d $'\0' à read ou mapfile.
  • si vos données ne sont pas des “vraies lignes” (multilignes, CSV complexes), utilisez un parseur dédié (awk/csvkit/Python).