Jak wykonać pętlę w wierszach pliku?

Powiedz, że mam ten plik:

hello world hello world 

Ten program

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

wyjścia

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

Chciałbym mieć for iteruj po każdym wierszu z osobna, ignorując jednak białe znaki, tj. ostatnie dwie linie należy zastąpić przez

tester: hello world 

Używając cudzysłowów for i in "$(cat $1)"; powoduje, że i zostaje przypisany do całego pliku naraz. Co mam zmienić?

Odpowiedź

(9 lat później 🙂
Obie podane odpowiedzi zakończyłyby się niepowodzeniem na plikach bez znaku nowej linii na końcu, co skutecznie pominie ostatnią linię, nie spowoduje błędów, doprowadzi do katastrofy sposób :).

Najlepsze zwięzłe rozwiązanie, jakie do tej pory znalazłem, że „Just Works” (zarówno w bashu, jak i sh):

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

Aby uzyskać bardziej szczegółową dyskusję, zobacz tę dyskusję dotyczącą StackOverflow: How to use „while czytaj „(Bash), aby przeczytać ostatnią linię w pliku, jeśli nie ma nowej linii na końcu pliku?

Uwaga: to podejście dodaje dodatkowy znak nowej linii do ostatniej linii, jeśli jest brak.

Komentarze

  • Niezły chwyt, dziękuję! Zwróć uwagę na " Uważaj, że dodaje to dodatkowy znak nowej linii w EOF, jeśli już go nie ma. " komentarz chociaż
  • Tobiaszu, ja ' dodam to jako notatkę, dziękuję.

Odpowiedź

Z 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 

Należy jednak pamiętać, że pominie puste wiersze, ponieważ nowa linia to znak IFS-biała spacja, sekwencje z tego liczy się jako 1, a wiodące i końcowe są ignorowane. Za pomocą zsh i ksh93 (nie bash) możesz zmienić to na IFS=$"\n\n" aby znak nowej linii nie był traktowany specjalnie, należy jednak pamiętać, że wszystkie końcowe znaki nowej linii (w tym końcowe puste wiersze) będą zawsze usuwane przez podstawienie polecenia.

Lub z read (koniec z cat ):

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

Tam są zachowywane puste wiersze, ale pamiętaj, że pominie on ostatni wiersz, jeśli nie został odpowiednio oddzielony znakiem nowego wiersza.

Komentarze

  • dziękuję, nie ' nie wiedziałem, że można < w całą pętlę. Chociaż teraz to ma sens, widziałem to
  • Widzę, że IFS \ read -r line' in second example. Is really IFS = `potrzebne? IMHO wystarczy powiedzieć: while read -r line; do echo "tester: $line"; done < "$1"
  • @GrzegorzWierzowiecki IFS= wyłącza usuwanie początkowych i końcowych białych spacji. Zobacz W while IFS= read.., dlaczego IFS nie działa?
  • @BenMares Aby zapobiec globbingowi wyrażenia, które prawdopodobnie pojawiają się w czytanym tekście, od rozszerzenia do pasujących nazw plików. Spróbuj na przykład printf '%s\n' '*o' 'bar' >afile; touch foo; IFS=$'\n'; for i in $(cat afile); do echo "$i"; done.
  • A while IFS= read -r line || [ "$line" ]; do przetworzy końcową linię niepoprawnie oddzieloną znakiem nowej linii (ale zostanie dodany z powrotem).

Odpowiedź

Ale warto to zrobić że dość często i nigdy nie pamiętam dokładnego sposobu używania while IFS= read..., więc zdefiniowałem następującą funkcję w moim profilu basha:

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

Ta funkcja najpierw określa liczbę wierszy w pliku, a następnie używa sed do wyodrębniania wiersz po wierszu i przekazuje każdy wiersz jako pojedynczy argument ciągu do dowolnego podanego funkcjonować. Przypuszczam, że może to stać się naprawdę nieefektywne w przypadku dużych plików, ale jak dotąd nie stanowiło to dla mnie problemu (oczywiście sugestie, jak ulepszyć to powitanie).

Użycie jest całkiem przyjemne 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 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *