Vi

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

  • February 24, 2016

我無法理解一個奇怪的行為: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 個字節:前三行中的每一行都有 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(不是Vim)版本 3.10?(沒有“-version”或其他了解它的方式))。

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

vi(也許不是在較新的版本中?或 Vim?)在文件末尾靜默添加換行符是否正常?(我認為 ~ 表示上一行沒有以換行符結尾。)

**編輯:**一些額外的更新和一些總結,非常感謝以下答案:

  • vi 在寫入缺少它的文件時靜默添加尾隨換行符(除非文件為空)。
  • 它只在寫作時這樣做!(即,直到你 :w,你可以使用 :e 來驗證文件是否仍然像你打開它一樣……(即:它仍然顯示“文件名”

$$ Last line is not complete $$N 行,M 個字元)。保存時,會默默添加換行符,沒有特定警告(它確實說明了它節省了多少字節,但在大多數情況下這不足以知道添加了換行符)(感謝@jiliagre 與我談論打開 vi 消息,它幫助我找到了一種方法來知道變化何時真正發生)

  • 這(無聲更正)是POSIX行為!(參考@barefoot-io 答案)

這是預期的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

這可能意味著 AIXvi基於 SVR3 原始碼。

在任何情況下,這種行為和[Incomplete last line]警告消息至少從 1979 年和 AFAIK 就一直存在於遺留的 Bill Joyvi原始碼中,保留在從 System V 原始碼版本創建的所有分支中,從中建構了專有的 Unix,如 AIX。

按照時間順序,這種行為不是 POSIX 一致性的結果,而是 Bill Joy 最初決定幫助使用者編輯虛假文本文件的結果,然後,十年後,POSIX 委員會決定保持這種容忍度。

如果您使用edinstead of vi,您會注意到前者對問題更為詳細,至少如果您ed來自 SVR3 或更新的源分支:

$ ed file
**'\n' appended**
8
q

另請注意,空文件是恰好包含零行的有效文本文件。由於沒有要修復的未終止行,vi因此在保存文件時不會附加換行符。

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 丟棄 ASCII NUL 字元和最後一個換行符之後的所有字元。它拒絕讀取包含非 ASCII 字元的文件。

引用自:https://unix.stackexchange.com/questions/263869