Leia um arquivo orientado a linhas que pode não terminar com uma nova linha

11

Eu tenho um arquivo chamado /tmp/urlFileonde cada linha representa um URL. Estou tentando ler o arquivo da seguinte maneira:

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

Se a última linha não terminar com um caractere de nova linha, essa linha não será lida. Eu queria saber por que?

É possível ler todas as linhas, independentemente de terem terminado com uma nova linha ou não?

Tim
fonte
8
É discutido em Por que o uso de um loop de shell para processar o texto é considerado uma má prática? (com alguma maneira de fazê-lo)
Stéphane Chazelas
2
Hah @ Stéphane Eu gosto do TBD lá ;-).
Stephen Kitt
2
Outra maneira de adicionar a nova linha à direita, se ela estiver ausente; awk 1 /tmp/urlFile.. entãoawk 1 /tmp/urlFile | while ...
muru
@uru, essa é uma resposta melhor do que qualquer outra aqui.
Curinga
1
Desde que você está perguntando por que ele não é lido: stackoverflow.com/a/729795/1968
Konrad Rudolph

Respostas:

13

Você faria:

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

(efetivamente, esse loop adiciona de volta a nova linha ausente na última linha (não)).

Veja também:

Stéphane Chazelas
fonte
Obrigado. Eu li os artigos vinculados e talvez eu perca alguma coisa, por que "esse loop adiciona de volta a nova linha que falta na última (não) linha"?
Tim
1
@ Tim O que Stephane parece significar é que ele adiciona de volta a nova linha ausente na saída, já que todas as printfchamadas aqui têm \n.
Sergiy Kolodyazhnyy
6

Isso parece ser resolvido em parte com readarray -t:

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

Observe, no entanto, que, embora isso funcione para arquivos de tamanho razoável, esta solução apresenta um novo problema em potencial com arquivos muito grandes - ele primeiro lê o arquivo em uma matriz que deve ser iterada. Para arquivos muito grandes, isso pode consumir tempo e memória, potencialmente ao ponto de falha.

DopeGhoti
fonte
Obrigado. Qual parte ele resolve e qual não?
Tim
Ele resolve o problema com a falta de uma nova linha final, mas apresenta um novo problema em potencial com arquivos muito grandes, porque ele primeiro lê o arquivo em uma matriz que deve ser iterada.
DopeGhoti 18/01/19
1
@DopeGhoti Essa é uma boa informação - posso sugerir que você a adicione diretamente na resposta?
precisa saber é o seguinte
A resposta foi alterada.
DopeGhoti 19/0118
5

Por definição , um arquivo de texto consiste em uma sequência de linhas. Uma linha termina com um caractere de nova linha. Assim, um arquivo de texto termina com um caractere de nova linha, a menos que esteja vazio.

O readbuilt-in destina-se apenas a ler arquivos de texto. Você não está passando um arquivo de texto, portanto, não pode esperar que ele funcione perfeitamente. O shell lê todas as linhas - o que está pulando são os caracteres extras após a última linha.

Se você tiver um arquivo de entrada potencialmente malformado que pode estar faltando sua última linha, poderá adicionar uma nova linha a ele, apenas para ter certeza.

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

Os arquivos que devem ser arquivos de texto, mas que não possuem a nova linha final, geralmente são produzidos pelos editores do Windows. Isso geralmente ocorre em combinação com as terminações de linha do Windows, que são CR LF, em oposição ao LF do Unix. Os caracteres CR raramente são úteis em qualquer lugar e, em qualquer caso, não podem aparecer nos URLs; portanto, você deve removê-los.

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

Caso o arquivo de entrada seja bem formado e termine com uma nova linha, isso echoadiciona uma linha em branco extra. Como os URLs não podem estar vazios, apenas ignore as linhas em branco.

Observe também que readnão lê linhas de maneira direta. Ele ignora os espaços em branco à esquerda e à direita, o que para um URL é provavelmente desejável. Ele trata a barra invertida no final de uma linha como um caractere de escape, fazendo com que a próxima linha seja unida à primeira menos a sequência barra invertida-nova linha, o que definitivamente não é desejável. Então você deve passar a -ropção para read. É muito, muito raro readser a coisa certa e não read -r.

{ <"/tmp/urlFile" tr -d '\r'; echo; } | while read -r url
do
  if [ -z "$url" ]; then continue; fi
  
done
Gilles 'SO- parar de ser mau'
fonte
3

Bem, readretorna um valor falso se encontrar o final do arquivo antes de uma nova linha, mas, mesmo que isso aconteça, ele ainda atribui o valor lido. Portanto, podemos verificar se a chamada final readretorna algo diferente de uma linha vazia e processá-la normalmente. Portanto, apenas saia do loop após readretornar false e a linha estar vazia:

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

Outra maneira seria assim:

Quando a leitura atinge o final do arquivo em vez do final da linha, ela lê os dados e os atribui às variáveis, mas sai com um status diferente de zero. Se o seu loop for construído "durante a leitura; faça coisas; pronto

Portanto, em vez de testar diretamente o status de saída de leitura, teste um sinalizador e faça com que o comando de leitura defina esse sinalizador de dentro do corpo do loop. Dessa forma, independentemente do status de saída das leituras, todo o corpo do loop é executado, porque a leitura era apenas uma das listas de comandos no loop como qualquer outro, não um fator decisivo para determinar se o loop será executado.

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

Referido a partir daqui .

Hunter.S. Thompson
fonte
1
gato "/ tmp / urlFile" | enquanto lê o URL
Faz
    eco $ url
feito

Este é um uso inútil decat .

Ironicamente, você pode substituir o cat processo aqui por algo realmente útil: uma ferramenta que os sistemas POSIX possuem para adicionar a nova linha ausente e transformar o arquivo em um arquivo de texto POSIX adequado.

sed -e '$ a \' "/ tmp / urlFile" | enquanto lê -r url
Faz
    printf "% s \ n" "$ {url}"
feito

Leitura adicional

JdeBP
fonte
1
O comportamento do sed não é especificado pelo POSIX quando a entrada não termina em um caractere de nova linha; também quando houver linhas maiores que LINE_MAX, enquanto o comportamento de readfor especificado nesses casos.
Stéphane Chazelas