¿Vi agrega silenciosamente una nueva línea (LF) al final del archivo?

36

Tengo problemas para entender un comportamiento extraño: vi parece agregar una nueva línea (ASCII: LF, ya que es un sistema Unix ( AIX )) al final del archivo, cuando NO lo escribí específicamente.

Edito el archivo como tal en vi (teniendo cuidado de no ingresar una nueva línea al final):

# vi foo   ## Which I will finish on the char "9" and not input a last newline, then `:wq`
123456789
123456789
123456789
123456789
~
~
  ## When I save, the cursor is just above the last "9", and no newline was added.

Espero que vi lo guarde "como está", para tener 39 bytes: 10 caracteres ASCII en cada una de las tres primeras líneas (números 1 a 9, seguidos de una nueva línea (LF en mi sistema)) y solo 9 en la última línea (caracteres 1 a 9, sin línea nueva terminada / LF).

Pero aparece cuando lo guardo es de 40 bytes (en lugar de 39), y od muestra un LF final :

# wc foo
       4       4      40 foo  ## I expected 39 here! as I didn't add the last newline
# od -a toto
0000000    1   2   3   4   5   6   7   8   9  lf   1   2   3   4   5   6
0000020    7   8   9  lf   1   2   3   4   5   6   7   8   9  lf   1   2
0000040    3   4   5   6   7   8   9  lf
0000050
     ## An "lf" terminates the file?? Did vi add it silently?

Si creo el archivo con un printf haciendo exactamente lo que hice dentro de vi, funciona como se esperaba:

# ## I create a file with NO newline at the end:
# printf "123456789\n123456789\n123456789\n123456789" > foo2
# wc foo2  ## This one is as expected: 39 bytes, exactly as I was trying to do above with vi.
       3       4      39 foo  ## As expected, as I didn't add the last newline

  ## Note that for wc, there are only three lines!
  ## (So wc -l doesn't count lines; it counts the [newline] chars... Which is rather odd.)

# root@SPU0WMY1:~  ## od -a foo2
0000000    1   2   3   4   5   6   7   8   9  lf   1   2   3   4   5   6
0000020    7   8   9  lf   1   2   3   4   5   6   7   8   9  lf   1   2
0000040    3   4   5   6   7   8   9
0000047                                ## As expected, no added LF.

