Supongo que todos aquí están familiarizados con el dicho de que todos los archivos de texto deben terminar con una nueva línea. Hace años que conozco esta "regla", pero siempre me he preguntado: ¿por qué?
Supongo que todos aquí están familiarizados con el dicho de que todos los archivos de texto deben terminar con una nueva línea. Hace años que conozco esta "regla", pero siempre me he preguntado: ¿por qué?
Respuestas:
Porque así es como el estándar POSIX define una línea :
- Línea 3.206
- Una secuencia de cero o más caracteres que no son <newline> más un carácter <newline> que termina.
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 programas tienen problemas para procesar la última línea de un archivo si no se termina la nueva línea.
Hay al menos una gran ventaja de esta guía cuando se trabaja en un emulador de terminal: 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 {a,b,c}.txt
foo
barbaz
Y, como el ejemplo anterior también demuestra, cuando se muestra el archivo en la línea de comando (por ejemplo, a través de more
), un archivo terminado en nueva línea da como resultado una visualización correcta. Un archivo terminado incorrectamente puede ser ilegible (segunda línea).
Para mantener la coherencia, es muy útil seguir esta regla; de lo contrario, se generará un trabajo adicional al tratar con las herramientas predeterminadas de Unix.
Piénselo de manera diferente: si las líneas no terminan con nueva línea, hacer comandos como cat
útiles es mucho más difícil: ¿cómo se hace un comando para concatenar archivos de manera que
b.txt
y c.txt
?Por supuesto, esto es solucionable, pero necesita hacer que el uso sea cat
más complejo (agregando argumentos de línea de comando posicional, por ejemplo cat a.txt --no-newline b.txt c.txt
), y ahora el comando en lugar de cada archivo individual controla cómo se pega junto con otros archivos. Esto es casi seguro que no es conveniente.
... O necesita introducir un carácter centinela especial para marcar una línea que se supone que debe continuar en lugar de terminar. Bueno, ahora está atrapado en la misma situación que en POSIX, excepto invertido (carácter de continuación de línea en lugar de carácter de terminación de línea).
Ahora, en los sistemas que no son compatibles con POSIX (hoy en día es principalmente Windows), el punto es discutible: los archivos generalmente no terminan en una nueva línea, y la definición (informal) de una línea podría ser, por ejemplo, "texto separado por nuevas líneas" (tenga en cuenta el énfasis). Esto es completamente válido. Sin embargo, para los datos estructurados (por ejemplo, el código de programación) hace que el análisis sea mínimamente más complicado: generalmente significa que los analizadores tienen que 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 tokens en lugar del analizador; en otras palabras, agregue un token de "nueva línea artificial" al final de la entrada.
cat
de una manera que sea útil y consistente.
Cada línea debe terminar en un carácter de nueva línea, incluida la última. Algunos programas tienen problemas para procesar la última línea de un archivo si no se termina la nueva línea.
GCC lo advierte no porque no pueda procesar el archivo, sino porque debe hacerlo como parte del estándar.
El estándar del lenguaje C dice que un archivo fuente que no está vacío terminará en un carácter de nueva línea, que no estará precedido inmediatamente por un carácter de barra diagonal inversa.
Como se trata de una cláusula "deberá", debemos emitir un mensaje de diagnóstico por una violación de esta regla.
Esto se encuentra en la sección 2.1.1.2 del estándar 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 .
wc -l
no contará la última línea de un archivo si no se termina la nueva línea. Además, cat
unirá la última línea de un archivo con la primera línea del siguiente archivo en una sola si la última línea del primer archivo no tiene una nueva línea terminada. Casi cualquier programa que busque nuevas líneas como delimitador tiene el potencial de estropear esto.
wc
ha sido ya mencionado ....
cat
y wc
)?
Esta respuesta es un intento de una respuesta técnica en lugar de una opinión.
Si queremos ser puristas de POSIX, definimos una línea como:
Una secuencia de cero o más caracteres que no son <newline> más un carácter <newline> que termina.
Fuente: https://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 que no son <newline> al final del archivo.
Fuente: https://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 ninguno puede exceder {LINE_MAX} bytes de longitud, incluido el carácter <newline>. 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 operan 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: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397
Una cadena como:
Una secuencia contigua de bytes terminados por e incluyendo el primer byte nulo.
Fuente: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396
A partir de esto, podemos deducir que la única vez que potencialmente encontraremos algún tipo de problema es si tratamos con el concepto de una línea de un archivo o un 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 conocemos debe terminar con una <nueva línea>).
El caso en cuestión: wc -l filename
.
Del wc
manual de 'leemos:
Una línea se define como una cadena de caracteres delimitada por un carácter <línea nueva>.
¿Cuáles son las implicaciones para los archivos JavaScript, HTML y CSS, ya que son archivos de texto ?
En los navegadores, IDEs modernos y otras aplicaciones front-end no hay problemas para omitir EOL en EOF. Las aplicaciones analizarán los archivos correctamente. Tiene que hacerlo ya que no todos los sistemas operativos cumplen con el estándar POSIX, por lo que no sería práctico que las herramientas que no sean OS (por ejemplo, navegadores) manejen archivos de acuerdo con el estándar POSIX (o cualquier estándar de nivel OS).
Como resultado, podemos estar relativamente seguros de que EOL en EOF prácticamente no tendrá un impacto negativo a nivel de aplicación, independientemente de si se está ejecutando en un sistema operativo UNIX.
En este punto, podemos decir con confianza que omitir EOL en EOF es seguro cuando se trata de JS, HTML, CSS en el lado del cliente. En realidad, podemos afirmar que es seguro minificar cualquiera de estos archivos, que no contengan <newline>.
Podemos ir 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? Sistema de herramientas a nivel.
Esto significa que los únicos problemas que pueden surgir son las herramientas que hacen un esfuerzo por adherir su funcionalidad a la semántica de POSIX (por ejemplo, la definición de una línea como se muestra en la figura wc
).
Aun así, no todos los shells se adherirán automáticamente a POSIX. Bash, por ejemplo, no tiene el comportamiento POSIX predeterminado. Hay un interruptor que le permita: POSIXLY_CORRECT
.
Alimento para reflexionar sobre el valor de EOL <newline>: https://www.rfc-editor.org/old/EOLstory.txt
Mantenerse en la pista de herramientas, para todos los propósitos prácticos, consideremos esto:
Trabajemos con un archivo que no tiene EOL. Al momento de escribir esto, el archivo en este ejemplo es un JavaScript minimizado 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 cat
tamaño del archivo es exactamente la suma de sus partes individuales. Si la concatenación de archivos JavaScript es una preocupación para los archivos JS, la preocupación más apropiada sería comenzar cada archivo JavaScript con un punto y coma.
Como alguien más mencionó en este hilo: ¿qué cat
pasa si desea dos archivos cuya salida se convierte 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
sólo menciona la lectura de entrada hasta EOF, no <nueva línea>. Tenga en cuenta que el -n
cambio de cat
también imprimirá una línea terminada no <newline> (o línea incompleta ) como una línea , ya que el recuento comienza en 1 (de acuerdo con el man
.)
-n Numera 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 finalizar los archivos con una EOL. En C, C ++, Java (JAR), etc., algunos estándares dictarán una nueva línea de validez; no existe dicho estándar para JS, HTML, CSS.
Por ejemplo, en lugar de usar wc -l filename
uno podría hacerlo awk '{x++}END{ print x}' filename
, y puede estar seguro de que el éxito de la tarea no se ve comprometido por un archivo que queremos procesar que no escribimos (por ejemplo, una biblioteca de terceros como el JS minificado que curl
d), a menos que nuestro la intención era realmente contar líneas en el sentido compatible con POSIX.
Conclusión
Habrá muy pocos casos de uso de la vida real en los que omitir EOL en EOF para ciertos archivos de texto como JS, HTML y CSS tendrá un impacto negativo, si es que lo tiene. Si confiamos en que <newline> esté presente, estamos restringiendo la confiabilidad de nuestras herramientas solo a los archivos que creamos 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.
No dude en publicar casos de uso, ya que se aplican a JS, HTML y CSS, donde podemos examinar cómo omitir EOL tiene un efecto adverso.
Puede estar relacionado con la diferencia entre :
Si cada línea termina en un final de línea, esto evita, por ejemplo, que la concatenación de dos archivos de texto haría que la última línea de la primera ejecución se convirtiera en la primera línea de la segunda.
Además, un editor puede verificar en la carga si el archivo termina 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, ...) "olvidaron" esa EOL final, que no fue muy apreciada .
No solo eso, sino que interpretaron esa EOL final de manera incorrecta, como 'comenzar una nueva línea', y en realidad comienzan a mostrar otra línea como si ya existiera.
Esto era muy visible con un archivo de texto 'adecuado' con un editor de texto con buen comportamiento como vim, en comparación con abrirlo en uno de los editores anteriores. Mostraba una línea extra 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
wc
no espera esto, ya que simplemente está trabajando dentro de la definición POSIX de una "línea" en lugar de la comprensión intuitiva de la "línea" de la mayoría de las personas.
wc -l
imprimir 1
en ambos casos, pero algunas personas podrían decir que el segundo caso debería imprimir 2
.
\n
en un terminador de línea, en lugar de un separador de línea, como lo hace POSIX / UNIX, entonces esperar que el segundo caso imprima 2 es absolutamente una locura.
Básicamente, hay muchos programas que no procesarán los archivos correctamente si no obtienen el EOL EOF final.
GCC le advierte sobre esto porque se espera como parte del estándar C. (sección 5.1.1.2 aparentemente)
Advertencia del compilador "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 'vaciado' de los datos transferidos.
Hoy, el carácter de nueva línea ya no es necesario. Claro, muchas aplicaciones todavía tienen problemas si la nueva línea no está allí, pero lo consideraría un error en esas aplicaciones.
Sin embargo, si tiene un formato de archivo de texto en el que necesita la nueva línea, obtendrá una verificación de datos simple muy barata: si el archivo termina con una línea que no tiene una nueva línea al final, sabe que el archivo está roto. Con solo un byte adicional para cada línea, puede detectar archivos rotos con alta precisión y casi sin tiempo de CPU.
Un caso de uso 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
al buscar el archivo para saber cuándo se editó esa línea por última vez, se mostrará la adición de texto, no la confirmación antes de lo que realmente quería ver.
\n
). Problema resuelto.
Además de las razones prácticas anteriores, no me sorprendería si los creadores de Unix (Thompson, Ritchie, et al.) O sus predecesores 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 de líneas posibles. 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:
wc -l
no contará una "línea" final si no termina con una nueva línea.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 cat
. El uso copy a+b c
terminará fusionando la última línea de archivo a
con la primera línea de archivo b
.Me lo he preguntado por años. Pero me encontré con 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 sabríamos (simplemente verifique si la última línea está terminada). De lo contrario, probablemente tendríamos que descartar la última línea cada vez, solo para estar seguros.
Presumiblemente simplemente que algún código de análisis esperaba que estuviera allí.
No estoy seguro de considerarlo una "regla", y ciertamente no es algo a lo que me adhiera religiosamente. El código más sensible sabrá cómo analizar el texto (incluidas las codificaciones) línea por línea (cualquier elección de terminaciones de línea), con o sin una nueva línea en la última línea.
De hecho, si termina con una nueva línea: ¿hay (en teoría) una línea final vacía entre la EOL y la EOF? Uno para reflexionar ...
También hay un problema práctico de programación con archivos que carecen de nuevas líneas al final: el read
Bash incorporado (no sé sobre otras read
implementaciones) no funciona como se esperaba:
printf $'foo\nbar' | while read line
do
echo $line
done
¡Esto solo sefoo
imprime ! La razón es que cuando read
encuentra la última línea, escribe el contenido $line
pero devuelve el código de salida 1 porque llegó a EOF. Esto rompe el while
ciclo, por lo que nunca llegamos a la echo $line
parte. 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 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.
¿Por qué los archivos (de texto) deben terminar con una nueva línea?
Como bien expresado por muchos, porque:
Muchos programas no se comportan bien o fallan sin él.
Incluso los programas que manejan bien un archivo carecen de un final '\n'
, la funcionalidad de la herramienta puede no cumplir con las expectativas del usuario, lo que puede no estar claro en este caso de esquina.
Los programas rara vez rechazan el final '\n'
(no sé de ninguno).
Sin embargo, esto plantea la siguiente pregunta:
¿Qué debe hacer el código sobre los archivos de texto sin una nueva línea?
Lo más importante: no escriba código que suponga 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 piratas informáticos y bloqueos. 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
...
}
Si '\n'
se necesita el seguimiento final , alertar al usuario sobre su ausencia y la acción tomada. IOW, valide el formato del archivo. Nota: Esto puede incluir un límite a la longitud máxima de línea, codificación de caracteres, etc.
Definir claramente, documentar, el manejo del código de un final faltante '\n'
.
No, como sea posible, genere un archivo que carece del final '\n'
.
Es muy tarde aquí, pero me enfrenté a un error en el procesamiento de archivos y eso ocurrió porque los archivos no terminaban con una nueva línea vacía. Estábamos procesando archivos de texto sed
y sed
omitimos la última línea de la salida, lo que causaba una estructura json no válida y enviaba el resto del proceso a un estado fallido.
Todo lo que estábamos haciendo era:
Hay un archivo de muestra que dice: foo.txt
con algún json
contenido dentro.
[{
someProp: value
},
{
someProp: value
}] <-- No newline here
El archivo fue creado en la máquina de viudas y las secuencias de comandos de la ventana procesaban ese archivo usando los comandos de PowerShell. Todo bien.
Cuando procesamos el mismo archivo usando el sed
comandosed 's|value|newValue|g' foo.txt > foo.txt.tmp
El archivo recién generado fue
[{
someProp: value
},
{
someProp: value
y boom, falló el resto de los procesos debido al JSON inválido.
Por lo tanto, siempre es una buena práctica finalizar su archivo con una nueva línea vacía.
Siempre tuve la impresión de que la regla venía de cuando era difícil analizar un archivo sin una nueva línea final. Es decir, terminaría escribiendo código donde el final de línea fue definido por el carácter EOL o EOF. Era más simple asumir que una línea terminaba con EOL.
Sin embargo, creo que la regla se deriva de los compiladores de C que requieren la nueva línea. Y como se señaló en la advertencia del compilador "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 sigue 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 con Linux o todos los sistemas UNIX para el caso. Recuerdo que hubo errores de compilación (gcc si no me equivoco) porque los archivos de código fuente no terminaron con una nueva línea vacía. ¿Por qué se hizo de esta manera? Uno debe preguntarse.
En mi humilde opinión, es una cuestión de estilo personal y opinión.
En los viejos tiempos, no ponía 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.