
Het is vrij eenvoudig om de inhoud van een Linux-tekstbestand regel voor regel in een shellscript te lezen – zolang je maar te maken hebt met een paar subtiele valstrikken. Hier leest u hoe u het op een veilige manier kunt doen.
Bestanden, tekst en idioom
Elke programmeertaal heeft een reeks idiomen. Dit zijn de standaard, eenvoudige manieren om een reeks veelvoorkomende taken uit te voeren. Ze zijn de elementaire of standaardmanier om een van de functies van de taal te gebruiken waarmee de programmeur werkt. Ze worden onderdeel van de toolkit met mentale blauwdrukken van een programmeur.
Acties zoals het lezen van gegevens uit bestanden, het werken met loops en het omwisselen van de waarden van twee variabelen zijn goede voorbeelden. De programmeur kent ten minste één manier om hun doelen op een generieke of vanille manier te bereiken. Misschien is dat voldoende voor de huidige eis. Of misschien verfraaien ze de code om deze efficiënter of toepasbaarder te maken voor de specifieke oplossing die ze ontwikkelen. Maar het hebben van het bouwsteen-idioom binnen handbereik is een goed startpunt.
Als u idiomen in één taal kent en begrijpt, wordt het ook gemakkelijker om een nieuwe programmeertaal te leren. Weten hoe dingen in de ene taal zijn opgebouwd en op zoek gaan naar het equivalent – of het beste – in een andere taal, is een goede manier om de overeenkomsten en verschillen tussen programmeertalen die je al kent en de taal die je leert te waarderen.
Regels uit een bestand lezen: The One-Liner
In Bash kun je een while loop op de opdrachtregel om elke regel tekst uit een bestand te lezen en er iets mee te doen. Ons tekstbestand heet ‘data.txt’. Het bevat een lijst met de maanden van het jaar.
January February March . . October November December
Onze simpele oneliner is:
while read line; do echo $line; done < data.txt

De while loop leest een regel uit het bestand en de uitvoeringsstroom van het kleine programma gaat naar de body van de lus. De echo commando schrijft de regel tekst in het terminalvenster. De leespoging mislukt als er geen regels meer zijn om te lezen en de lus is voltooid.
Een handige truc is de mogelijkheid om een bestand in een lus om te leiden. In andere programmeertalen moet u het bestand openen, eruit lezen en weer sluiten als u klaar bent. Met Bash kun je eenvoudig bestandsomleiding gebruiken en de shell al die dingen op laag niveau voor je laten afhandelen.
Deze oneliner is natuurlijk niet erg handig. Linux biedt al het cat commando, dat precies dat voor ons doet. We hebben een langdradige manier bedacht om een drieletterig commando te vervangen. Maar het toont wel zichtbaar de principes van het lezen uit een bestand.
Dat werkt tot op zekere hoogte goed genoeg. Stel dat we een ander tekstbestand hebben met de namen van de maanden. In dit bestand is de escape-reeks voor een teken voor een nieuwe regel aan elke regel toegevoegd. We noemen het ‘data2.txt’.
Januaryn Februaryn Marchn . . Octobern Novembern Decembern
Laten we onze oneliner gebruiken voor ons nieuwe bestand.
while read line; do echo $line; done < data2.txt

Het backslash-escape-personage ‘ ”Is verwijderd. Het resultaat is dat aan elke regel een “n” is toegevoegd. Bash interpreteert de backslash als het begin van een ontsnappingsreeks. Vaak willen we niet dat Bash interpreteert wat het leest. Het kan handiger zijn om een regel in zijn geheel te lezen – backslash-escape-reeksen en alles – en te kiezen wat u wilt ontleden of vervangen, binnen uw eigen code.
Als we een zinvolle verwerking of ontleding van de tekstregels willen uitvoeren, hebben we een script nodig.
Regels lezen uit een bestand met een script
Hier is ons script. Het heet “script1.sh.”
#!/bin/bash
Counter=0
while IFS='' read -r LinefromFile || [[ -n "${LinefromFile}" ]]; do
((Counter++))
echo "Accessing line $Counter: ${LinefromFile}"
done < "$1"
We stellen een variabele in met de naam Counter nul, dan definiëren we onze while lus.
Het eerste statement op de while-regel is IFS='' . IFS staat voor internal field separator. Het bevat waarden die Bash gebruikt om woordgrenzen te identificeren. Standaard verwijdert de leesopdracht de voorloop- en volgspaties. Als we de regels uit het bestand precies willen lezen zoals ze zijn, moeten we instellen IFS om een lege string te zijn.
We zouden dit een keer buiten de lus kunnen instellen, net zoals we de waarde van Counter . Maar met meer complexe scripts – vooral die met veel door de gebruiker gedefinieerde functies erin – is het mogelijk dat IFS kunnen elders in het script op verschillende waarden worden ingesteld. Verzekeren dat IFS wordt elke keer dat de while loop itereert garanties dat we weten wat zijn gedrag zal zijn.
We gaan een regel tekst voorlezen in een variabele met de naam LinefromFile . We gebruiken de -r (lees backslash als een normaal teken) optie om backslashes te negeren. Ze worden net als elk ander personage behandeld en krijgen geen speciale behandeling.
Er zijn twee voorwaarden die voldoen aan de while loop en laat de tekst worden verwerkt door de body van de loop:
-
read -r LinefromFile: Wanneer een regel tekst met succes uit het bestand is gelezen, wordt dereadcommando stuurt een successignaal naar hetwhile, en dewhilelus geeft de uitvoeringsstroom door aan het lichaam van de lus. Merk op dat dereadcommando moet een newline-teken aan het einde van de regel tekst zien om het als succesvol gelezen te beschouwen. Als het bestand geen POSIX-compatibel tekstbestand is, bevat de laatste regel mogelijk geen teken voor een nieuwe regel. Als hetreadcommando ziet het einde van de bestandsmarkering (EOF) voordat de regel wordt beëindigd door een nieuwe regel niet behandel het als een succesvolle lezing. Als dat gebeurt, wordt de laatste regel tekst niet doorgegeven aan de hoofdtekst van de lus en niet verwerkt. -
[ -n "${LinefromFile}" ]: We moeten wat extra werk doen om niet-POSIX-compatibele bestanden te verwerken. Deze vergelijking controleert de tekst die uit het bestand wordt gelezen. Als het niet wordt beëindigd met een newline-teken, zal deze vergelijking nog steeds succes opleveren voor dewhilelus. Dit zorgt ervoor dat eventuele fragmenten van de achterlijn worden verwerkt door de body van de lus.
Deze twee clausules worden gescheiden door de logische operator OR ‘ || ”Zodat als een van beide clausule geeft succes terug, de opgehaalde tekst wordt verwerkt door de hoofdtekst van de lus, of er nu een nieuw-regel-teken is of niet.
In de body van onze lus verhogen we de Counter variabel door één en gebruiken echo om wat output naar het terminalvenster te sturen. Het regelnummer en de tekst van elke regel worden weergegeven.
We kunnen nog steeds onze omleidingstruc gebruiken om een bestand om te leiden naar een lus. In dit geval sturen we $ 1 om, een variabele die de naam bevat van de eerste opdrachtregelparameter die aan het script is doorgegeven. Met deze truc kunnen we gemakkelijk de naam van het gegevensbestand doorgeven waaraan we het script willen laten werken.
Kopieer en plak het script in een editor en sla het op met de bestandsnaam ‘script1.sh’. Gebruik de chmod commando om het uitvoerbaar te maken.
chmod +x script1.sh

