Добавляет ли vi молча символ новой строки (LF) в конец файла?

Добавляет ли vi молча символ новой строки (LF) в конец файла?

Мне трудно понять странное поведение: vi, похоже, добавляет новую строку (ASCII: LF, поскольку это Unix (ЭКС) system) в конце файла, хотя я НЕ вводил это специально.

Я редактирую файл следующим образом в vi (стараясь не вводить символ новой строки в конце):

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

Я ожидаю, что vi сохранит его «как есть», то есть у меня будет 39 байт: 10 символов ASCII в каждой из первых трех строк (цифры от 1 до 9, за которыми следует символ новой строки (LF в моей системе)) и только 9 в последней строке (символы от 1 до 9, без завершающего символа новой строки/LF).

Но когда я его сохраняю, он появляется40байт (вместо 39), иod показывает завершающий LF:

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

Если я создам файл с printf, выполняющим точно то же, что я делал в vi, то он будет работать так, как и ожидалось:

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

Оба файла (foo (40 символов) и foo2 (39 символов) выглядят абсолютно одинаково, если я снова открываю их с помощью vi...

А если я открою foo2 (39 символов, без завершающего символа новой строки) в vi ипросто сделайте это :wqбез какого-либо редактирования, он говорит, что пишет 40 символов, и появляется перевод строки!

У меня нет доступа к более новой версии vi (я делаю это на AIX, vi (неВим) версия 3.10, я думаю? (без "-version" или других способов узнать это)).

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

Нормально ли для vi (а может быть, и для более поздних версий? Или для Vim?) молча добавлять новую строку в конец файла? (Я думал, что ~ указывает на то, что предыдущая строка НЕ ​​заканчивается новой строкой.)

--

Редактировать:некоторые дополнительные обновления и небольшое резюме, с большой благодарностью за ответы ниже:

  • vi молча добавляет завершающий символ новой строки в момент записи файла, в котором он отсутствует (если только файл не пуст).

  • он делает это только во время записи! (т. е., пока вы не :w, вы можете использовать :e, чтобы убедиться, что файл все еще в том же состоянии, в котором вы его открыли... (т. е. он все еще показывает "filename" [Последняя строка не завершена] строка N, символ M). Когда вы сохраняете, новая строка добавляется молча, без специального предупреждения (он сообщает, сколько байтов он сохраняет, но в большинстве случаев этого недостаточно, чтобы узнать, что была добавлена ​​новая строка) (спасибо @jiliagre за то, что рассказал мне об открытии сообщения vi, это помогло мне найти способ узнать, когда действительно происходит изменение)

  • Это (тихое исправление)POSIXповедение! (см. ответ @barefoot-io для ссылок)

решение1

POSIX требует такого поведения, так что в этом нет ничего необычного.

ИзРуководство по POSIX vi:

ВХОДНЫЕ ФАЙЛЫ

Описание входных файлов, поддерживаемых командой vi, см. в разделе ВХОДНЫЕ ФАЙЛЫ команды ex.

Следуя по тропе кPOSIX ex руководство:

ВХОДНЫЕ ФАЙЛЫ

Входные файлы должны быть текстовыми файлами или файлами, которые были бы текстовыми файлами, за исключением неполной последней строки, длина которой не превышает {LINE_MAX}-1 байт и которая не содержит символов NUL. По умолчанию любая неполная последняя строка должна рассматриваться так, как если бы она имела завершающий символ <newline>. Редактирование других форм файлов может быть опционально разрешено реализациями ex.

Раздел ВЫХОДНЫЕ ФАЙЛЫ руководства по vi также перенаправляет на ex:

ВЫХОДНЫЕ ФАЙЛЫ

Выходные данные ex должны представлять собой текстовые файлы.

Пара определений POSIX:

3.397 Текстовый файл

Файл, содержащий символы, организованные в ноль или более строк. Строки не содержат символов NUL, и ни одна из них не может превышать {LINE_MAX} байт в длину, включая символ <newline>. Хотя POSIX.1-2008 не различает текстовые файлы и двоичные файлы (см. стандарт ISO C), многие утилиты выдают предсказуемый или осмысленный вывод только при работе с текстовыми файлами. Стандартные утилиты, имеющие такие ограничения, всегда указывают «текстовые файлы» в своих разделах STDIN или INPUT FILES.

3.206 Линия

Последовательность из нуля или более символов, не являющихся символами новой строки, плюс завершающий символ новой строки.

Эти определения в контексте этих отрывков из страниц руководства означают, что в то время как совместимая реализация ex/vi должна принимать некорректный текстовый файл, если единственным дефектом этого файла является отсутствие завершающего символа новой строки, при записи буфера этого файла результатом должен быть допустимый текстовый файл.

Хотя в этой статье упоминается стандарт POSIX версии 2013 года,соответствующие положения также присутствуют в гораздо более старом издании 1997 года.

Наконец, если вы считаете добавление новой строки ex нежелательным, вы почувствуете себя глубоко оскорбленным нетерпимым ed седьмого издания UNIX (1979).Из руководства:

При чтении файла ed отбрасывает символы ASCII NUL и все символы после последнего символа новой строки. Он отказывается читать файлы, содержащие не-ASCII символы.

решение2

Это ожидаемое viповедение.

В вашем файле имеется неполная последняя строка, поэтому, строго говоря (т.е. согласно стандарту POSIX), это не текстовый файл, а двоичный файл.

viкоторый является текстовым редактором файлов, а не двоичным, корректно исправляет ошибку при сохранении.

Это позволяет другим инструментам текстовых файлов, таким как wc, sedи им подобным, предоставлять ожидаемый вывод. Обратите внимание, что это viне умалчивает о проблеме:


$ 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

Обратите внимание, чтобы получить некоторые подсказки о том, какая viверсия у вас запущена, вы можете использовать :veкоманду. Здесь показано, что я использую устаревшую версию SVR4, определенно нет vim:

:ve
Version SVR4.0, Solaris 2.5.0

Судя по всему, в вашем заявлении говорится следующее:

:ve
Version 3.10

Вероятно, это означает, что AIX viоснован на исходном коде SVR3.

В любом случае, такое поведение и [Incomplete last line]предупреждающее сообщение присутствовали в исходном viкоде Билла Джоя по крайней мере с 1979 года и, насколько мне известно, сохранились во всех ветвях, созданных из выпусков исходного кода System V, на основе которых были созданы проприетарные Unix, такие как AIX.

Если говорить хронологически, то такое поведение является не следствием соответствия POSIX, а скорее следствием первоначального решения Билла Джоя оказать помощь пользователям, редактирующим поддельные текстовые файлы, а затем, десятилетие спустя, решения комитета POSIX сохранить эту толерантность.

Если вы используете edвместо vi, вы заметите, что первый вариант более подробно описывает проблему, по крайней мере, если вы edиз ветки исходного кода SVR3 или более новой:

$ ed file
'\n' appended
8
q

Обратите внимание также, что пустой файл — это допустимый текстовый файл, который содержит ноль строк. Поскольку нет незавершенной строки, которую нужно исправить, viне добавляет новую строку при сохранении файла.

решение3

Текст, в котором ошибочно отсутствует последний символ новой строки, пропускается через whileцикл оболочки, что приводит к тому, что последняя строка молча отбрасывается.

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

Обеспечение того, чтобы была конечная новая строка, является правильным, разумным и надлежащим значением по умолчанию. Другой вариант подразумевает знание и наличие времени для аудита всего кода оболочки, который касается текста, в котором отсутствует конечная новая строка, или риск потери последней строки текста.

решение4

Я не припомню другого поведения, при котором в конце файла добавлялась бы новая строка (использую viс середины 80-х).

Указывает ~на строку на экране, которая не является частью текста, а не на то, что файл не заканчивается символом новой строки. (Вы можете усложнить отслеживание ошибок, если поставите символ ~в последней строке скриптов оболочки). Если вы загрузите короткий файл с символом новой строки в конце, вы сами увидите ~и опровергнете свою мысль, что это указывает на текст, не заканчивающийся символом новой строки.

Связанный контент