Por que os arquivos de texto devem terminar com uma nova linha?

Presumo que todos aqui estejam familiarizados com o ditado de que todos os arquivos de texto devem terminar com uma nova linha. Eu conheço essa “regra” há anos, mas sempre me perguntei – por quê?

Porque é assim que o padrão POSIX define uma linha :

Linha 3.206
Uma seqüência de zero ou mais caracteres não-newline além de um caractere de terminação.

Portanto, as linhas que não terminam em um caractere de nova linha não são consideradas linhas reais. É por isso que alguns programas têm problemas para processar a última linha de um arquivo, se não for terminada em nova linha.

Há pelo menos uma vantagem para essa diretriz quando se trabalha em um emulador de terminal: Todas as ferramentas do Unix esperam essa convenção e trabalham com ela. Por exemplo, ao concatenar arquivos com cat , um arquivo terminado por newline terá um efeito diferente de um sem:

 $ more a.txt foo$ more b.txt bar $ more c.txt baz $ cat *.txt foobar baz 

E, como o exemplo anterior também demonstra, ao exibir o arquivo na linha de comando (por exemplo, via more ), um arquivo terminado por nova linha resulta em uma exibição correta. Um arquivo incorretamente terminado pode ser truncado (segunda linha).

Por uma questão de consistência, é muito útil seguir esta regra – fazer o contrário resultará em trabalho extra ao lidar com as ferramentas padrão do Unix.

Agora, em sistemas não compatíveis com POSIX (hoje em dia, principalmente Windows), o ponto é discutível: os arquivos geralmente não terminam com uma nova linha e a definição (informal) de uma linha pode ser, por exemplo, “texto separado por novas linhas”. (note a ênfase). Isso é inteiramente válido. No entanto, para dados estruturados (por exemplo, código de programação), torna a análise minimamente mais complicada: geralmente significa que os analisadores precisam ser reescritos. Se um analisador foi originalmente escrito com a definição POSIX em mente, pode ser mais fácil modificar o stream do token em vez do analisador – em outras palavras, include um token “newline artificial” no final da input.

Cada linha deve ser terminada em um caractere de nova linha, incluindo o último. Alguns programas têm problemas ao processar a última linha de um arquivo, se não for terminada em nova linha.

O GCC alerta sobre isso não porque não pode processar o arquivo, mas porque ele precisa fazer parte do padrão.

O padrão da linguagem C diz que Um arquivo de origem que não está vazio terminará em um caractere de nova linha, que não deve ser imediatamente precedido por um caractere de barra invertida.

Como esta é uma cláusula “deve”, devemos emitir uma mensagem de diagnóstico para uma violação desta regra.

Isso está na seção 2.1.1.2 do padrão ANSI C 1989. Seção 5.1.1.2 da norma ISO C 1999 (e provavelmente também a norma ISO C 1990).

Referência: O arquivo de mensagens do GCC / GNU .

Essa resposta é uma tentativa de resposta técnica, e não de opinião.

Se queremos ser puristas POSIX, definimos uma linha como:

Uma seqüência de zero ou mais caracteres não-newline além de um caractere de terminação.

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

Uma linha incompleta como:

Uma sequência de um ou mais caracteres não no final do arquivo.

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195

Um arquivo de texto como:

Um arquivo que contém caracteres organizados em zero ou mais linhas. As linhas não contêm caracteres NUL e nenhuma pode exceder {LINE_MAX} bytes de comprimento, incluindo o caractere . Embora POSIX.1-2008 não faça distinção entre arquivos de texto e arquivos binários (consulte o padrão ISO C), muitos utilitários só produzem resultados previsíveis ou significativos ao operar em arquivos de texto. Os utilitários padrão que possuem tais restrições sempre especificam “arquivos de texto” em suas seções STDIN ou INPUT FILES.

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

Uma string como:

Uma sequência contígua de bytes terminada por e incluindo o primeiro byte nulo.

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

A partir daí, podemos deduzir que a única vez em que potencialmente encontraremos qualquer tipo de problema é lidarmos com o conceito de uma linha de um arquivo ou um arquivo como um arquivo de texto (sendo que um arquivo de texto é uma organização de zero ou mais linhas, e uma linha que sabemos deve terminar com um ).

Caso em questão: wc -l filename .

Do manual do wc lemos:

Uma linha é definida como uma cadeia de caracteres delimitada por um caractere .