Ambos archivos (foo (40 caracteres) y foo2 (39 caracteres) aparecen exactamente iguales si los vuelvo a abrir con vi ...

Y si abro foo2 (39 caracteres, sin nueva línea de terminación) en vi y simplemente lo hago :wqsin editarlo , dice que escribe 40 caracteres, ¡y aparece el salto de línea!

No puedo tener acceso a un vi más reciente (lo hago en AIX, vi (no en Vim ) versión 3.10, creo? (No "versión" u otro medio para saberlo)).

# strings /usr/bin/vi | grep -i 'version.*[0-9]'
@(#) Version 3.10

¿Es normal que vi (y tal vez no en una versión más reciente o Vim) agregue en silencio una nueva línea al final de un archivo? (Pensé que ~ indicaba que la línea anterior NO terminaba con una nueva línea).

-

Editar: algunas actualizaciones adicionales y un poco de resumen, con un gran agradecimiento a las respuestas a continuación:

  • vi agrega silenciosamente una nueva línea final en el momento en que escribe un archivo que le faltaba (a menos que el archivo esté vacío).

  • ¡solo lo hace al momento de escribir! (es decir, hasta que: w, puede usar: e para verificar que el archivo todavía está tal como lo abrió ... (es decir: todavía muestra "nombre de archivo" [Última línea no está completa] Línea N, carácter M). Cuando guarda, se agrega una nueva línea de forma silenciosa, sin una advertencia específica (dice cuántos bytes guarda, pero en la mayoría de los casos esto no es suficiente para saber que se agregó una nueva línea) (gracias a @jiliagre por hablar conmigo sobre el abriendo el mensaje vi, me ayudó a encontrar una manera de saber cuándo ocurre realmente el cambio)

  • ¡Esta (corrección silenciosa) es un comportamiento POSIX ! (ver la respuesta @ barefoot-io para referencias)

Olivier Dulac
fuente
Solo para completar, qué versión de AIX (versión completa).
EightBitTony
2
No estoy al tanto de que vi de AIX tenga esta opción - aparece solo vim
Jeff Schaller
1
@JeffSchaller: gracias por el enlace. Desafortunadamente, el vi nativo no tiene ": set noeol" ni siquiera la opción -b para abrir en modo binario ...
Olivier Dulac
1
Es posible que pueda obtener la viversión o al menos una pista sobre su origen ejecutando el :vecomando.
jlliagre
1
@ThomasDickey De hecho. Por alguna razón, IBM eliminó la expágina del manual donde :vernormalmente se documenta el comando.
jlliagre

Respuestas:

28

Este es el vicomportamiento esperado .

Su archivo tiene una última línea incompleta, por lo que, estrictamente hablando (es decir, de acuerdo con el estándar POSIX), no es un archivo de texto sino un archivo binario.

vi que es un editor de archivos de texto, no binario, lo arregla con gracia cuando lo guarda.

Esto permite que otras herramientas de archivos de texto, como Me gusta wc, sedproporcionen el resultado esperado. Tenga en cuenta que vino es silencioso sobre el problema:


$ printf "one\ntwo" >file     # Create a unterminated file
$ cat file                    # Note the missing newline before the prompt
one
two$ wc -l file               # wc ignores the incomplete last line
       1 file
$ sed '' file > file1
$ cat file1                   # so does a legacy sed
one
$ PATH=$(getconf PATH) sed  '' file
one                           # while a POSIX conformant sed warns you:
sed: Missing newline at end of file file.
two
$ vi file
one
two
~
~
~                             # vi tells you too about the issue
"file" [Incomplete last line] 2 lines, 7 characters

:w

"file" 2 lines, 8 characters  # and tells it writes two lines
                              # You'll even notice it writes one more
                              # character if you are a very shrewd observer :-)
:q
$ cat file                    # the file is now valid text
one
two
$ wc -l file                  # wc reports the expected number of lines
       2 file
$ sed '' file > file1         # sed works as expected
$ cat file1
one
two

Tenga en cuenta que para obtener algunas pistas sobre la viversión que está ejecutando, puede usar el :vecomando. Aquí se muestra que estoy usando un SVR4 heredado, definitivamente no vim:

:ve
Version SVR4.0, Solaris 2.5.0

Aparentemente, el tuyo dice:

:ve
Version 3.10

Eso probablemente significa que AIX vise basa en el código fuente SVR3.

En cualquier caso, este comportamiento y el [Incomplete last line] mensaje de advertencia han estado en el vicódigo fuente heredado de Bill Joy desde al menos 1979 y AFAIK, retenido en todas las ramas creadas a partir de las versiones del código fuente del Sistema V, a partir de las cuales se construyeron Unix como AIX.

Cronológicamente hablando, este comportamiento no es consecuencia de la conformidad con POSIX, sino más bien una consecuencia de la decisión original de Bill Joy de ayudar a los usuarios a editar archivos de texto falsos, y luego, una década después, la decisión del comité POSIX de mantener esta tolerancia.

Si usa en edlugar de vi, notará que el primero es más detallado sobre el problema, al menos si edes de SVR3 o de una rama fuente más nueva:

$ ed file
'\n' appended
8
q

Tenga en cuenta también que un archivo vacío es un archivo de texto válido que contiene cero líneas. Como no hay una línea sin terminar que corregir, vino agrega una nueva línea al guardar el archivo.

jlliagre
fuente
1
Creo que confunde vim con vi;) legacy vi es mucho menos detallado que esto ...
Olivier Dulac
@ OlivierDulac No los estoy confundiendo. Esta prueba se realizó utilizando el legado SVR4 vial igual que el OP, aunque en un Unix diferente. Este no es vimni otro clon. Respuesta actualizada para aclarar esto.
jlliagre
@ OlivierDulac Hmm, acabo de notar que en realidad eres el OP. Parece que AIX está utilizando una rama de System V anterior para su viimplementación. Posiblemente SVR3. ¿Estás seguro de que no hay ningún [Incomplete last line]mensaje cuando abres el archivo?
jlliagre
@OlivierDulac Este enlace parece implicar que este mismo mensaje puede mostrarse mediante la viimplementación de AIX : www-01.ibm.com/support/docview.wss?uid=isg1IZ27694
jlliagre
Trataré de ver esto mañana
Olivier Dulac
51

POSIX requiere este comportamiento, por lo que no es de ninguna manera inusual.

Del manual POSIX vi :

ARCHIVOS DE ENTRADA

Consulte la sección ARCHIVOS DE ENTRADA del comando ex para obtener una descripción de los archivos de entrada admitidos por el comando vi.

Siguiendo el camino hacia el manual POSIX ex :

ARCHIVOS DE ENTRADA

Los archivos de entrada serán archivos de texto o archivos que serían archivos de texto, excepto por una última línea incompleta que no tenga más de {LINE_MAX} -1 bytes de longitud y no contenga caracteres NUL. Por defecto, cualquier última línea incompleta se tratará como si tuviera una <línea nueva> final. La edición de otras formas de archivos puede ser permitida opcionalmente por implementaciones ex.

La sección ARCHIVOS DE SALIDA del manual vi también redirige a ex:

ARCHIVOS DE SALIDA

La salida de ex serán archivos de texto.

Un par de definiciones POSIX:

3.397 Archivo de texto

Un archivo que contiene caracteres organizados en cero o más líneas. Las líneas no contienen caracteres NUL y ninguna puede superar los {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.

Línea 3.206

Una secuencia de cero o más caracteres no <newline> más un carácter <newline> final.

Estas definiciones en el contexto de estos extractos de página del manual significan que si bien una implementación ex / vi conforme debe aceptar un archivo de texto con formato incorrecto si la única deformidad de ese archivo es una nueva línea final ausente, al escribir el búfer de ese archivo, el resultado debe ser un archivo de texto válido.

Si bien esta publicación hace referencia a la edición 2013 del estándar POSIX, las estipulaciones relevantes también aparecen en la edición mucho más antigua de 1997 .

Por último, si encuentra que la apariencia de línea nueva de su ex no es bienvenida, se sentirá profundamente violado por la edición intolerante de UNIX (1979) de la Séptima Edición. Del manual :

Al leer un archivo, ed descarta los caracteres ASCII NUL y todos los caracteres después de la última línea nueva. Se niega a leer archivos que contengan caracteres no ASCII.

IO descalzo
fuente
gracias, eso contesta mi pregunta. Solo esperaré unos días más en caso de que se mejore la respuesta, pero en este momento siento que puede ser la respuesta aceptada.
Olivier Dulac
¡Muy bien hecho en la respuesta bien documentada, directamente de las especificaciones! :)
Comodín
1
@Wildcard, sin embargo, el comportamiento precedió a las especificaciones.
jlliagre
@jlliagre, a menos que tenga una memoria de Bill Joy o tal vez el creador de ex(no sé su nombre), creo que las especificaciones POSIX son tan buenas como se puede esperar. ;) Más cerca de la "fuente original" en este punto, aunque es cierto que comenzaron como más o menos descripciones de la funcionalidad existente.
Comodín
3
@Wildcard exfue co-escrito por Bill Joy y Chuck Alley ( web.cecs.pdx.edu/~kirkenda/joy84.html .) No cuestiono las especificaciones POSIX y el hecho de que las viversiones actuales lo siguen, solo declaro el comportamiento mucho tiempo lo precede.
jlliagre
1

No recuerdo ningún otro comportamiento que se agregue una nueva línea al final de un archivo ( videsde mediados de los 80).

El ~indica que una línea en la pantalla que no es parte del texto, no que el archivo no termine en una nueva línea. (Puede ser difícil rastrear errores si coloca un ~en la última línea de scripts de shell). Si carga un archivo corto con una nueva línea al final, lo verá ~usted mismo y refutará que su pensamiento indica que el texto no termina con una nueva línea.

Anthon
fuente
lo que me sorprende es la adición de una nueva línea ... Espero que vi no la agregue en silencio, pero parece que sí ... Estoy buscando una explicación de esta actitud (el hecho preocupante es: abro foo2 (sin LF) y solo: wq, CAMBIA su contenido ... así que me muestra algo pero guarda otra cosa ... raro, por decir lo menos ^^
Olivier Dulac
en su predecesor ( ed) crearía líneas y las editaría, no agregando caracteres. Siempre pensé en vi como un editor orientado a líneas también. Pero entiendo tu sorpresa.
Anthon
1

El texto que carece incorrectamente de la última línea nueva ejecutada a través de un whilebucle de shell da como resultado que la última línea se descarte silenciosamente.

$ (echo transaction 1; echo -n transaction 2) \
  | while read line; do echo $line; done
transaction 1
$ 

Asegurarse de que haya una nueva línea definitiva es el valor predeterminado correcto, correcto y correcto. La otra opción implica conocer y tener tiempo para auditar todo el código de shell que toca el texto que carece de la nueva línea final, o arriesgarse a perder la última línea del texto.

thrig
fuente