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ê?
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ê?
Respostas:
Porque é assim que o padrão POSIX define uma linha :
- 3.206 Linha
- Uma sequência de zero ou mais caracteres não <newline> mais um caractere <newline> final.
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 ao processar a última linha de um arquivo, se não houver uma nova linha finalizada.
Há pelo menos uma grande vantagem nessa diretriz ao trabalhar em um emulador de terminal: Todas as ferramentas Unix esperam essa convenção e trabalham com ela. Por exemplo, ao concatenar arquivos com cat
, um arquivo encerrado por nova linha terá um efeito diferente de um sem:
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz
E, como o exemplo anterior também demonstra, ao exibir o arquivo na linha de comando (por exemplo, via more
), um arquivo finalizado por nova linha resulta em uma exibição correta. Um arquivo finalizado incorretamente pode estar distorcido (segunda linha).
Para maior consistência, é muito útil seguir esta regra - caso contrário, haverá um trabalho extra ao lidar com as ferramentas padrão do Unix.
Pense de maneira diferente: se as linhas não são terminadas por nova linha, tornar os comandos cat
úteis é muito mais difícil: como você faz um comando para concatenar arquivos como
b.txt
e c.txt
?É claro que isso é solucionável, mas você precisa tornar o uso cat
mais complexo (adicionando argumentos de linha de comando posicionais, por exemplo cat a.txt --no-newline b.txt c.txt
), e agora o comando, em vez de cada arquivo individual, controla como é colado junto com outros arquivos. Isso quase certamente não é conveniente.
… Ou você precisa introduzir um caractere sentinela especial para marcar uma linha que deve continuar e não terminar. Bem, agora você está preso à mesma situação que no POSIX, exceto invertido (continuação de linha em vez de caractere de término de linha).
Agora, em sistemas não compatíveis com POSIX (hoje em dia principalmente no Windows), o ponto é discutível: os arquivos geralmente não terminam com uma nova linha, e a definição (informal) de uma linha pode, por exemplo, ser "texto que é separado por novas linhas" (observe a ênfase). Isso é totalmente válido. Entretanto, para dados estruturados (por exemplo, código de programação), 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 fluxo do token do que o analisador - em outras palavras, adicione um token de "nova linha artificial" ao final da entrada.
cat
de uma maneira que seja útil e consistente.
Cada linha deve terminar em um caractere de nova linha, incluindo o último. Alguns programas têm problemas ao processar a última linha de um arquivo, se a nova linha não for finalizada.
O GCC alerta sobre isso não porque não pode processar o arquivo, mas porque precisa fazer parte do padrão.
O padrão da linguagem C diz que 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.
Como esta é uma cláusula "deve", devemos emitir uma mensagem de diagnóstico por violação desta regra.
Isso está na seção 2.1.1.2 da norma 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 GCC / GNU .
wc -l
não contará a última linha de um arquivo se ele não for finalizado com a nova linha. Além disso, cat
unirá a última linha de um arquivo com a primeira linha do próximo arquivo em uma, se a última linha do primeiro arquivo não for finalizada com a nova linha. Praticamente qualquer programa que esteja procurando novas linhas como delimitador tem o potencial de atrapalhar isso.
wc
se já foi mencionado ....
cat
e wc
)?
Esta resposta é uma tentativa de uma resposta técnica e não de opinião.
Se queremos ser puristas do POSIX, definimos uma linha como:
Uma sequência de zero ou mais caracteres não <newline> mais um caractere <newline> final.
Fonte: https://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 <newline> no final do arquivo.
Fonte: https://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 <newline>. Embora o 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 produzem apenas resultados previsíveis ou significativos ao operar em arquivos de texto. Os utilitários padrão que possuem essas restrições sempre especificam "arquivos de texto" nas seções STDIN ou INPUT FILES.
Fonte: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397
Uma sequência como:
Uma sequência contígua de bytes terminada por e incluindo o primeiro byte nulo.
Fonte: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396
Deste então, podemos derivar que a única vez que vamos potencialmente encontrar qualquer tipo de questões são se lidar 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 igual a zero ou mais linhas, e uma linha que conhecemos deve terminar com um <newline>).
Caso em questão: wc -l filename
.
No wc
manual, lemos:
Uma linha é definida como uma sequência de caracteres delimitada por um caractere <newline>.
Quais são as implicações nos arquivos JavaScript, HTML e CSS, pois são arquivos de texto ?
Em navegadores, IDEs modernos e outros aplicativos front-end, não há problemas em ignorar o EOL no EOF. Os aplicativos analisarão os arquivos corretamente. Como nem todos os sistemas operacionais estão em conformidade com o padrão POSIX, seria impraticável que ferramentas que não sejam de sistema operacional (por exemplo, navegadores) manipulem 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 não terá praticamente nenhum impacto negativo no nível do aplicativo - independentemente de estar em execução em um SO UNIX.
Neste ponto, podemos dizer com segurança que ignorar o EOL no EOF é seguro ao lidar com JS, HTML, CSS no lado do cliente. Na verdade, podemos afirmar que minimizar qualquer um desses arquivos, que não contenha <newline>, é seguro.
Podemos dar um passo adiante e dizer que, no que diz respeito ao NodeJS, ele também não pode aderir ao padrão POSIX, pois pode ser executado em ambientes não compatíveis com POSIX.
O que nos resta então? Ferramentas no nível do sistema.
Isso significa que os únicos problemas que podem surgir são as ferramentas que se esforçam para aderir sua funcionalidade à semântica do POSIX (por exemplo, definição de uma linha conforme mostrado em wc
).
Mesmo assim, nem todas as conchas aderem automaticamente ao POSIX. O Bash, por exemplo, não é padrão para o comportamento POSIX. Há um interruptor para ativá-lo: POSIXLY_CORRECT
.
Alimento para reflexão sobre o valor da EOL <newline>: https://www.rfc-editor.org/old/EOLstory.txt
Permanecendo na trilha de ferramentas, para todos os propósitos e propósitos práticos, vamos considerar o seguinte:
Vamos trabalhar com um arquivo que não possui EOL. No momento da redação deste documento, 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 cat
tamanho do arquivo é exatamente a soma de suas partes individuais. Se a concatenação de arquivos JavaScript for uma preocupação para arquivos JS, a preocupação mais apropriada seria iniciar cada arquivo JavaScript com ponto e vírgula.
Como outra pessoa mencionada neste tópico: e se você quiser cat
dois arquivos cuja saída se torne apenas uma linha em vez de duas? Em outras palavras, cat
faz o que deveria fazer.
O man
de cat
apenas menciona a entrada de leitura até EOF, não <newline>. Observe que a -n
opção de cat
imprimir também imprimirá uma linha não terminada não <nova linha> (ou linha incompleta ) como uma linha - sendo que a contagem começa em 1 (de acordo com o man
.)
-n Numere as linhas de saída, começando em 1.
Agora que entendemos como o POSIX define uma linha , esse comportamento se torna ambíguo ou realmente não compatível.
A compreensão do objetivo e da conformidade de uma determinada ferramenta ajudará a determinar o quão crítico é finalizar os arquivos com uma EOL. Em C, C ++, Java (JARs), etc ... alguns padrões determinam uma nova linha de validade - esse padrão não existe para JS, HTML, CSS.
Por exemplo, em vez de usar o que wc -l filename
se poderia fazer awk '{x++}END{ print x}' filename
, tenha certeza de que o sucesso da tarefa não será prejudicado por um arquivo que poderemos processar que não escrevemos (por exemplo, uma biblioteca de terceiros como a JS minificada que damos curl
) - a menos que nosso A intenção era realmente contar linhas no sentido compatível com POSIX.
Conclusão
Haverá muito poucos casos de uso da vida real em que ignorar o EOL no EOF para determinados arquivos de texto, como JS, HTML e CSS, terá um impacto negativo - se houver. Se confiarmos na presença de <newline>, restringiremos 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: ferramentas de engenheiro que não têm a fraqueza de confiar na EOL na EOF.
Sinta-se à vontade para postar casos de uso, como eles se aplicam a JS, HTML e CSS, onde podemos examinar como ignorar o EOL tem um efeito adverso.
Pode estar relacionado à diferença entre :
Se cada linha termina em um final de linha, isso evita, por exemplo, que a concatenação de dois arquivos de texto faça com que a última linha da primeira seja executada na primeira linha da segunda.
Além disso, um editor pode verificar se o arquivo termina em um final de linha, salva-o na opção local 'eol' e o usa ao gravar o arquivo.
Alguns anos atrás (2005), muitos editores (ZDE, Eclipse, Scite, ...) "esqueceram" a EOL final, o que não foi muito apreciado .
Não apenas isso, mas eles interpretaram a EOL final incorretamente, como 'iniciar uma nova linha' e, na verdade, começam a exibir outra linha como se ela já existisse.
Isso ficou 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. Ele exibia 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
wc
não espera isso, por mais que esteja simplesmente trabalhando dentro da definição POSIX de "linha" em oposição à compreensão intuitiva da maioria das pessoas sobre "linha".
wc -l
imprimir 1
nos dois casos, mas algumas pessoas podem dizer que o segundo caso deve ser impresso 2
.
\n
em um terminador de linha, e não como um separador de linha, como o POSIX / UNIX, então esperar que o segundo caso imprima 2 é absolutamente louco.
Basicamente, existem muitos programas que não processam os arquivos corretamente se eles não obtiverem o EOL EOF final.
O GCC alerta sobre isso porque é esperado como parte do padrão C. (seção 5.1.1.2 aparentemente)
Aviso do compilador "Nenhuma nova linha no final do arquivo"
Isso se origina desde os primeiros dias em que terminais simples foram usados. O novo caractere de linha foi usado para disparar uma 'descarga' dos dados transferidos.
Hoje, o novo caractere de linha não é mais necessário. Claro, muitos aplicativos ainda têm problemas se a nova linha não estiver lá, mas consideraria um bug nesses aplicativos.
Se, no entanto, você possui um formato de arquivo de texto em que precisa da nova linha, você obtém uma verificação simples de dados muito barata: se o arquivo termina com uma linha que não tem 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 sem tempo de CPU.
Um caso de uso separado: quando seu arquivo de texto é controlado por versão (neste caso, especificamente no git, embora também se aplique a outros). Se o conteúdo for adicionado ao final do arquivo, a linha que era anteriormente a última linha será editada para incluir um caractere de nova linha. Isso significa que blame
o arquivo para descobrir quando a última linha foi editada mostrará a adição de texto, não a confirmação antes que você realmente queria ver.
\n
). Problema resolvido.
Além das razões práticas acima, não me surpreenderia se os criadores do Unix (Thompson, Ritchie, et al.) Ou seus antecessores Multics perceberam que há uma razão teórica para usar terminadores de linha em vez de separadores de linha: Com linha terminadores, você pode codificar todos os arquivos de linhas possíveis. Com os separadores de linha, não há diferença entre um arquivo com zero linhas e um arquivo contendo uma única linha vazia; ambos são codificados como um arquivo que contém zero caracteres.
Então, os motivos são:
wc -l
não contará uma "linha" final se não terminar com uma nova linha.cat
apenas funciona e funciona sem complicações. Ele apenas copia os bytes de cada arquivo, sem necessidade de interpretação. Eu não acho que exista um DOS equivalente cat
. O uso copy a+b c
acabará mesclando a última linha do arquivo a
com a primeira linha do arquivo b
.Eu me pergunto isso há anos. Mas me deparei com uma boa razão hoje.
Imagine um arquivo com um registro em todas as linhas (por exemplo, um arquivo CSV). E que o computador estava gravando registros no final do arquivo. Mas de repente caiu. Gee foi a última linha completa? (não é uma situação agradável)
Mas se sempre terminamos a última linha, saberíamos (basta verificar se a última linha está terminada). Caso contrário, provavelmente teríamos que descartar a última linha todas as vezes, apenas para estarmos seguros.
Presumivelmente, simplesmente que algum código de análise esperava que ele estivesse lá.
Não tenho certeza se consideraria uma "regra", e certamente não é algo que eu adira religiosamente. O código mais sensato saberá analisar o texto (incluindo codificações) linha por linha (qualquer opção de final 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 a EOL e a EOF? Um para refletir ...
Há também um problema prático de programação com arquivos sem novas linhas no final: o read
Bash interno (não sei sobre outras read
implementações) não funciona como o esperado:
printf $'foo\nbar' | while read line
do
echo $line
done
Isso imprime apenasfoo
! O motivo é que, quando read
encontra a última linha, ele grava o conteúdo, $line
mas retorna o código de saída 1 porque atingiu o EOF. Isso interrompe o while
ciclo, por isso nunca alcançamos a echo $line
parte. Se você quiser lidar com essa situação, faça o seguinte:
while read line || [ -n "${line-}" ]
do
echo $line
done < <(printf $'foo\nbar')
Ou seja, faça o echo
se read
falhou devido a uma linha não vazia no final do arquivo. Naturalmente, nesse caso, haverá uma nova linha extra na saída que não estava na entrada.
Por que os arquivos (texto) devem terminar com uma nova linha?
Bem expresso por muitos, porque:
Muitos programas não se comportam bem ou falham sem ele.
Mesmo os programas que lidam bem com um arquivo não têm final '\n'
, a funcionalidade da ferramenta pode não atender às expectativas do usuário - o que pode não ser claro neste caso de canto.
Programas raramente desaprovam final '\n'
(não conheço nenhum).
No entanto, isso gera a próxima pergunta:
O que o código deve fazer sobre arquivos de texto sem uma nova linha?
Mais importante - não escreva código que pressupõe que um arquivo de texto termine com uma nova linha . Assumir que um arquivo esteja em conformidade com um formato leva a 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
...
}
Se o rastreamento final '\n'
for necessário, alerte o usuário sobre sua ausência e as medidas tomadas. IOWs, valide o formato do arquivo. Nota: Isso pode incluir um limite para o comprimento máximo da linha, codificação de caracteres etc.
Defina claramente, documento, a manipulação do código de uma final ausente '\n'
.
Não gere , como possível, um arquivo que não possui final '\n'
.
É muito tarde aqui, mas eu apenas enfrentei um erro no processamento de arquivos e isso ocorreu porque os arquivos não estavam terminando com uma nova linha vazia. Estávamos processando arquivos de texto com sed
esed
omitindo a última linha da saída, causando a estrutura json inválida e enviando o restante do processo para o estado de falha.
Tudo o que estávamos fazendo era:
Há um exemplo de arquivo: foo.txt
com algum json
conteúdo dentro dele.
[{
someProp: value
},
{
someProp: value
}] <-- No newline here
O arquivo foi criado na máquina de viúvas e os scripts da janela estavam processando esse arquivo usando os comandos do PowerShell. Tudo bom.
Quando processamos o mesmo arquivo usando o sed
comandosed 's|value|newValue|g' foo.txt > foo.txt.tmp
O arquivo recém-gerado foi
[{
someProp: value
},
{
someProp: value
e boom, falhou no restante dos processos devido ao JSON inválido.
Portanto, é sempre uma boa prática finalizar seu arquivo com uma nova linha vazia.
Eu sempre tive a impressão de que a regra vinha dos dias em que era difícil analisar um arquivo sem uma nova linha final. Ou seja, você acabaria escrevendo código onde um final de linha foi definido pelo caractere EOL ou EOF. Era mais simples supor que uma linha terminasse com EOL.
No entanto, acredito que a regra é derivada de compiladores C que exigem a nova linha. E, como apontado no aviso do compilador "Nenhuma 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.
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 que havia erros de compilação (gcc, se não me engano) porque os arquivos de código-fonte não terminaram com uma nova linha vazia. Por que foi feito dessa maneira, resta-se pensar.
IMHO, é uma questão de estilo pessoal e opinião.
Antigamente, eu não colocava essa nova linha. Um caractere salvo significa mais velocidade nesse modem de 14,4K.
Posteriormente, coloquei essa nova linha para facilitar a seleção da linha final usando shift + downarrow.