Lire un fichier orienté ligne qui ne peut pas se terminer par une nouvelle ligne

11

J'ai un fichier nommé /tmp/urlFileoù chaque ligne représente une URL. J'essaie de lire le fichier comme suit:

cat "/tmp/urlFile" | while read url
do
    echo $url
done

Si la dernière ligne ne se termine pas par un caractère de nouvelle ligne, cette ligne ne sera pas lue. Je me demandais pourquoi?

Est-il possible de lire toutes les lignes, qu'elles se terminent par une nouvelle ligne ou non?

Tim
la source
2
Hah @ Stéphane j'aime bien le TBD là-bas ;-).
Stephen Kitt
2
Une autre façon d'ajouter la nouvelle ligne de fin si elle est manquante; awk 1 /tmp/urlFile.. soawk 1 /tmp/urlFile | while ...
muru
@muru, c'est une meilleure réponse que toute autre ici.
Wildcard
1
Puisque vous demandez pourquoi il n'est pas lu: stackoverflow.com/a/729795/1968
Konrad Rudolph

Réponses:

13

Vous feriez:

while IFS= read -r url || [ -n "$url" ]; do
  printf '%s\n' "$url"
done < url.list

(en fait, cette boucle ajoute la nouvelle ligne manquante sur la dernière (non) ligne).

Voir également:

Stéphane Chazelas
la source
Merci. J'ai lu les articles liés, et peut-être que je manque quelque chose, pourquoi "cette boucle ajoute le retour à la ligne manquant sur la dernière (non) ligne"?
Tim
1
@Tim Ce que Stephane semble vouloir dire, c'est qu'il ajoute le retour à la ligne manquant dans la sortie puisque tous les printfappels ici l'ont \n.
Sergiy Kolodyazhnyy
6

Cela semble être résolu en partie avec readarray -t:

readarray -t urls "/tmp/urlFile"
for url in "${urls[@]}"; do
    printf '%s\n' "$url"
done

Notez cependant que bien que cela fonctionne pour les fichiers de taille raisonnable, cette solution introduit un nouveau problème potentiel avec les fichiers très volumineux - elle lit d'abord le fichier dans un tableau qui doit ensuite être itéré. Pour les fichiers très volumineux, cela pourrait prendre du temps et de la mémoire, potentiellement jusqu'à l'échec.

DopeGhoti
la source
Merci. Quelle partie résout-elle et laquelle ne résout pas?
Tim
Il résout le problème avec l'absence d'une nouvelle ligne de fin, mais introduit un nouveau problème potentiel avec des fichiers très volumineux, car il lit d'abord le fichier dans un tableau qui doit ensuite être itéré.
DopeGhoti
1
@DopeGhoti Ce sont de bonnes informations - puis-je vous suggérer de les ajouter directement dans la réponse?
RJHunter
Cette réponse a été ainsi modifiée.
DopeGhoti
5

Par définition , un fichier texte se compose d'une séquence de lignes. Une ligne se termine par un caractère de nouvelle ligne. Ainsi, un fichier texte se termine par un caractère de nouvelle ligne, sauf s'il est vide.

La fonction readintégrée est uniquement destinée à lire des fichiers texte. Vous ne passez pas un fichier texte, vous ne pouvez donc pas espérer qu'il fonctionne de manière transparente. Le shell lit toutes les lignes - ce qu'il saute sont les caractères supplémentaires après la dernière ligne.

Si vous avez un fichier d'entrée potentiellement mal formé qui peut manquer sa dernière ligne, vous pouvez lui ajouter une nouvelle ligne, juste pour être sûr.

{ cat "/tmp/urlFile"; echo; } | 

Les fichiers qui devraient être des fichiers texte mais qui manquent la nouvelle ligne finale sont souvent produits par les éditeurs de Windows. Cela va généralement en combinaison avec les fins de ligne Windows, qui sont CR LF, par opposition au LF d'Unix. Les caractères CR sont rarement utiles n'importe où et ne peuvent en aucun cas apparaître dans les URL, vous devez donc les supprimer.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | 

Dans le cas où le fichier d'entrée est bien formé et se termine par une nouvelle ligne, l' echoajoute une ligne vierge supplémentaire. Étant donné que les URL ne peuvent pas être vides, ignorez simplement les lignes vides.

Notez également que readne lit pas les lignes d'une manière simple. Il ignore les espaces de début et de fin, ce qui est probablement souhaitable pour une URL. Il traite la barre oblique inversée à la fin d'une ligne comme un caractère d'échappement, provoquant la jonction de la ligne suivante avec la première moins la séquence barre oblique inverse-nouvelle ligne, ce qui n'est certainement pas souhaitable. Vous devez donc transmettre l' -roption à read. Il est très, très rare que ce readsoit la bonne chose read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done
Gilles 'SO- arrête d'être méchant'
la source
3

Eh bien, readretourne une valeur falsifiée si elle rencontre la fin du fichier avant une nouvelle ligne, mais même si c'est le cas, elle affecte toujours la valeur lue. Ainsi, nous pouvons vérifier si l'appel final de readrenvoie autre chose qu'une ligne vide et le traiter normalement. Donc, ne quittez la boucle qu'après readretourne false et la ligne est vide:

#!/bin/sh
while IFS= read -r line || [ "$line" ]; do 
    echo "line: $line"
done

$ printf 'foo\nbar' | sh ./read.sh 
line: foo
line: bar
$ printf 'foo\nbar\n' | sh ./read.sh 
line: foo
line: bar
ilkkachu
la source
1

Une autre façon serait la suivante:

Lorsque la lecture atteint la fin du fichier au lieu de la fin de la ligne, elle lit les données et les affecte aux variables, mais elle se termine avec un état différent de zéro. Si votre boucle est construite "pendant la lecture; faites des choses; terminé

Ainsi, au lieu de tester directement l'état de sortie de lecture, testez un indicateur et demandez à la commande de lecture de définir cet indicateur à partir du corps de la boucle. De cette façon, quel que soit l'état de sortie des lectures, tout le corps de la boucle s'exécute, car la lecture n'est qu'une des commandes de la boucle comme les autres, pas un facteur déterminant si la boucle sera exécutée.

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY 
done < /tmp/urlFile

Référé d' ici .

Hunter.S.Thompson
la source
1
cat "/ tmp / urlFile" | en lisant l'url
faire
    echo $ url
terminé

Ceci est une utilisation inutile decat .

Ironiquement, vous pouvez remplacer le catprocessus ici par quelque chose de réellement utile: un outil dont disposent les systèmes POSIX pour ajouter la nouvelle ligne manquante et transformer le fichier en un fichier texte POSIX approprié.

sed -e '$ a \' "/ tmp / urlFile" | en lisant -r url
faire
    printf "% s \ n" "$ {url}"
terminé

Lectures complémentaires

JdeBP
la source
1
Le comportement de sed n'est pas spécifié par POSIX lorsque l'entrée ne se termine pas par un caractère de nouvelle ligne; également lorsqu'il existe des lignes plus grandes que LINE_MAX, alors que le comportement de readest spécifié dans ces cas.
Stéphane Chazelas