Hoe loop je over de regels van een bestand?

Stel dat ik dit bestand heb:

hello world hello world 

Dit programma

#!/bin/bash for i in $(cat $1); do echo "tester: $i" done 

outputs

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

Ik “zou graag de for herhaal over elke regel afzonderlijk en negeer echter witruimten, dwz de laatste twee regels moeten worden vervangen door

tester: hello world 

Aanhalingstekens gebruiken for i in "$(cat $1)"; resulteert erin dat i het hele bestand in één keer wordt toegewezen. Wat moet ik wijzigen?

Antwoord

(9 jaar later 🙂
Beide verstrekte antwoorden zouden mislukken op bestanden zonder een nieuwe regel aan het einde, dit zal effectief de laatste regel overslaan, geen fouten produceren, zou tot een ramp leiden (hard geleerd way :).

De beste beknopte oplossing die ik tot nu toe heb gevonden is dat “Just Works” (in zowel bash als sh):

while IFS="" read -r LINE || [ -n "${LINE}" ]; do echo "processing line: ${LINE}" done < /path/to/input/file.txt 

Zie deze StackOverflow-discussie voor een meer diepgaande discussie: Hoe “while read “(Bash) om de laatste regel in een bestand te lezen als er geen nieuwe regel aan het einde van het bestand staat?

Let op: deze benadering voegt een extra nieuwe regel toe aan de laatste regel als die er is geen al.

Reacties

  • Leuke vangst, bedankt! Let op de " Pas op dat dit een extra nieuwe regel toevoegt aan EOF als die er al is. " opmerking echter
  • Tobias, ik ' zal dit als een opmerking toevoegen, bedankt.

Antwoord

Met for en IFS :

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

Merk echter op dat het lege regels zal overslaan omdat nieuwe regel een IFS-witruimte-teken is, reeksen daarvan tellen als 1 en de voorste en achterste worden genegeerd. Met zsh en ksh93 (niet bash) kun je deze wijzigen in IFS=$"\n\n" om nieuwe regel niet speciaal te behandelen, merk er echter op dat alle achterliggende tekens voor nieuwe regels (dus inclusief lege regels aan het einde) altijd zullen worden verwijderd door de opdrachtsubstitutie.

Of met read (niet meer cat ):

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

Daar blijven lege regels behouden, maar merk op dat het de laatste regel zou overslaan als deze niet correct werd gescheiden door een nieuw-regel-teken.

Reacties

  • bedankt, ik wist niet ' niet dat iemand < in een hele lus. Hoewel het nu volkomen logisch is, zag ik het
  • Ik zie IFS \ read -r line' in second example. Is really IFS = `nodig? IMHO het genoeg om te zeggen: while read -r line; do echo "tester: $line"; done < "$1"
  • @GrzegorzWierzowiecki IFS= schakelt het verwijderen van voorloop- en volgspaties uit. Zie In while IFS= read.., waarom heeft IFS geen effect?
  • @BenMares Om globbing te voorkomen uitdrukkingen die mogelijk voorkomen in de tekst die we lezen, worden uitgebreid naar overeenkomende bestandsnamen. Probeer bijvoorbeeld printf '%s\n' '*o' 'bar' >afile; touch foo; IFS=$'\n'; for i in $(cat afile); do echo "$i"; done.
  • Een while IFS= read -r line || [ "$line" ]; do verwerkt een regel aan het einde die niet correct wordt gescheiden door een teken voor een nieuwe regel (maar het wordt weer toegevoegd).

Antwoord

Voor wat het waard is, moet ik doen dat vrij vaak, en ik kan me nooit de exacte manier herinneren om while IFS= read... te gebruiken, dus definieerde ik de volgende functie in mijn bash-profiel:

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

Deze functie bepaalt eerst het aantal regels in het bestand, gebruikt vervolgens sed om regel na regel te extraheren, en geeft elke regel als een enkel stringargument door aan een gegeven functie. Ik veronderstel dat dit erg inefficiënt kan worden met grote bestanden, maar dat is tot dusver geen probleem voor mij geweest (suggesties om dit welkom te verbeteren natuurlijk).

Het gebruik is best aardig 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 

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *