Qual é o sentido de adicionar uma nova linha ao final de um arquivo?

166

Alguns compiladores (especialmente os C ou C ++) emitem avisos sobre:

No new line at end of file

Eu pensei que isso seria um problema apenas de programadores C, mas o github exibe uma mensagem na exibição de confirmação:

\ No newline at end of file

para um arquivo PHP.

Eu entendo a coisa do pré-processador explicada neste tópico , mas o que isso tem a ver com PHP? É a mesma include()coisa ou está relacionada ao tópico \r\nvs \n?

Qual é o sentido de ter uma nova linha no final de um arquivo?

Philipp Stephan
fonte
Duplicar do SO: stackoverflow.com/questions/729692/…
AlikElzin-kilaka 31/12/19
2
Para irritar as pessoas.
Andrew
4
Se você for cato arquivo, o próximo prompt será anexado à "linha" final se não terminar com uma nova linha.
Aaron Franke

Respostas:

188

Não se trata de adicionar uma nova linha extra no final de um arquivo, mas de não remover a nova linha que deveria estar lá.

Um arquivo de texto , no unix, consiste em uma série de linhas , cada uma das quais termina com um caractere de nova linha ( \n). Um arquivo que não está vazio e não termina com uma nova linha não é, portanto, um arquivo de texto.

Os utilitários que devem operar com arquivos de texto podem não funcionar bem com arquivos que não terminam com uma nova linha; utilitários Unix históricos podem ignorar o texto após a última nova linha, por exemplo. Os utilitários GNU têm uma política de comportamento decente com arquivos que não são de texto, assim como a maioria dos outros utilitários modernos, mas você ainda pode encontrar um comportamento estranho com arquivos que não possuem uma nova linha final¹.

Com o GNU diff, se um dos arquivos comparados termina com uma nova linha, mas não com o outro, é preciso observar esse fato. Como o diff é orientado a linhas, não é possível indicar isso armazenando uma nova linha para um dos arquivos, mas não para os outros - as novas linhas são necessárias para indicar onde cada linha no arquivo diff é iniciada e finalizada. O diff usa esse texto especial \ No newline at end of filepara diferenciar um arquivo que não terminou em uma nova linha de um arquivo que terminou.

A propósito, em um contexto C, um arquivo de origem também consiste em uma série de linhas. Mais precisamente, uma unidade de tradução é vista em uma implementação definida como uma série de linhas, cada uma das quais deve terminar com um caractere de nova linha ( n1256 §5.1.1.1). Em sistemas unix, o mapeamento é direto. No DOS e no Windows, cada sequência CR LF ( \r\n) é mapeada para uma nova linha ( \n; é o que sempre acontece ao ler um arquivo aberto como texto nesses sistemas operacionais). Existem alguns sistemas operacionais por aí que não têm um caractere de nova linha, mas possuem registros de tamanho fixo ou variável; nesses sistemas, o mapeamento de arquivos para a origem C introduz um\nno final de cada registro. Embora isso não seja diretamente relevante para o unix, significa que, se você copiar um arquivo de origem C que está perdendo sua nova linha final em um sistema com arquivos de texto com base em registros e copiá-lo novamente, você terminará com o arquivo incompleto a última linha truncada na conversão inicial ou uma nova linha extra pregada nela durante a conversão reversa.

¹ Exemplo: a saída da classificação GNU sempre termina com uma nova linha. Portanto, se o arquivo fooestiver com sua nova linha final em falta, você encontrará sort foo | wc -cum relatório a mais de um caractere cat foo | wc -c.