Quais são as implicações para os arquivos JavaScript, HTML e CSS, sendo que são arquivos de texto ?

Em navegadores, IDEs modernos e outros aplicativos front-end, não há problemas em pular o EOL no EOF. Os aplicativos analisarão os arquivos corretamente. É necessário que nem todos os Sistemas Operacionais estejam em conformidade com o padrão POSIX, portanto, seria impraticável para ferramentas que não são do SO (por exemplo, navegadores) manipularem arquivos de acordo com o padrão POSIX (ou qualquer padrão no nível do sistema operacional).

Como resultado, podemos estar relativamente confiantes de que o EOL no EOF praticamente não terá impacto negativo no nível do aplicativo – independentemente de estar em execução em um sistema operacional UNIX.

Neste ponto, podemos dizer com segurança que pular o EOL no EOF é seguro quando se lida com JS, HTML, CSS no lado do cliente. Na verdade, podemos afirmar que a exclusão de qualquer um desses arquivos, que não contenha nenhuma , é segura.

Podemos dar um passo além e dizer que, no que diz respeito ao NodeJS, ele também não pode aderir ao padrão POSIX, pois ele pode ser executado em ambientes não compatíveis com POSIX.

Com o que nos resta então? Ferramentas de nível de sistema.

Isso significa que os únicos problemas que podem surgir são as ferramentas que fazem um esforço para aderir sua funcionalidade à semântica do POSIX (por exemplo, definição de uma linha como mostrado em wc ).

Mesmo assim, nem todos os shells aderem automaticamente ao POSIX. Bash, por exemplo, não padroniza o comportamento do POSIX. Há um comutador para ativá-lo: POSIXLY_CORRECT .

Alimento para pensar sobre o valor de EOL sendo : http://www.rfc-editor.org/EOLstory.txt

Permanecendo na faixa de ferramentas, para todos os efeitos e propósitos práticos, vamos considerar isso:

Vamos trabalhar com um arquivo que não tenha EOL. A partir desta redação, o arquivo neste exemplo é um JavaScript minificado sem EOL.

 curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js $ cat x.js y.js > z.js -rw-r--r-- 1 milanadamovsky 7905 Aug 14 23:17 x.js -rw-r--r-- 1 milanadamovsky 7905 Aug 14 23:17 y.js -rw-r--r-- 1 milanadamovsky 15810 Aug 14 23:18 z.js 

Observe que o tamanho do arquivo do cat é exatamente a sum de suas partes individuais. Se a concatenação de arquivos JavaScript for uma preocupação para os arquivos JS, a preocupação mais apropriada seria iniciar cada arquivo JavaScript com um ponto e vírgula.

Como alguém mencionou neste segmento: e se você quiser cat dois arquivos cuja saída se torna apenas uma linha em vez de dois? Em outras palavras, o cat faz o que deve fazer.

O man do cat menciona apenas a input de input até EOF, não . Note que a opção -n de cat também imprime uma linha não terminada (ou linha incompleta ) como uma linha – sendo que a contagem começa em 1 (de acordo com o man ).

-n Número das linhas de saída, começando em 1.

Agora que entendemos como o POSIX define uma linha , esse comportamento se torna ambíguo ou, na verdade, não compatível.

Entender o propósito e a conformidade de uma determinada ferramenta ajudará a determinar o quanto é importante encerrar arquivos com um EOL. Em C, C ++, Java (JARs), etc … alguns padrões ditarão uma nova linha de validade – não existe tal padrão para JS, HTML, CSS.

Por exemplo, em vez de usar wc -l filename é possível fazer awk '{x++}END{ print x}' filename e ter a certeza de que o sucesso da tarefa não é comprometido por um arquivo que possamos processar que não escrevemos ( por exemplo, uma biblioteca de terceiros, como a JS minificada que encurvamos d) – a menos que nossa intenção fosse realmente contar linhas no sentido compatível com POSIX.

Conclusão

Haverá pouquíssimos casos de uso da vida real em que pular o EOL no EOF para determinados arquivos de texto, como JS, HTML e CSS, terá um impacto negativo – se for o caso. Se confiarmos em estar presente, estamos restringindo a confiabilidade de nossas ferramentas apenas aos arquivos que criamos e nos abrimos para possíveis erros introduzidos por arquivos de terceiros.

Moral da história: ferramental de engenheiro que não tem a fraqueza de confiar na EOL no EOF.

