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 源代碼版本創建的所有分支中,並從中構建了像AIX 這樣的專有Unix。

從時間順序來看,這種行為並不是 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 腳本的最後一行添加 ,則可能會很難追蹤錯誤)。如果您加載一個末尾帶有換行符的短文件,您將看到自己~並反駁您認為它指示非換行符結尾的文字。

相關內容