Laten we eens kijken wat ons script maakt van het data2.txt-tekstbestand en de backslashes erin.
./script1.sh data2.txt

Elk teken op de regel wordt woordelijk weergegeven. De backslashes worden niet geïnterpreteerd als escape-tekens. Ze worden afgedrukt als gewone tekens.
De lijn doorgeven aan een functie
We laten nog steeds de tekst op het scherm zien. In een realistisch programmeerscenario staan we waarschijnlijk op het punt iets interessants te doen met de tekstregel. In de meeste gevallen is het een goede programmeerpraktijk om de verdere verwerking van de lijn in een andere functie af te handelen.
Hier is hoe we het zouden kunnen doen. Dit is “script2.sh.”
#!/bin/bash
Counter=0
function process_line() {
echo "Processing line $Counter: $1"
}
while IFS='' read -r LinefromFile || [[ -n "${LinefromFile}" ]]; do
((Counter++))
process_line "$LinefromFile"
done < "$1"
We definiëren onze Counter variabele zoals eerder, en dan definiëren we een functie genaamd process_line() . De definitie van een functie moet verschijnen voordat de functie wordt voor het eerst aangeroepen in het script.
Onze functie wordt doorgegeven aan de nieuw gelezen regel tekst in elke iteratie van het while lus. We hebben toegang tot die waarde binnen de functie door de $1 variabele. Als er twee variabelen aan de functie zijn doorgegeven, kunnen we die waarden openen met $1 en $2 , enzovoort voor meer variabelen.
De while loop is grotendeels hetzelfde. Er is slechts één verandering in het lichaam van de lus. De echo lijn is vervangen door een oproep naar de process_line() functie. Merk op dat u de “()” haakjes niet in de naam van de functie hoeft te gebruiken wanneer u deze aanroept.
De naam van de variabele die de regel tekst bevat, LinefromFile , wordt tussen aanhalingstekens geplaatst wanneer het wordt doorgegeven aan de functie. Dit is geschikt voor regels met spaties erin. Zonder de aanhalingstekens wordt het eerste woord behandeld als $1 door de functie wordt het tweede woord geacht te zijn $2 , enzovoort. Het gebruik van aanhalingstekens zorgt ervoor dat de hele regel tekst wordt behandeld als $1. Merk op dat dit zo is niet hetzelfde $1 dat hetzelfde gegevensbestand bevat dat aan het script is doorgegeven.
Omdat Counter is gedeclareerd in de hoofdtekst van het script en niet in een functie, kan ernaar worden verwezen in de process_line() functie.
Kopieer of typ het bovenstaande script in een editor en sla het op met de bestandsnaam ‘script2.sh’. Maak het uitvoerbaar met chmod :
chmod +x script2.sh

Nu kunnen we het uitvoeren en een nieuw gegevensbestand doorgeven, “data3.txt”. Hierin staat een lijst met de maanden en een regel met veel woorden erop.
January February March . . October November nMore text "at the end of the line" December
Onze opdracht is:
./script2.sh data3.txt

De regels worden uit het bestand gelezen en een voor een doorgegeven aan het process_line() functie. Alle regels worden correct weergegeven, inclusief de oneven regel met de backspace, aanhalingstekens en meerdere woorden erin.
Bouwstenen zijn nuttig
Er is een gedachtegang die zegt dat een idioom iets unieks voor die taal moet bevatten. Dat is geen overtuiging waarop ik me abonneer. Wat belangrijk is, is dat het de taal goed gebruikt, gemakkelijk te onthouden is en een betrouwbare en robuuste manier biedt om bepaalde functionaliteit in uw code te implementeren.