¿Por qué los archivos de texto deben terminar con una nueva línea?

Supongo que todos aquí están familiarizados con el adagio de que todos los archivos de texto deben terminar con una nueva línea. Conozco esta “regla” desde hace años, pero siempre me he preguntado: ¿por qué?

Porque así es como el estándar POSIX define una línea :

3.206 Línea
Una secuencia de cero o más caracteres no más un carácter de terminación .

Por lo tanto, las líneas que no terminan en un carácter de nueva línea no se consideran líneas reales. Es por eso que algunos progtwigs tienen problemas para procesar la última línea de un archivo si no se termina la línea nueva.

Al trabajar con un emulador de terminal, hay al menos una ventaja importante en esta directriz: todas las herramientas de Unix esperan esta convención y funcionan con ella. Por ejemplo, al concatenar archivos con cat , un archivo terminado por nueva línea tendrá un efecto diferente que uno sin:

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

Y, como también lo demuestra el ejemplo anterior, al mostrar el archivo en la línea de comando (por ejemplo, a través de more ), un archivo terminado en la nueva línea da como resultado una visualización correcta. Un archivo incorrectamente terminado puede ser confuso (segunda línea).

Para mayor coherencia, es muy útil seguir esta regla; de lo contrario, se incurrirá en trabajo adicional al tratar con las herramientas Unix predeterminadas.

Ahora, en sistemas no compatibles con POSIX (hoy en día es principalmente Windows), el punto es discutible: los archivos generalmente no terminan con una nueva línea, y la definición (informal) de una línea podría ser, por ejemplo, “texto separado por líneas nuevas” (tenga en cuenta el énfasis). Esto es completamente válido. Sin embargo, para los datos estructurados (por ejemplo, código de progtwigción) hace que el análisis sea mínimamente más complicado: generalmente significa que los analizadores deben ser reescritos. Si un analizador se escribió originalmente con la definición POSIX en mente, entonces podría ser más fácil modificar la secuencia de token en lugar del analizador; en otras palabras, agregar un token de “nueva línea artificial” al final de la entrada.

Cada línea debe terminar en un carácter de nueva línea, incluido el último. Algunos progtwigs tienen problemas para procesar la última línea de un archivo si no se termina la línea nueva.

GCC lo advierte no porque no pueda procesar el archivo, sino porque tiene que hacerlo como parte del estándar.

El estándar de lenguaje C dice que un archivo de origen que no está vacío debe terminar en un carácter de nueva línea, que no debe estar precedido inmediatamente por un carácter de barra inclinada invertida.

Como se trata de una cláusula “debe”, debemos emitir un mensaje de diagnóstico para una violación de esta regla.

Esto se encuentra en la sección 2.1.1.2 de la norma ANSI C 1989. Sección 5.1.1.2 de la norma ISO C 1999 (y probablemente también la norma ISO C 1990).

Referencia: el archivo de correo GCC / GNU .

Esta respuesta es un bash de respuesta técnica más que de opinión.

Si queremos ser puristas POSIX, definimos una línea como:

Una secuencia de cero o más caracteres no más un carácter de terminación .

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

Una línea incompleta como:

Una secuencia de uno o más caracteres no al final del archivo.

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

Un archivo de texto como:

Un archivo que contiene caracteres organizados en cero o más líneas. Las líneas no contienen caracteres NUL y ninguna puede exceder {LINE_MAX} bytes de longitud, incluido el carácter . Aunque POSIX.1-2008 no distingue entre archivos de texto y archivos binarios (consulte el estándar ISO C), muchas utilidades solo producen resultados predecibles o significativos cuando se trabaja en archivos de texto. Las utilidades estándar que tienen tales restricciones siempre especifican “archivos de texto” en sus secciones STDIN o INPUT FILES.

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

Una cadena como:

Una secuencia contigua de bytes terminada por e incluyendo el primer byte nulo.

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

A partir de esto, podemos deducir que el único momento en el que potencialmente encontraremos algún tipo de problema es tratar el concepto de línea de un archivo o archivo como un archivo de texto (ya que un archivo de texto es una organización de cero o más líneas, y una línea que sabemos debe terminar con una ).

Caso en punto: wc -l filename .

