我无法理解一个奇怪的行为:vi 似乎在文件末尾添加了一个换行符(ASCII:LF,因为它是 Unix ( AIX ) 系统),而我没有专门输入它。
我在 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.
Run Code Online (Sandbox Code Playgroud)
我希望 vi 将它“按原样”保存,因此有 39 个字节:前三行(数字 1 到 9,后跟一个换行符(我的系统上为 LF))中的每一行都有 10 个 ASCII 字符,最后一行只有 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?
Run Code Online (Sandbox Code Playgroud)
如果我使用 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.
Run Code Online (Sandbox Code Playgroud)
如果我用 vi 重新打开这两个文件(foo(40 个字符)和 foo2(39 个字符),它们看起来完全一样......
如果我在 vi 中打开 foo2(39 个字符,没有终止换行符)并且不进行:wq任何编辑,它会说它写了 40 个字符,并且出现换行符!
我无法访问更新的 vi(我在 AIX 上执行此操作,我认为是vi(不是Vim)3.10 版?(没有“-version”或其他了解它的方法))。
# strings /usr/bin/vi | grep -i 'version.*[0-9]'
@(#) Version 3.10
Run Code Online (Sandbox Code Playgroud)
vi(也许不是更新版本?或 Vim?)在文件末尾默默添加换行符是否正常?(我认为 ~ 表示前一行没有以换行符结尾。)
——
编辑:一些额外的更新和一些总结,非常感谢以下答案:
vi 在写入缺少它的文件时默默地添加一个尾随换行符(除非文件为空)。
它只在写作时这样做!(即,在您 :w 之前,您可以使用 :e 来验证文件在您打开时是否仍然存在……(即:它仍然显示“文件名”[最后一行不完整] N 行,M 字符)。保存时,会默默添加换行符,没有特定警告(它确实说明了它保存了多少字节,但在大多数情况下这不足以知道添加了换行符)(感谢 @jiliagre 与我谈论打开 vi 消息,它帮助我找到了一种方法来了解更改何时真正发生)
这(无声更正)是POSIX行为!(请参阅@barefoot-io 答案以获取参考)
Bar*_* IO 52
POSIX 需要这种行为,因此它在任何情况下都没有异常。
输入文件
有关 vi 命令支持的输入文件的说明,请参阅 ex 命令的 INPUT FILES 部分。
按照POSIX ex 手册的线索:
输入文件
输入文件应为文本文件或将是文本文件的文件,除了长度不超过 {LINE_MAX}-1 个字节且不包含 NUL 字符的不完整的最后一行。默认情况下,任何不完整的最后一行都将被视为它有一个尾随 <newline>。ex 实现可以选择性地允许编辑其他形式的文件。
vi 手册的 OUTPUT FILES 部分也重定向到 ex:
输出文件
ex 的输出应为文本文件。
一对 POSIX 定义:
包含组织成零个或多个行的字符的文件。这些行不包含 NUL 字符,并且长度不能超过 {LINE_MAX} 个字节,包括 <newline> 字符。尽管 POSIX.1-2008 不区分文本文件和二进制文件(参见 ISO C 标准),但许多实用程序仅在对文本文件进行操作时产生可预测或有意义的输出。具有此类限制的标准实用程序总是在其 STDIN 或 INPUT FILES 部分中指定“文本文件”。
零个或多个非 <newline> 字符加上终止 <newline> 字符的序列。
这些手册页摘录上下文中的这些定义意味着,如果文件的唯一缺陷是缺少最终换行符,则符合 ex/vi 的实现必须接受格式错误的文本文件,但在写入该文件的缓冲区时,结果必须是有效的文本文件。
虽然这篇文章引用了 POSIX 标准的 2013 版,但相关规定也出现在更旧的 1997 版中。
最后,如果您发现 ex 的换行符不受欢迎,您会觉得第七版 UNIX (1979) 的不宽容 ed 严重侵犯了您。从手册:
读取文件时,ed 丢弃 ASCII NUL 字符和最后一个换行符之后的所有字符。它拒绝读取包含非 ASCII 字符的文件。
jll*_*gre 31
这是预期的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
Run Code Online (Sandbox Code Playgroud)
请注意,要获得有关vi您正在运行的版本的一些线索,您可以使用该:ve命令。它在这里显示我在这里使用的是旧版 SVR4,绝对不是vim:
:ve
Version SVR4.0, Solaris 2.5.0
Run Code Online (Sandbox Code Playgroud)
显然,你的意思是:
:ve
Version 3.10
Run Code Online (Sandbox Code Playgroud)
这可能意味着 AIXvi基于 SVR3 源代码。
无论如何,这种行为和[Incomplete last line]警告消息vi至少自 1979 年以来一直存在于遗留的 Bill Joy源代码和 AFAIK 中,保留在从 System V 源代码版本创建的所有分支中,其中构建了专有的 Unix(如 AIX)。
从时间上讲,这种行为不是 POSIX 一致性的结果,而是 Bill Joy 最初决定帮助用户编辑虚假文本文件的结果,然后,十年后,POSIX 委员会决定保持这种容忍度。
如果您使用ed而不是vi,您会注意到前者对这个问题更加冗长,至少如果您ed来自 SVR3 或更新的源分支:
$ ed file
'\n' appended
8
q
Run Code Online (Sandbox Code Playgroud)
另请注意,空文件是恰好包含零行的有效文本文件。由于没有未终止的行要修复,vi因此在保存文件时不会附加换行符。