Lê um arquivo orientado por linha que pode não terminar com uma nova linha

7

Eu tenho um arquivo chamado /tmp/urlFile , onde cada linha representa um URL. Eu 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 estava me perguntando por que?

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

    
por Tim 18.01.2018 / 18:43

6 respostas

9

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 (não) linha).

Veja também:

por 18.01.2018 / 19:03
6

Isso parece ser resolvido em parte com readarray -t :

readarray -t urls "/tmp/urlFile"
for url in "${urls[@]}"; do
    echo "$url"
done

Observe, entretanto, 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, em seguida, deve ser iterada. Para arquivos muito grandes, isso pode consumir tanto o tempo quanto a memória, potencialmente até o ponto de falha.

    
por 18.01.2018 / 18:59
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 read builtin destina-se apenas a ler arquivos de texto. Você não está passando um arquivo de texto, então você não pode esperar que ele funcione perfeitamente. O shell lê todas as linhas - o que está pulando são os caracteres extras depois da última linha.

Se você tem um arquivo de entrada potencialmente malformado que pode estar faltando sua última linha, você pode adicionar uma nova linha a ele, só para ter certeza.

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

Os arquivos que devem ser arquivos de texto, mas não possuem a nova linha final, são geralmente produzidos pelos editores do Windows. Isso geralmente ocorre em combinação com os finais 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 não podem aparecer em 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, o echo adiciona uma linha extra em branco. Como as URLs não podem estar vazias, basta ignorar as linhas em branco.

Observe também que read não lê linhas de maneira simples. Ele ignora espaços em branco iniciais e finais, o que, para uma 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 seqüência de barra invertida-nova linha, o que definitivamente não é desejável. Então você deve passar a opção -r para read . É muito raro que read seja a coisa certa em vez de read -r .

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

Bem, read retorna um valor falsy se encontrar o fim do arquivo antes de uma nova linha, mas mesmo assim, ele ainda atribui o valor lido. Assim, podemos verificar se a chamada final de read retorna algo diferente de uma linha vazia e processá-la normalmente. Então, apenas saia do loop após read retornar false e a linha estiver 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
    
por 18.01.2018 / 19:03
1

Outra maneira seria assim:

When read reaches end-of-file instead of end-of-line, it does read in the data and assign it to the variables, but it exits with a non-zero status. If your loop is constructed "while read ;do stuff ;done

So instead of testing the read exit status directly, test a flag, and have the read command set that flag from within the loop body. That way regardless of reads exit status, the entire loop body runs, because read was just one of the list of commands in the loop like any other, not a deciding factor of if the loop will get run at all.

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

Referido de aqui .

    
por 18.01.2018 / 19:06
1
cat "/tmp/urlFile" | while read url
do
    echo $url
done

Este é um Uso inútil de cat .

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

sed -e '$a\' "/tmp/urlFile" | while read -r url
do
    printf "%s\n" "${url}"
done

Leitura adicional

por 18.01.2018 / 21:09