Gilles
fonte
Em relação a "... séries de linhas, cada uma das quais deve terminar com um caractere de nova linha (n1256 §5.1.1.1)" -> Ao revisar uma versão mais recente do C11dr N1570, não encontrou suporte para outra coisa senão talvez: "Um arquivo de origem que não está vazio deve terminar com um caractere de nova linha, que não deve ser imediatamente precedido por um caractere de barra invertida antes que qualquer emenda seja realizada". §5.1.1.2 2, mas isso parece estar restrito às especificações de emenda.
Chux
@chux Essa frase também está presente no n1256. A última linha deve terminar com um caractere de nova linha. As linhas que não são a última linha também devem obviamente terminar com um caractere de nova linha para indicar que essa linha termina e a próxima linha começa. Assim, cada linha deve terminar com um caractere de nova linha.
Gilles
Hmmm, para mim, essa linha "" Um arquivo de origem ... a união ocorre. "Pode se limitar a como as considerações sobre união e não os arquivos em geral. No entanto, eu vejo como alguém poderia ver o contrário. Talvez eu procure um post que incide sobre isso.
Chux
> "O diff usa este texto especial \ Nenhuma nova linha no final do arquivo para diferenciar um arquivo que não terminou em uma nova linha do arquivo que terminou." O Git mostra esse texto não apenas quando ele compara arquivos. Mas mesmo quando um novo arquivo foi adicionado ao git. Portanto, esse argumento não é válido, suponho.
Viktor Kruglikov 12/01
> "Utilitários que supostamente operam em arquivos de texto podem não funcionar bem com arquivos que não terminam com uma nova linha" Eu não acho que é negócio do git se preocupar com problemas de baixo nível, como falta de \ n por causa do POSIX requisitos. Eu acho que se o git mostra essa mensagem, a razão deve estar nos problemas de controle de origem .
Viktor Kruglikov 12/01
42

Não necessariamente o motivo, mas uma consequência prática de arquivos que não terminam com uma nova linha:

Considere o que aconteceria se você quisesse processar vários arquivos usando cat. Por exemplo, se você deseja encontrar a palavra foono início da linha em três arquivos:

cat file1 file2 file3 | grep -e '^foo'

Se a primeira linha do arquivo3 começar com foo, mas o arquivo2 não tiver uma final \napós a última linha, essa ocorrência não seria encontrada pelo grep, porque a última linha do arquivo2 e a primeira linha do arquivo3 seriam vistas pelo grep como uma única linha.

Portanto, por consistência e para evitar surpresas, tento manter meus arquivos sempre terminando com uma nova linha.

Sergio Acosta
fonte
Mas é negócio do git se preocupar com a concatenação de arquivos?
Viktor Kruglikov
Não é lógico que você deve apenas colocar '\n'na operação de gato ...
Andrew
3
É como dizer: "Às vezes, eu anexo Strings que têm \nespaço em branco nas extremidades; portanto, para manter as coisas consistentes, eu sempre coloco \n _____nas duas extremidades das minhas strings". Bem, não, a coisa certa a fazer é cortar as cordas e concatená-las adequadamente.
Andrew
16

Existem dois aspectos:

  1. Existem / existem alguns compiladores C que não podem analisar a última linha se ela não terminar com uma nova linha. O padrão C especifica que um arquivo C deve terminar com uma nova linha (C11, 5.1.1.2, 2.) e que uma última linha sem uma nova linha produz comportamento indefinido (C11, J.2, 2º item). Talvez por razões históricas, porque algum fornecedor desse compilador fazia parte do comitê quando o primeiro padrão foi escrito. Assim, o aviso do GCC.

  2. diffprogramas (como usado pelo git diffgithub etc.) mostram diferenças linha por linha entre os arquivos. Eles geralmente imprimem uma mensagem quando apenas um arquivo termina com uma nova linha, pois senão você não veria essa diferença. Por exemplo, se a única diferença entre dois arquivos é a presença do último caractere de nova linha, sem a dica, pareceria que os dois arquivos eram iguais, quando diffe cmpretornam um sucesso desigual no código de saída e as somas de verificação dos arquivos (por exemplo, via md5sum) não correspondem.

