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


36

我很难理解一个奇怪的行为:当我没有专门键入它时,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.

我希望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?

如果我使用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(不是Vim)3.10版吗?(没有“ -version”或其他了解它的方法))。

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

在文件末尾静默添加换行符对vi(也许不是在较新的版本?还是Vim?)中正常吗?(我认为〜表示上一行没有以换行符结尾。)

-

编辑:一些其他更新和一些摘要,在很大程度上要感谢以下答案:

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

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

  • 这(静默更正)是POSIX行为!(请参阅@ barefoot-io答案以获取参考)


仅出于完整性考虑,请选择哪个版本的AIX(完整版)。
AugustBitTony

2
我不知道AIX的vi是否具有此选项-
Jeff Schaller

1
@JeffSchaller:链接的thx。不幸的是,原生vi没有“:set noeol”,甚至没有-b选项可以在二进制模式下打开...
Olivier Dulac

1
您可以vi通过运行:ve命令来获取版本或至少有关其来源的线索。
jlliagre

1
@ThomasDickey确实。由于某种原因,IBM删除了通常记录ex:ver命令的手册页。
jlliagre

Answers:


28

这是预期的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

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

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

从时间上讲,这种行为不是POSIX一致性的结果,而是比尔·乔伊(Bill Joy)最初的决定的结果,该决定是为了帮助用户编辑伪造的文本文件,然后,十年后,POSIX委员会决定保持这种容忍度。

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

$ ed file
'\n' appended
8
q

还要注意,空文件是一个碰巧包含零行的有效文本文件。由于没有固定vi的行,因此在保存文件时不添加换行符。


1
我相信您将vim误认为是vi;)旧版vi的详细程度远不如此...
Olivier Dulac

@OlivierDulac我不会混淆他们。vi就像OP一样,该测试是使用SVR4传统完成的,尽管它在另一个Unix上。这不是vim另一个克隆。答案已更新,以澄清这一点。
jlliagre

@OlivierDulac嗯,我刚刚注意到您实际上是OP。看来AIX正在使用较旧的System V分支来vi实现。可能是SVR3。确定[Incomplete last line]打开文件时没有消息吗?
jlliagre

@OlivierDulac此链接似乎暗示AIX vi实现可以显示此非常相同的消息:www-01.ibm.com/support/docview.wss?uid=isg1IZ27694
jlliagre

我明天尝试看一下
Olivier Dulac 2016年

51

POSIX要求这种行为,因此它绝非异常。

POSIX vi手册中

输入文件

有关vi命令支持的输入文件的描述,请参见ex命令的INPUT FILES部分。

遵循POSIX ex手册的方法

输入文件

输入文件应为文本文件或应为文本文件的文件,但最后一行不完整,长度不超过{LINE_MAX} -1个字节且不包含NUL字符。默认情况下,任何不完整的最后一行应被视为具有尾随的<newline>。ex实现可能会选择性地允许编辑其他形式的文件。

vi手册的OUTPUT FILES部分也重定向到ex:

输出文件

ex的输出应为文本文件。

一对POSIX定义:

3.397文本文件

包含以零行或更多行组织的字符的文件。这些行不包含NUL字符,并且长度都不能超过{LINE_MAX}个字节,包括<newline>字符。尽管POSIX.1-2008不能区分文本文件和二进制文件(请参阅ISO C标准),但是许多实用程序在对文本文件进行操作时只能产生可预测或有意义的输出。具有此类限制的标准实用程序始终在其STDIN或INPUT FILES部分中指定“文本文件”。

3.206线

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

这些定义在这些手册页摘录的上下文中表示,如果一致的ex / vi实现必须接受格式错误的文本文件(如果该文件的唯一变形是缺少最后的换行符),则在写入该文件的缓冲区时,结果必须是有效的文本文件。

尽管本文引用了POSIX标准的2013年版,但相关规定也出现在更老的1997年版中

最后,如果您发现ex的换行符不受欢迎,您将深深地受到第七版UNIX(1979)不容忍的ed的侵犯。从手册

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


谢谢,这确实回答了我的问题。我将再等几天,以防出现更好的答案,但是现在我觉得您可以成为公认的答案。
Olivier Dulac

直接从规格上对完全记录的答案做得很好!:)
通配符

1
@Wildcard,但此行为先于规范。
jlliagre

@jlliagre,除非您有比尔·乔伊ex(Bill Joy)的回忆录,或者是(不知道他的名字)的创建者,否则我认为POSIX规范是可以预期的。;)到目前为止,“原始来源”还是最接近的,尽管确实如此,它们最初或多或少是对现有功能的描述。
通配符

3
@Wildcard ex是由比尔·乔伊和Chuck巷共同编写(web.cecs.pdx.edu/~kirkenda/joy84.html。)我不质疑POSIX规范,事实上目前的vi版本不遵循它,我只是陈述行为早于它。
jlliagre

1

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

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


让我吃惊的是增加了一个换行符。我希望vi不会默默地添加它,但是看来确实如此。我正在寻找这种态度的解释(令人不安的事实是:我打开foo2(没有尾随LF)和:wq,它更改了内容...因此它向我显示了一些内容,但又保存了另一件事...很奇怪,至少可以说^^
Olivier Dulac

在其前身(ed)中,您可以创建行并进行编辑,而无需添加字符。我也一直将vi视为面向行的编辑器。但是我理解你的惊讶。
Anthon

1

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

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

确保有最终的换行符是正确的,理智的和适当的默认设置。另一个选择涉及知道并有时间审核所有缺少最终换行符的触摸到文本的外壳程序代码,或者冒着丢失文本最后一行的风险。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.