Sinta-se à vontade para postar casos de uso como eles se aplicam ao JS, HTML e CSS, onde podemos examinar como pular o EOL tem um efeito adverso.

Pode estar relacionado com a diferença entre :

  • arquivo de texto (cada linha deve terminar em um final de linha)
  • arquivo binário (não há “linhas” verdadeiras para falar e o comprimento do arquivo deve ser preservado)

Se cada linha terminar em um final de linha, isso evita, por exemplo, que a concatenação de dois arquivos de texto torne a última linha da primeira execução na primeira linha do segundo.

Além disso, um editor pode verificar no carregamento se o arquivo termina em um fim-de-linha, salva em sua opção local ‘eol’ e usa isso ao gravar o arquivo.

Alguns anos atrás (2005), muitos editores (ZDE, Eclipse, Scite, …) fizeram “esquecer” essa EOL final, que não foi muito apreciada .
Não apenas isso, mas eles interpretaram o EOL final incorretamente, como “inicie uma nova linha”, e realmente começam a exibir outra linha como se ela já existisse.
Isso era muito visível com um arquivo de texto “adequado” com um editor de texto bem comportado como o vim, comparado a abri-lo em um dos editores acima. Exibiu uma linha extra abaixo da última linha real do arquivo. Você vê algo assim:

 1 first line 2 middle line 3 last line 4 

Algumas ferramentas esperam isso. Por exemplo, wc espera isso:

 $ echo -n "Line not ending in a new line" | wc -l 0 $ echo "Line ending with a new line" | wc -l 1 

Basicamente, existem muitos programas que não processam arquivos corretamente se não obtiverem o final EOL EOF.

O GCC avisa sobre isso porque é esperado como parte do padrão C. (seção 5.1.1.2 aparentemente)

“Não há nova linha no final do arquivo” aviso do compilador

Isso se origina desde os primeiros dias, quando foram usados ​​terminais simples. O caractere de nova linha foi usado para acionar um ‘flush’ dos dados transferidos.

Hoje, o caractere de nova linha não é mais necessário. Claro, muitos aplicativos ainda têm problemas se a nova linha não estiver lá, mas eu consideraria um bug nesses aplicativos.

Se, no entanto, você tiver um formato de arquivo de texto onde exija a nova linha, obterá uma verificação de dados simples muito barata: se o arquivo terminar com uma linha que não tenha nova linha no final, você sabe que o arquivo está quebrado. Com apenas um byte extra para cada linha, você pode detectar arquivos quebrados com alta precisão e quase nenhum tempo de CPU.

Um caso de uso separado: quando seu arquivo de texto é controlado por versão (neste caso, especificamente sob git, embora se aplique a outros também). Se o conteúdo for adicionado ao final do arquivo, a linha que anteriormente era a última linha será editada para include um caractere de nova linha. Isso significa que blame o arquivo para descobrir quando essa linha foi editada pela última vez mostrará a adição de texto, não o commit antes que você realmente queria ver.

Há também um problema de programação prática com arquivos com falta de novas linhas no final: O Bash interno de read (não sei sobre outras implementações de read ) não funciona como esperado:

 printf $'foo\nbar' | while read line do echo $line done 

Isso imprime apenas foo ! A razão é que, quando a read encontra a última linha, ela grava o conteúdo na $line mas retorna o código de saída 1 porque atingiu o EOF. Isso quebra o loop while, então nunca alcançamos a parte da echo $line . Se você quiser lidar com essa situação, você precisa fazer o seguinte:

 while read line || [ -n "${line-}" ] do echo $line done < <(printf $'foo\nbar') 

Ou seja, faça o echo se a read falhou por causa de uma linha não vazia no final do arquivo. Naturalmente, neste caso, haverá uma nova linha extra na saída que não estava na input.

Presumivelmente, simplesmente que algum código de análise esperava que estivesse lá.

Não tenho certeza se consideraria uma “regra”, e certamente não é algo que eu adote religiosamente. O código mais sensato saberá como analisar texto (incluindo codificações) linha por linha (qualquer escolha de terminações de linha), com ou sem uma nova linha na última linha.

De fato – se você terminar com uma nova linha: existe (em teoria) uma linha final vazia entre o EOL e o EOF? Um a ponderar …