maxschlepzig
fonte
faz sentido com o programa diff
Thamaraiselvam
Parece que diffs deve ser mais inteligente.
Andrew
@ Andrew, não, não. diffé esperado que imprima diferenças, se houver alguma. E se um arquivo tem uma nova linha como último caractere enquanto o outro não, então essa diferença deve ser notada de alguma forma na saída.
maxschlepzig
Sua última declaração está correta. No entanto, o visualizador de diferenças não precisa exibir "novas linhas" ( \n) para começar; ele pode simplesmente mostrar "novas linhas".
Andrew
10

O que \ No newline at end of filevocê obtém do github aparece no final de um patch (em diffformato , veja a nota no final da seção "Formato unificado").

Os compiladores não se importam se há uma nova linha ou não no final de um arquivo, mas git(e os diff/ patchutilitários) precisam levar isso em consideração. Existem muitas razões para isso. Por exemplo, esquecer de adicionar ou remover uma nova linha no final de um arquivo alteraria seu hashsum ( md5sum/ sha1sum). Além disso, os arquivos nem sempre são programas, e uma final \npode fazer alguma diferença.

Nota : Sobre o aviso dos compiladores C, acho que eles insistem em uma nova linha final para fins de compatibilidade com versões anteriores. Compiladores muito antigos podem não aceitar a última linha se não terminar com \n(ou outra sequência de caracteres de final de linha dependente do sistema).

Stéphane Gimenez
fonte
7
"Acho que eles insistem em uma nova linha final para fins de compatibilidade com versões anteriores" - Não, eles insistem nisso porque o padrão C o exige .
MestreLion 28/08
11
O @MestreLion C requer uma nova linha final para o código-fonte C (C11 §5.1.1.2 2). Observe que, para E / S do arquivo de texto , C possui "Se a última linha requer um caractere de nova linha final é definida pela implementação". §7.21.2 2
chux
Quem está usando compiladores muito antigos? Pare de usá-los.
Andrew
11
@MestreLion: E por que você acha que o padrão C o exige…
Stéphane Gimenez
@ StéphaneGimenez: consistência, melhor compatibilidade e interoperabilidade entre diferentes sistemas operacionais (o POSIX também define linhas que terminam em '\ n')
MestreLion
4

Há também o ponto de manter a história do diff. Se um arquivo terminar sem um caractere de nova linha, adicionar qualquer coisa ao final do arquivo será visualizado pelos utilitários diff como alterando a última linha (porque \nestá sendo adicionada a ele).

Isso pode causar resultados indesejados com comandos como git blamee hg annotate.

Hosam Aly
fonte
Parece que diffs precisa ser mais inteligente.
Andrew
As ferramentas diferentes estão sendo inteligentes. Eles notam a alteração sutil no arquivo (o que é importante porque inevitavelmente altera o hash do arquivo). E o GNU diff e o git diff aceitam uma -wopção para ignorar as alterações de espaço em branco ao gerar dados para humanos.
joeytwiddle
4

POSIX, este é um conjunto de padrões especificados pelo IEEE para manter a compatibilidade entre sistemas operacionais.

Uma delas é a definição de uma "linha" como sendo uma sequência de zero ou mais caracteres não, além de um caractere de nova linha final.

Portanto, para que a última linha seja reconhecida como uma "linha" real, ela deve ter um caractere de nova linha final.

Isso é importante se você depende das ferramentas do SO para dizer a contagem de linhas ou dividir / ajudar a analisar seu arquivo. Dado que o PHP é uma linguagem de script, é inteiramente possível, especialmente nos seus primeiros dias ou até agora (não faço idéia / postulando) que tinha dependências do SO como essa.

Na realidade, a maioria dos sistemas operacionais não é totalmente compatível com POSIX e os humanos não são tão parecidos com máquinas ou se preocupam em terminar novas linhas. Portanto, para a maioria das coisas, é uma mistura de tudo, quer se preocupe com isso, avisar ou apenas passar o último pedaço de texto é realmente uma linha, então inclua-o.

user3379747
fonte