Przeczytaj plik zorientowany liniowo, który może nie kończyć się nową linią

11

Mam plik o nazwie, w /tmp/urlFilektórej każda linia reprezentuje adres URL. Próbuję odczytać z pliku w następujący sposób:

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

Jeśli ostatni wiersz nie kończy się znakiem nowego wiersza, wiersz ten nie zostanie odczytany. Zastanawiałem się dlaczego?

Czy można odczytać wszystkie wiersze, niezależnie od tego, czy kończą się nowym wierszem, czy nie?

Tim
źródło
2
Hah @ Stéphane Podoba mi się tam TBD ;-).
Stephen Kitt
2
Inny sposób dodania końcowego znaku nowej linii, jeśli go brakuje; awk 1 /tmp/urlFile.. więcawk 1 /tmp/urlFile | while ...
muru
@muru, to lepsza odpowiedź niż jakakolwiek inna tutaj.
Wildcard
1
Ponieważ pytasz, dlaczego nie jest czytane: stackoverflow.com/a/729795/1968
Konrad Rudolph

Odpowiedzi:

13

Zrobiłbyś:

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

(w rzeczywistości ta pętla dodaje brakującą nową linię do ostatniej (innej niż) linii).

Zobacz też:

Stéphane Chazelas
źródło
Dzięki. Czytam powiązane artykuły, a może coś mi umknie, dlaczego „ta pętla dodaje brakującą nową linię do ostatniej (nie) linii”?
Tim
1
@ Tim Tym, co wydaje się oznaczać Stephane, jest to, że dodaje on brakującą nową linię na wyjściu, ponieważ wszystkie printfwywołania tutaj mają \n.
Sergiy Kolodyazhnyy
6

Wydaje się to częściowo rozwiązane przez readarray -t:

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

Należy jednak pamiętać, że chociaż działa to w przypadku plików o rozsądnych rozmiarach, to rozwiązanie wprowadza potencjalny nowy problem z bardzo dużymi plikami - najpierw odczytuje plik do tablicy, która następnie musi być iterowana. W przypadku bardzo dużych plików może to być czasochłonne i zajmować pamięć, potencjalnie aż do awarii.

DopeGhoti
źródło
Dzięki. Którą część rozwiązuje, a która nie?
Tim
Rozwiązuje problem z brakiem nowej linii, ale wprowadza potencjalny nowy problem z bardzo dużymi plikami, ponieważ najpierw odczytuje plik do tablicy, którą następnie należy iterować.
DopeGhoti
1
@DopeGhoti To dobra informacja - czy mogę zasugerować dodanie jej bezpośrednio do odpowiedzi?
RJHunter
Ta odpowiedź została zmieniona.
DopeGhoti
5

Z definicji plik tekstowy składa się z sekwencji wierszy. Linia kończy się znakiem nowej linii. Zatem plik tekstowy kończy się znakiem nowej linii, chyba że jest pusty.

readWbudowane jest przeznaczona tylko do odczytu plików tekstowych. Nie przekazujesz pliku tekstowego, więc nie możesz mieć nadziei, że będzie działał bezproblemowo. Powłoka czyta wszystkie linie - pomija dodatkowe znaki po ostatniej linii.

Jeśli masz potencjalnie zniekształcony plik wejściowy, w którym może brakować ostatniego wiersza, możesz dodać do niego nowy wiersz, dla pewności.

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

Pliki, które powinny być plikami tekstowymi, ale nie mają ostatniej linii nowej linii, są często tworzone przez edytory Windows. Zwykle dzieje się to w połączeniu z zakończeniami linii Windows, które są CR LF, w przeciwieństwie do LF Unixa. Znaki CR są rzadko przydatne w dowolnym miejscu i w żadnym wypadku nie mogą pojawiać się w adresach URL, dlatego należy je usunąć.

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

W przypadku, gdy plik wejściowy jest poprawnie uformowany i kończy się nową linią, echododaje dodatkową pustą linię. Ponieważ adresy URL nie mogą być puste, po prostu zignoruj ​​puste wiersze.

Zauważ też, że readnie odczytuje wierszy w prosty sposób. Ignoruje początkowe i końcowe białe znaki, co w przypadku adresu URL jest prawdopodobnie pożądane. Traktuje odwrotny ukośnik na końcu linii jako znak zmiany znaczenia, powodując połączenie następnej linii z pierwszym minus sekwencją odwrotnego ukośnika-nowa linia, co zdecydowanie nie jest pożądane. Powinieneś więc przekazać tę -ropcję read. To jest bardzo, bardzo rzadkie, readaby być właściwą rzeczą, a nie read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done
Gilles „SO- przestań być zły”
źródło
3

Cóż, readzwraca wartość falsy jeżeli spełnia EOF przed linią, ale nawet jeśli tak, to nadal przypisuje wartość ją przeczytać. Możemy więc sprawdzić, czy końcowe wywołanie readzwraca coś innego niż pustą linię, i przetworzyć to jak zwykle. Opuść pętlę dopiero po readzwróceniu wartości false, a linia będzie pusta:

#!/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
źródło
1

Innym sposobem byłoby tak:

Kiedy odczyt osiąga koniec pliku zamiast końca linii, odczytuje dane i przypisuje je do zmiennych, ale kończy działanie ze statusem niezerowym. Jeśli twoja pętla jest zbudowana "podczas czytania; rób rzeczy; gotowe

Dlatego zamiast bezpośrednio testować status wyjścia odczytu, przetestuj flagę i ustaw polecenie odczytu w treści pętli. W ten sposób, niezależnie od odczytu wyjścia status, działa całe ciało pętli, ponieważ read był tylko jedną z listy poleceń w pętli, jak każda inna, a nie czynnikiem decydującym o tym, czy pętla w ogóle zostanie uruchomiona.

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

Polecony stąd .

Hunter.S. Thompson
źródło
1
cat "/ tmp / urlFile" | podczas czytania adresu URL
zrobić
    echo $ url
gotowy

To jest bezużyteczne użyciecat .

Jak na ironię, możesz zastąpić ten catproces czymś naprawdę przydatnym: narzędziem, które systemy POSIX mają do dodania brakującej nowej linii i przekształcenia pliku we właściwy plik tekstowy POSIX.

sed -e '$ a \' "/ tmp / urlFile" | podczas odczytu -r url
zrobić
    printf "% s \ n" "$ {url}"
gotowy

Dalsza lektura

JdeBP
źródło
1
Zachowanie sed nie jest jednak określane przez POSIX, gdy dane wejściowe nie kończą się znakiem nowej linii; także wtedy, gdy istnieją linie większe niż LINE_MAX, podczas gdy zachowanie readjest określone w tych przypadkach.
Stéphane Chazelas,