vi 是否会在文件末尾默默添加换行符 (LF)?

vi 是否会在文件末尾默默添加换行符 (LF)?

我无法理解一个奇怪的行为:vi 似乎添加了一个换行符(ASCII:LF,因为它是 Unix (AIX) 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.

如果我用 vi 重新打开两个文件(foo(40 个字符)和 foo2(39 个字符)),它们看起来完全相同...

如果我在 vi 中打开 foo2 (39 个字符,没有终止换行符)并且:wq不做任何编辑就可以了,它说它写入了 40 个字符,并且出现了换行符!

我无法访问更新的 vi(我在 AIX 上执行此操作,vi(不是维姆)我认为是3.10版? (没有“-版本”或其他了解它的方式))。

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

vi(也许不是在较新的版本中?或者 Vim?)在文件末尾默默地添加换行符是正常的吗? (我认为 ~ 表明前一行没有以换行符结尾。)

--

编辑:一些额外的更新和一些总结,非常感谢以下答案:

  • vi 在写入缺少换行符的文件时默默地添加尾随换行符(除非文件为空)。

  • 它只在写作时这样做! (即,直到您:w,您可以使用:e来验证文件是否仍与您打开时一样...(即:它仍然显示“文件名”[最后一行不完整] N行,M字符)。当你保存时,会默默地添加一个换行符,没有特定的警告(它确实说明了它保存了多少字节,但这在大多数情况下不足以知道添加了换行符)(感谢@jiliagre与我谈论了打开 vi 消息,它帮助我找到了一种方法来知道更改何时真正发生)

  • 这(无声更正)是POSIX行为! (参考@barefoot-io的答案)

答案1

POSIX 需要这种行为,所以它没有任何异常。

来自POSIX vi 手册:

输入文件

有关 vi 命令支持的输入文件的说明,请参阅 ex 命令的输入文件部分。

循着小道来到POSIX 前手册:

输入文件

输入文件应为文本文件或将是文本文件的文件,但不完整的最后一行长度不超过 {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线

零个或多个非 <newline> 字符加上终止 <newline> 字符的序列。

这些手册页摘录上下文中的这些定义意味着,虽然一致的 ex/vi 实现必须接受格式错误的文本文件(如果该文件的唯一缺陷是最终换行符缺失),但在写入该文件的缓冲区时,结果必须是有效的文本文件。

虽然这篇文章引用了 2013 版 POSIX 标准,相关规定也出现在更早的1997年版本中

最后,如果您发现 ex 的换行符不受欢迎,您会感到第七版 UNIX (1979) 的不宽容 ed 严重侵犯了您。来自手册:

读取文件时,ed 会丢弃 ASCII NUL 字符以及最后一个换行符之后的所有字符。它拒绝读取包含非 ASCII 字符的文件。

答案2

这是预期的vi行为。

您的文件的最后一行不完整,因此严格来说(即根据 POSIX 标准),它不是文本文件,而是二进制文件。

vi这是一个文本文件编辑器,而不是二进制文件编辑器,在保存时可以优雅地修复它。

这允许其他文本文件工具(如wcsed等)提供预期的输出。请注意,并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

这可能意味着 AIXvi基于 SVR3 源代码。

无论如何,这种行为和[Incomplete last line]警告消息至少从 1979 年起就一直存在于 Bill Joy 的遗留vi源代码中,据我所知,保留在从 System V 源代码版本创建的所有分支中,并从中构建了专有的 Unix(如 AIX)。

从时间顺序来看,这种行为并不是 POSIX 一致性的结果,而更多的是 Bill Joy 最初决定帮助用户编辑虚假文本文件的结果,然后,十年后,POSIX 委员会决定保持这种宽容。

如果您使用ed而不是vi,您会注意到前者对于该问题更加详细,至少如果您ed来自 SVR3 或更新的源分支:

$ ed file
'\n' appended
8
q

另请注意,空文件是恰好包含零行的有效文本文件。由于没有未终止的行需要修复,因此vi在保存文件时不会附加换行符。

答案3

不正确地缺少通过 shell 循环运行的最终换行符的文本while会导致最后一行被默默丢弃。

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

确保有一个最终的换行符是正确、明智和正确的默认设置。另一种选择是了解并有时间审核所有涉及缺少最终换行符的文本的 shell 代码,或者冒丢失文本最后一行的风险。

答案4

我不记得在文件末尾添加换行符的任何其他行为(vi自 80 年代中期开始使用)。

表示~屏幕上的一行不是文本的一部分,而不是文件不以换行符结尾。 (如果~在 shell 脚本的最后一行添加 ,则可能会很难跟踪错误)。如果您加载一个末尾带有换行符的短文件,您将看到自己~并反驳您认为它指示非换行符结尾的文本。

相关内容