Cum să buclați peste liniile unui fișier?

Spune că am acest fișier:

hello world hello world 

Acest program

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

ieșiri

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

Aș dori să am for iterați peste fiecare linie, ignorând în mod individual spațiile albe, de exemplu, ultimele două linii ar trebui înlocuite cu

tester: hello world 

Folosind ghilimele

are ca rezultat i a fi atribuit întregul fișier simultan. Ce ar trebui să schimb?

Răspuns

(9 ani mai târziu 🙂
Ambele răspunsuri furnizate ar eșua pe fișiere fără o linie nouă la sfârșit, acest lucru va sări peste ultima linie, nu va produce erori, ar duce la dezastru (învățat greu mod :).

Cea mai bună soluție concisă pe care am găsit-o până acum că „Just Works” (atât în bash, cât și în sh):

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

Pentru o discuție mai aprofundată, consultați această discuție StackOverflow: Cum se folosește „în timp ce citiți „(Bash) pentru a citi ultima linie dintr-un fișier dacă nu există nicio linie nouă la sfârșitul fișierului?

Atenție: această abordare adaugă o nouă linie suplimentară la ultima linie dacă există niciunul deja.

Comentarii

  • O captură frumoasă, mulțumesc! Rețineți " Feriți-vă că acest lucru adaugă o nouă linie suplimentară la EOF dacă nu există deja. " comentariu deși
  • Tobias, ' voi adăuga acest lucru ca notă, mulțumesc.

Răspuns

Cu for și IFS :

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

Rețineți totuși că va sări peste liniile goale ca newline fiind un caracter IFS-white-space, secvențe din acesta contează ca 1, iar cele din urmă și cele din urmă sunt ignorate. Cu zsh și ksh93 (nu bash), îl puteți schimba în IFS=$"\n\n" pentru ca noua linie să nu fie tratată special, totuși rețineți că toate caracterele de urmărire de linie nouă (astfel încât să includă și linii goale) vor fi întotdeauna eliminate prin substituirea comenzii.

Sau cu read (nu mai există cat ):

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

Acolo se păstrează liniile goale, dar rețineți că s-ar sări ultima linie dacă nu ar fi delimitată corect de un caracter de linie nouă.

Comentarii

  • mulțumesc, nu ' nu știam că se poate < într-o buclă întreagă. Deși are perfect sens acum l-am văzut
  • Văd IFS \ read -r line' in second example. Is really IFS = `este necesar? Îmi este suficient să spunem: while read -r line; do echo "tester: $line"; done < "$1"
  • @GrzegorzWierzowiecki IFS= dezactivează dezizolarea spațiului alb principal. A se vedea În while IFS= read.., de ce IFS nu are efect?
  • @BenMares Pentru a preveni aglomerarea expresii care apar posibil în textul pe care îl citim de la extindere la nume de fișiere potrivite. Încercați, de exemplu, printf '%s\n' '*o' 'bar' >afile; touch foo; IFS=$'\n'; for i in $(cat afile); do echo "$i"; done.
  • Un while IFS= read -r line || [ "$line" ]; do va procesa o linie finală delimitată în mod corespunzător de un caracter de linie nouă (dar va fi adăugat înapoi).

Răspuns

Pentru ceea ce merită, trebuie să fac asta destul de des și nu-mi amintesc niciodată modul exact de utilizare a while IFS= read..., așa că am definit următoarea funcție în profilul meu 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 } 

Această funcție determină mai întâi numărul de linii din fișier, apoi folosește sed pentru a extrage linie după linie și trece fiecare linie ca un singur șir argument oricărui dat funcţie. Presupun că acest lucru ar putea deveni cu adevărat ineficient cu fișierele mari, dar asta nu a fost o problemă pentru mine până acum (sugestii despre cum să îmbunătățesc această întâmpinare bineînțeles).

Utilizarea este destul de dulce 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 

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *