Leggi un file orientato alla linea che potrebbe non terminare con una nuova riga


11

Ho un file chiamato /tmp/urlFiledove ogni riga rappresenta un URL. Sto provando a leggere dal file come segue:

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

Se l'ultima riga non termina con un carattere di nuova riga, tale riga non verrà letta. Mi chiedevo perché?

È possibile leggere tutte le righe, indipendentemente dal fatto che siano terminate con una nuova riga o no?



2
Hah @ Stéphane Mi piace il TBD lì ;-).
Stephen Kitt,

2
Un altro modo per aggiungere la nuova riga finale se manca; awk 1 /tmp/urlFile.. cosìawk 1 /tmp/urlFile | while ...
muru

@muru, questa è una risposta migliore di qualsiasi altra qui.
Wildcard il

1
Dal momento che stai chiedendo perché non viene letto: stackoverflow.com/a/729795/1968
Konrad Rudolph,

Risposte:


13

Faresti:

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

(effettivamente, quel loop aggiunge nuovamente la nuova riga mancante sull'ultima riga (non)).

Guarda anche:


Grazie. Ho letto gli articoli collegati e forse mi manca qualcosa, perché "quel ciclo aggiunge la nuova riga mancante sull'ultima (non) riga"?
Tim

1
@Tim Ciò che Stephane sembra significare è che aggiunge nuovamente la nuova riga mancante nell'output poiché tutte le printfchiamate qui hanno \n.
Sergiy Kolodyazhnyy,

6

Questo sembra essere risolto in parte con readarray -t:

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

Si noti tuttavia che, sebbene funzioni per file di dimensioni ragionevoli, questa soluzione introduce un potenziale nuovo problema con file molto grandi: legge prima il file in un array che deve essere ripetuto. Per file molto grandi, ciò potrebbe richiedere molto tempo e memoria, potenzialmente fino al punto di errore.


Grazie. Quale parte risolve e quale no?
Tim

Risolve il problema con la mancanza di una nuova riga finale, ma introduce un potenziale nuovo problema con file molto grandi, perché prima legge il file in un array che deve quindi essere ripetuto.
DopeGhoti,

1
@DopeGhoti Queste sono buone informazioni - posso suggerirti di aggiungerle direttamente alla risposta?
RJHunter,

La risposta è stata così modificata.
DopeGhoti,

5

Per definizione , un file di testo è costituito da una sequenza di righe. Una riga termina con un carattere di nuova riga. Pertanto un file di testo termina con un carattere di nuova riga, a meno che non sia vuoto.

L' readintegrato è pensato solo per leggere file di testo. Non stai passando un file di testo, quindi non puoi sperare che funzioni senza problemi. La shell legge tutte le righe: ciò che sta saltando sono i caratteri extra dopo l'ultima riga.

Se si dispone di un file di input potenzialmente non valido che potrebbe mancare all'ultima riga, è possibile aggiungere una nuova riga ad esso, solo per essere sicuri.

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

I file che dovrebbero essere file di testo ma che mancano della nuova riga finale vengono spesso prodotti dagli editor di Windows. Questo di solito va in combinazione con i finali di linea di Windows, che sono CR LF, al contrario di Lx di Unix. I caratteri CR sono raramente utili ovunque e non possono apparire negli URL in ogni caso, quindi è necessario rimuoverli.

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

Nel caso in cui il file di input sia ben formato e termina con una nuova riga, viene echoaggiunta una riga vuota aggiuntiva. Poiché gli URL non possono essere vuoti, ignora solo le righe vuote.

Si noti inoltre che readnon legge le righe in modo semplice. Ignora gli spazi bianchi iniziali e finali, che per un URL è probabilmente desiderabile. Tratta la barra rovesciata alla fine di una linea come un carattere di escape, facendo sì che la linea successiva venga unita al primo meno la sequenza barra rovesciata-newline, il che non è assolutamente desiderabile. Quindi dovresti passare l' -ropzione a read. È molto, molto raro readessere la cosa giusta piuttosto che read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done

3

Bene, readrestituisce un valore errato se incontra la fine del file prima di una nuova riga, ma anche se lo fa, assegna comunque il valore letto. Quindi, possiamo verificare se la chiamata finale di readrestituisce qualcosa di diverso da una linea vuota ed elaborarla come di consueto. Quindi, esci dal ciclo solo dopo che readrestituisce false e la riga è vuota:

#!/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

1

Un altro modo sarebbe come questo:

Quando read raggiunge la fine del file anziché la fine della riga, legge i dati e li assegna alle variabili, ma esce con uno stato diverso da zero. Se il tuo ciclo è costruito "mentre leggi; fai cose; fatto

Quindi, invece di testare direttamente lo stato dell'uscita di lettura, testare un flag e fare in modo che il comando di lettura abbia impostato quel flag all'interno del corpo del loop. In questo modo, indipendentemente dallo stato di uscita delle letture, viene eseguito l'intero corpo del loop, poiché read era solo uno degli elenchi di comandi nel loop come un altro, non un fattore decisivo se il loop verrà eseguito affatto.

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

Riferito da qui .


1
cat "/ tmp / urlFile" | mentre leggi url
fare
    echo $ url
fatto

Questo è un uso inutile dicat .

Ironia della sorte, qui puoi sostituire il catprocesso con qualcosa di veramente utile: uno strumento che i sistemi POSIX hanno per aggiungere la nuova riga mancante e trasformare il file in un file di testo POSIX appropriato.

sed -e '$ a \' "/ tmp / urlFile" | mentre leggi -r url
fare
    printf "% s \ n" "$ {url}"
fatto

Ulteriori letture


1
Il comportamento di sed non è specificato da POSIX quando l'input non termina con un carattere di nuova riga; anche quando ci sono linee più grandi di LINE_MAX, mentre il comportamento di readè specificato in quei casi.
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.