Del manual del wc leemos:

Una línea se define como una cadena de caracteres delimitados por un carácter .

¿Cuáles son las implicaciones para los archivos JavaScript, HTML y CSS de que se trata de archivos de texto ?

En navegadores, IDEs modernos y otras aplicaciones de front-end, no hay problemas al omitir EOL en EOF. Las aplicaciones analizarán los archivos correctamente. Tiene que ser porque no todos los sistemas operativos se ajustan al estándar POSIX, por lo que no sería práctico para las herramientas que no son del sistema operativo (por ejemplo, navegadores) manejar archivos de acuerdo con el estándar POSIX (o cualquier estándar de nivel de sistema operativo).

Como resultado, podemos estar relativamente seguros de que EOL en EOF no tendrá prácticamente ningún impacto negativo en el nivel de la aplicación, independientemente de si se ejecuta en un sistema operativo UNIX.

En este punto, podemos decir con confianza que omitir EOL en EOF es seguro cuando se trata con JS, HTML, CSS en el lado del cliente. En realidad, podemos afirmar que minimizar cualquiera de estos archivos, que no contiene , es seguro.

Podemos dar un paso más allá y decir que, en lo que respecta a NodeJS, tampoco puede adherirse al estándar POSIX, ya que puede ejecutarse en entornos no compatibles con POSIX.

¿Qué nos queda entonces? Herramientas a nivel del sistema.

Esto significa que los únicos problemas que pueden surgir son las herramientas que hacen un esfuerzo para adherir su funcionalidad a la semántica de POSIX (por ejemplo, la definición de una línea como se muestra en wc ).

Aun así, no todas las shells se adherirán automáticamente a POSIX. Bash, por ejemplo, no adopta el comportamiento POSIX por defecto. Hay un interruptor para habilitarlo: POSIXLY_CORRECT .

Un elemento de reflexión sobre el valor de EOL siendo : http://www.rfc-editor.org/EOLstory.txt

Permaneciendo en la pista de herramientas, para todos los propósitos prácticos, consideremos esto:

Trabajemos con un archivo que no tiene EOL. Al escribir esto, el archivo en este ejemplo es un JavaScript miniaturizado sin 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 el tamaño del archivo cat es exactamente la sum de sus partes individuales. Si la concatenación de archivos JavaScript es una preocupación para los archivos JS, la preocupación más adecuada sería iniciar cada archivo JavaScript con un punto y coma.

Como alguien más mencionó en este hilo: ¿qué pasaría si desea obtener dos archivos cuyo resultado se convierta en una sola línea en lugar de dos? En otras palabras, cat hace lo que se supone que debe hacer.

El man de cat solo menciona la lectura de entrada hasta EOF, no . Tenga en cuenta que el -n de cat también imprimirá una línea que no sea terminada (o una línea incompleta ) como una línea , ya que el recuento comienza en 1 (según el man ).

-n Número las líneas de salida, comenzando en 1.

Ahora que entendemos cómo POSIX define una línea , este comportamiento se vuelve ambiguo o realmente no conforme.

Comprender el propósito y el cumplimiento de una herramienta determinada ayudará a determinar qué tan importante es terminar los archivos con un EOL. En C, C ++, Java (JAR), etc. … algunos estándares dictarán una nueva línea para la validez; no existe tal estándar para JS, HTML, CSS.

Por ejemplo, en lugar de usar wc -l filename uno podría hacer awk '{x++}END{ print x}' filename , y estar seguro de que el éxito de la tarea no se ve amenazado por un archivo que queremos procesar que no escribimos ( por ejemplo, una biblioteca de terceros como la JS minificada que curlizamos d) – a menos que nuestra intención fuera contar líneas en el sentido de conformidad con POSIX.

Conclusión

Habrá muy pocos casos de uso de la vida real donde omitir EOL en EOF para ciertos archivos de texto como JS, HTML y CSS tendrá un impacto negativo, si es que lo hace. Si confiamos en que está presente, estamos restringiendo la confiabilidad de nuestras herramientas solo a los archivos que generamos y nos abrimos a posibles errores introducidos por archivos de terceros.

Moraleja de la historia: herramientas de ingeniería que no tienen la debilidad de confiar en EOL en EOF.