Além das razões práticas acima, não me surpreenderia se os criadores do Unix (Thompson, Ritchie, et al.) Ou seus antecessores do Multics perceberam que há uma razão teórica para usar terminadores de linha em vez de separadores de linha: terminadores, você pode codificar todos os possíveis arquivos de linhas. Com os separadores de linha, não há diferença entre um arquivo de linhas zero e um arquivo contendo uma única linha vazia; ambos são codificados como um arquivo contendo zero caracteres.

Então, as razões são:

  1. Porque é assim que POSIX define.
  2. Porque algumas ferramentas esperam ou “se comportam mal” sem isso. Por exemplo, wc -l não contará uma “linha” final se não terminar com uma nova linha.
  3. Porque é simples e conveniente. No Unix, o cat simplesmente funciona e funciona sem complicação. Apenas copia os bytes de cada arquivo, sem necessidade de interpretação. Eu não acho que haja um equivalente do DOS ao cat . Usando a copy a+bc irá acabar fundindo a última linha do arquivo a com a primeira linha do arquivo b .
  4. Porque um arquivo (ou stream) de zero linhas pode ser distinguido de um arquivo de uma linha vazia.

Por que arquivos (texto) devem terminar com uma nova linha?

Como bem expressa por muitos, porque:

  1. Muitos programas não se comportam bem ou falham sem ele.

  2. Mesmo os programas que lidam bem com um arquivo não têm um final '\n' , a funcionalidade da ferramenta pode não atender às expectativas do usuário – o que pode não ser claro neste caso.

  3. Programas raramente desaprovam final '\n' (não sei de nenhum).


No entanto, isso implora a próxima pergunta:

O que deve fazer código sobre arquivos de texto sem uma nova linha?

  1. Mais importante – Não escreva um código que pressuponha que um arquivo de texto termine com uma nova linha . Assumir que um arquivo está em conformidade com um formato leva à corrupção de dados, ataques de hackers e falhas. Exemplo:

     // Bad code while (fgets(buf, sizeof buf, instream)) { // What happens if there is no \n, buf[] is truncated leading to who knows what buf[strlen(buf) - 1] = '\0'; // attempt to rid trailing \n ... } 
  2. Se o trailing final '\n' for necessário, alertar o usuário sobre sua ausência e a ação tomada. IOWs, valide o formato do arquivo. Nota: Isso pode include um limite para o comprimento máximo da linha, codificação de caracteres etc.

  3. Defina claramente, documente, a manipulação do código de um final '\n' ausente.

  4. Não, quanto possível, gerar um arquivo que não tenha o final '\n' .

Eu me perguntava isso há anos. Mas me deparei com uma boa razão hoje.

Imagine um arquivo com um registro em cada linha (ex: um arquivo CSV). E que o computador estava gravando registros no final do arquivo. Mas de repente caiu. Nossa última linha foi completa? (não é uma situação legal)

Mas se nós sempre terminamos a última linha, então saberíamos (simplesmente verifique se a última linha está terminada). Caso contrário, provavelmente teríamos que descartar a última linha todas as vezes, apenas para estar seguro.

Eu sempre tive a impressão de que a regra veio dos dias em que a análise de um arquivo sem uma nova linha de encerramento era difícil. Ou seja, você acabaria escrevendo código onde um fim de linha foi definido pelo caractere EOL ou EOF. Era mais simples assumir que uma linha terminava com EOL.

No entanto, acredito que a regra é derivada de compiladores C que exigem a nova linha. E, como apontado no aviso do compilador “Não há nova linha no final do arquivo” , #include não adicionará uma nova linha.

Imagine que o arquivo está sendo processado enquanto o arquivo ainda está sendo gerado por outro processo.

Pode ter a ver com isso? Um sinalizador que indica que o arquivo está pronto para ser processado.

Eu pessoalmente gosto de novas linhas no final dos arquivos de código-fonte.

Pode ter sua origem no Linux ou em todos os sistemas UNIX. Lembro-me de erros de compilation (gcc, se não estou enganado) porque os arquivos de código-fonte não terminaram com uma nova linha vazia. Por que foi feito assim, resta a maravilha.

IMHO, é uma questão de estilo pessoal e opinião.

Antigamente, eu não colocava essa nova linha. Um personagem salvo significa mais velocidade através desse modem de 14.4K.

Mais tarde, eu coloquei essa nova linha para que seja mais fácil selecionar a linha final usando shift + downarrow.