Comment boucler sur les lignes d'un fichier?

61

Dites que j'ai ce fichier:

hello
world
hello world

Ce programme

#!/bin/bash

for i in $(cat $1); do
    echo "tester: $i"
done

les sorties

tester: hello
tester: world
tester: hello
tester: world

Je voudrais avoir l' foritération sur chaque ligne individuellement en ignorant les espaces, c'est-à-dire que les deux dernières lignes doivent être remplacées par

tester: hello world

L'utilisation de guillemets for i in "$(cat $1)";entraîne l' iattribution de l'ensemble du fichier à la fois. Que devrais-je changer?

Tobias Kienzler
la source

Réponses:

69

Avec foret IFS :

#!/bin/bash

IFS=$'\n'       # make newlines the only separator
set -f          # disable globbing
for i in $(cat < "$1"); do
  echo "tester: $i"
done

Notez cependant qu'il ignorera les lignes vides, car newline étant un caractère IFS-white-space, ses séquences comptent pour 1 et les premières et les dernières sont ignorées. Avec zshet ksh93(non bash), vous pouvez le changer IFS=$'\n\n'pour que la nouvelle ligne ne soit pas traitée de manière spéciale. Notez cependant que tous les caractères de fin de ligne suivants (ce qui inclut les lignes vides de fin) seront toujours supprimés par la substitution de commande.

Ou avecread (pas plus cat):

#!/bin/bash

while IFS= read -r line; do
  echo "tester: $line"
done < "$1"

Là, les lignes vides sont conservées, mais notez que la dernière ligne sera ignorée si elle n’est pas correctement délimitée par un caractère de nouvelle ligne.

remuer
la source
5
merci, je ne savais pas que l'on pouvait <faire une boucle complète. Bien que ce soit parfaitement logique maintenant, je l'ai vu
Tobias Kienzler le
1
Je vois IFS \ read -r line' in second example. Is really IFS = `nécessaire? IMHO il suffit de dire:while read -r line; do echo "tester: $line"; done < "$1"
Grzegorz Wierzowiecki
4
@GrzegorzWierzowiecki IFS=désactive la suppression des espaces de début et de fin. Voir In while IFS= read.., pourquoi IFS n'a aucun effet?
Gilles 'SO- arrête d'être méchant'
0

Pour ce que cela vaut, je dois le faire assez souvent, et je ne me souviens jamais de la manière exacte d'utiliser while IFS= read..., alors j'ai défini la fonction suivante dans mon profil bash:

# iterate the line of a file and call input function
iterlines() {
    (( $# < 2 )) && { echo "Usage: iterlines <File> <Callback>"; return; }
    local File=$1
    local Func=$2
    n=$(cat "$File" | wc -l)
    for (( i=1; i<=n; i++ )); do
        "$Func" "$(sed "${i}q;d" "$File")"
    done
}

Cette fonction détermine d’abord le nombre de lignes du fichier, puis utilise sedpour extraire ligne après ligne, puis transmet chaque ligne sous la forme d’un argument de chaîne unique à une fonction donnée. Je suppose que cela peut devenir vraiment inefficace avec des fichiers volumineux, mais cela ne m'a pas posé de problème jusqu'à présent (suggestions sur la manière d'améliorer cet accueil bien sûr).

L'utilisation est assez douce IMO:

>> cat example.txt # note the use of spaces, whitespace, etc.
a/path

This is a sentence.
"wi\th quotes"
$End
>> iterlines example.txt echo # preserves quotes, $ and whitespace
a/path

This is a sentence.
"wi\th quotes"
$End
>> x() { echo "$#"; }; iterlines example.txt x # line always passed as single input string
1
1 
1
1
1
Sheljohn
la source