Siéntase libre de publicar casos de uso, ya que se aplican a JS, HTML y CSS, donde podemos examinar cómo la omisión de EOL tiene un efecto adverso.

Puede estar relacionado con la diferencia entre :

  • archivo de texto (se supone que cada línea termina en un final de línea)
  • archivo binario (no hay verdaderas “líneas” para hablar, y la longitud del archivo debe conservarse)

Si cada línea finaliza en un final de línea, esto evita, por ejemplo, que concatenar dos archivos de texto haga que la última línea de la primera carrera entre en la primera línea del segundo.

Además, un editor puede verificar si el archivo finaliza en un final de línea, lo guarda en su opción local ‘eol’ y lo usa al escribir el archivo.

Hace unos años (2005), muchos editores (ZDE, Eclipse, Scite, …) se “olvidaron” de la EOL final, que no fue muy apreciada .
No solo eso, sino que interpretaron ese EOL final incorrectamente, como ‘comenzar una nueva línea’, y en realidad comienzan a mostrar otra línea como si ya existiera.
Esto fue muy visible con un archivo de texto “adecuado” con un editor de texto de buen comportamiento como vim, en comparación con abrirlo en uno de los editores anteriores. Mostraba una línea adicional debajo de la última línea real del archivo. Ves algo como esto:

 1 first line 2 middle line 3 last line 4 

Algunas herramientas esperan esto. Por ejemplo, wc espera esto:

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

Básicamente hay muchos progtwigs que no procesarán los archivos correctamente si no obtienen el EOL EOL final.

GCC te advierte sobre esto porque se espera como parte del estándar C. (sección 5.1.1.2 aparentemente)

Advertencia de comstackción “No hay nueva línea al final del archivo”

Esto se origina desde los primeros días cuando se usaban terminales simples. El carácter de nueva línea se utilizó para desencadenar un “lavado” de los datos transferidos.

Hoy, ya no se requiere la charla nueva. Claro, muchas aplicaciones todavía tienen problemas si la nueva línea no está allí, pero consideraría un error en esas aplicaciones.

Sin embargo, si tiene un formato de archivo de texto donde necesita la nueva línea, obtendrá una verificación de datos simple muy económica: si el archivo finaliza con una línea que no tiene línea nueva al final, sabrá que el archivo está roto. Con solo un byte extra para cada línea, puede detectar archivos rotos con gran precisión y casi sin tiempo de CPU.

Un caso de uso por separado: cuando su archivo de texto está controlado por la versión (en este caso específicamente bajo git aunque también se aplica a otros). Si se agrega contenido al final del archivo, la línea que anteriormente era la última línea se habrá editado para incluir un carácter de nueva línea. Esto significa que blame la blame al archivo para saber cuándo se editó por última vez esa línea mostrará la adición de texto, no la confirmación anterior a la que realmente deseaba ver.

También hay un problema de progtwigción práctica con archivos que carecen de nuevas líneas al final: la read Bash incorporado (no sé sobre otras implementaciones de read ) no funciona como se esperaba:

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

Esto imprime solo foo ! La razón es que cuando read encuentra la última línea, escribe el contenido en $line pero devuelve el código de salida 1 porque llegó a EOF. Esto rompe el ciclo while, por lo que nunca llegamos a la parte echo $line . Si desea manejar esta situación, debe hacer lo siguiente:

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

Es decir, haga el echo si la read falló debido a una línea no vacía al final del archivo. Naturalmente, en este caso habrá una nueva línea adicional en la salida que no estaba en la entrada.

Presumiblemente simplemente que algún código de análisis esperaba que estuviera allí.

No estoy seguro de que lo considere una “regla”, y ciertamente no es algo a lo que me adhiera religiosamente. El código más sensible sabrá cómo analizar texto (incluidas las codificaciones) línea por línea (cualquier elección de terminaciones de línea), con o sin línea nueva en la última línea.

De hecho, si finaliza con una nueva línea: ¿existe (en teoría) una línea final vacía entre la EOL y la EOF? Uno para reflexionar …

