file - unix2dos - set ff unix vim




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

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ê?


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 incluir 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' .


É muito tarde aqui, mas acabei de enfrentar um bug no processamento de arquivos e isso aconteceu porque os arquivos não estavam terminando com uma nova linha vazia. Estávamos processando arquivos de texto com sed e sed estava omitindo a última linha da saída que estava causando estrutura json inválida e enviando o restante do processo para o estado de falha.

Tudo o que estávamos fazendo era:

Há um arquivo de amostra que diz: foo.txt com algum conteúdo json dentro dele.

[{
    someProp: value
},
{
    someProp: value
}] <-- No newline here

O arquivo foi criado na máquina de widows e os scripts de janela estavam processando esse arquivo usando comandos powershall. Tudo bom.

Quando processamos o mesmo arquivo usando sed comando sed 's|value|newValue|g' foo.txt > foo.txt.tmp O arquivo recém-gerado foi

[{
    someProp: value
},
{
    someProp: value

e boom, ele falhou no restante dos processos devido ao JSON inválido.

Portanto, é sempre uma boa prática finalizar seu arquivo com uma nova linha vazia.


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


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 <newline> 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 <nova linha> 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 <nova linha>. 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 <newline>).

Caso em questão: wc -l filename .

Do manual do wc lemos:

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

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 <nova linha>, é 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 <newline>: 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 soma 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 entrada de leitura até EOF, não <newline>. Note que a opção -n de cat também imprime uma linha não <newline> 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 o 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 <newline> 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.


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 final 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.


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 ! O motivo é 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 entrada.


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.


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.


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 <newline> 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 ele não for terminado.

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 nova linha 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 questões 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 fluxo do token em vez do analisador - em outras palavras, incluir um token “newline artificial” no final da entrada.


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 ...







newline