Además de las razones prácticas anteriores, no me sorprendería si los creadores de Unix (Thompson, Ritchie, et al.) O sus predecesores de Multics se dieran cuenta de que hay una razón teórica para usar terminadores de línea en lugar de separadores de línea: con línea terminadores, puede codificar todos los archivos posibles de líneas. Con los separadores de línea, no hay diferencia entre un archivo de líneas cero y un archivo que contiene una sola línea vacía; ambos están codificados como un archivo que contiene cero caracteres.

Entonces, las razones son:

  1. Porque esa es la forma en que POSIX lo define.
  2. Porque algunas herramientas lo esperan o “se portan mal” sin él. Por ejemplo, wc -l no contará una “línea” final si no termina con una nueva línea.
  3. Porque es simple y conveniente. En Unix, el cat simplemente funciona y funciona sin complicaciones. Simplemente copia los bytes de cada archivo, sin necesidad de interpretación. No creo que haya un DOS equivalente a un cat . El uso de copy a+bc terminará fusionando la última línea del archivo a con la primera línea del archivo b .
  4. Porque un archivo (o secuencia) de líneas cero se puede distinguir de un archivo de una línea vacía.

¿Por qué los archivos (de texto) deben terminar con una nueva línea?

Como bien lo expresson muchos, porque:

  1. Muchos progtwigs no se comportan bien o fallan sin él.

  2. Incluso los progtwigs que manejan bien un archivo carecen de una terminación '\n' , la funcionalidad de la herramienta puede no cumplir con las expectativas del usuario, lo que puede no ser claro en este caso de esquina.

  3. Los progtwigs rara vez rechazan la final '\n' (no sé de ninguna).


Sin embargo, esto plantea la siguiente pregunta:

¿Qué debería hacer el código sobre los archivos de texto sin una nueva línea?

  1. Lo más importante: no escriba código que asum que un archivo de texto termina con una nueva línea . Asumir que un archivo se ajusta a un formato conduce a la corrupción de datos, ataques de hackers y lockings. Ejemplo:

     // 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. Si se necesita el '\n' final, advierta al usuario sobre su ausencia y las medidas tomadas. IOWs, valida el formato del archivo. Nota: Esto puede incluir un límite a la longitud de línea máxima, encoding de caracteres, etc.

  3. Definir claramente, documentar, el manejo del código de una final faltante '\n' .

  4. No genere , como sea posible, un archivo que carece de la terminación '\n' .

Me lo he preguntado por años. Pero encontré una buena razón hoy.

Imagine un archivo con un registro en cada línea (por ejemplo, un archivo CSV). Y que la computadora estaba escribiendo registros al final del archivo. Pero de repente se estrelló. ¿Gee fue la última línea completa? (no es una buena situación)

Pero si siempre terminamos la última línea, entonces lo sabríamos (simplemente verifique si la última línea ha finalizado). De lo contrario, probablemente tendremos que descartar la última línea cada vez, solo para estar seguros.

Siempre tuve la impresión de que la regla venía de los días en que era difícil analizar un archivo sin una nueva línea final. Es decir, terminarías escribiendo código donde el carácter EOL o EOF definía un final de línea. Era simplemente más simple asumir que una línea terminaba con EOL.

Sin embargo, creo que la regla se deriva de los comstackdores de C que requieren la nueva línea. Y como se señala en la advertencia del comstackdor “No hay nueva línea al final del archivo” , #include no agregará una nueva línea.

Imagine que el archivo se está procesando mientras el archivo aún está siendo generado por otro proceso.

Podría tener que ver con eso? Una bandera que indica que el archivo está listo para ser procesado.

Personalmente me gustan las nuevas líneas al final de los archivos de código fuente.

Puede tener su origen en Linux o en todos los sistemas UNIX para el caso. Recuerdo errores de comstackción (gcc si no me equivoco) porque los archivos de código fuente no terminaron con una nueva línea vacía. ¿Por qué fue hecho de esta manera uno se queda maravillado?

En mi humilde opinión, es una cuestión de estilo personal y opinión.

En los viejos tiempos, no puse esa nueva línea. Un personaje guardado significa más velocidad a través de ese módem de 14.4K.

Más tarde, puse esa nueva línea para que sea más fácil seleccionar la línea final usando shift + downarrow.