vi はファイルの末尾に改行 (LF) を黙って追加しますか?

vi はファイルの末尾に改行 (LF) を黙って追加しますか?

奇妙な動作を理解するのに苦労しています。viは改行(ASCII:LF、Unix(エイエックス) システム) をファイルの末尾に追加しましたが、特に入力したわけではありません。

私は 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 はこれを「そのまま」保存することを期待しています。つまり、最初の 3 行にはそれぞれ 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?

vi 内で実行した操作とまったく同じ操作を printf で実行してファイルを作成すると、期待どおりに動作します。

# ## 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.

両方のファイル (foo (40 文字) と foo2 (39 文字)) を vi で再度開くと、まったく同じに見えます...

そして、foo2(39文字、改行なし)をviで開くと、:wq編集せずにそのままにしておく、40 文字を書き込むと表示され、改行が表示されます。

より新しいviにアクセスできません(私はAIXでこれを行います、vi(ヴィム) バージョン 3.10 だと思いますか? (「-version」またはそれを確認する他の手段はありません)。

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

vi (最近のバージョンではそうではないでしょうか? あるいは Vim でしょうか?) がファイルの末尾に暗黙的に改行を追加するのは正常ですか? (~ は前の行が改行で終わっていないことを示していると思いました。)

--

編集:いくつかの追加の更新と簡単な要約、そして以下の回答に心から感謝します。

  • vi は、末尾に改行がないファイルを書き込む瞬間に、自動的に末尾に改行を追加します (ファイルが空でない限り)。

  • 書き込み時にのみ実行されます。(つまり、:w を実行するまで、:e を使用して、ファイルが開いたときと同じ状態であるかどうかを確認できます... (つまり、"filename" [最後の行が不完全] N 行、M 文字がまだ表示されます)。保存すると、特定の警告なしに、改行が静かに追加されます (保存されるバイト数は表示されますが、ほとんどの場合、改行が追加されたことを認識するには不十分です) (vi の開始メッセージについて教えてくれた @jiliagre に感謝します。変更が実際にいつ発生するかを知る方法を見つけるのに役立ちました)

  • これは(沈黙の訂正)POSIX行動です!(参考として @barefoot-io の回答を参照してください)

答え1

POSIX ではこの動作が必須なので、決して異常なことではありません。

からPOSIX vi マニュアル:

入力ファイル

vi コマンドでサポートされている入力ファイルの説明については、ex コマンドの「入力ファイル」セクションを参照してください。

道を辿ってPOSIX ex マニュアル:

入力ファイル

入力ファイルは、テキスト ファイル、または不完全な最終行を除いてテキスト ファイルとなるファイルで、最終行の長さは {LINE_MAX}-1 バイト以下で、NUL 文字は含まれません。デフォルトでは、不完全な最終行は末尾に <newline> があるものとして扱われます。他の形式のファイルの編集は、ex 実装によってオプションで許可される場合があります。

vi マニュアルの OUTPUT FILES セクションも ex にリダイレクトされます。

出力ファイル

ex からの出力はテキスト ファイルになります。

POSIX 定義のペア:

3.397 テキストファイル

0 行以上の行にまとめられた文字を含むファイル。行には NUL 文字は含まれず、<改行> 文字を含めて {LINE_MAX} バイトを超えることはできません。POSIX.1-2008 ではテキスト ファイルとバイナリ ファイルは区別されませんが (ISO C 標準を参照)、多くのユーティリティはテキスト ファイルで操作する場合に予測可能な、または意味のある出力のみを生成します。このような制限がある標準ユーティリティは、常に STDIN または INPUT FILES セクションで「テキスト ファイル」を指定します。

3.206 ライン

0 個以上の非 <newline> 文字のシーケンスと終了の <newline> 文字。

これらのマニュアル ページの抜粋のコンテキストにおけるこれらの定義は、準拠した ex/vi 実装では、ファイルの唯一の変形が最後の改行がないという場合には不正な形式のテキスト ファイルを受け入れる必要があるものの、そのファイルのバッファーに書き込むときには、結果は有効なテキスト ファイルになる必要があることを意味します。

この投稿ではPOSIX標準の2013年版を参照していますが、関連する規定は、はるかに古い1997年版にも記載されている。

最後に、ex の改行付加が歓迎されない場合、Seventh Edition UNIX (1979) の非寛容な ed によって深く侵害されたと感じるでしょう。マニュアルより:

ファイルを読み取るとき、ed は ASCII NUL 文字と最後の改行文字以降のすべての文字を破棄します。非 ASCII 文字を含むファイルの読み取りは拒否します。

答え2

これは予想される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 年以来、Bill Joy のソース コードに存在しておりvi、私の知る限り、AIX などの独自の Unix が構築された System V ソース コード リリースから作成されたすべてのブランチに保持されています。

時系列で言えば、この動作は POSIX 準拠の結果ではなく、偽のテキスト ファイルをユーザーによる編集に役立てようという Bill Joy の当初の決定、そして 10 年後の POSIX 委員会によるこの許容範囲を維持するという決定の結果です。

edの代わりにを使用すると、少なくとも がSVR3 以降のソース ブランチからのものであるvi場合は、前者の方が問題についてより詳細に説明されていることに気付くでしょう。ed

$ ed file
'\n' appended
8
q

また、空のファイルは、行が 0 行しかない有効なテキスト ファイルであることにも注意してください。修正すべき未終了の行がないため、viファイルを保存するときに改行は追加されません。

答え3

最後の改行が不適切に欠落しているテキストをシェルwhileループで実行すると、最後の行が暗黙的に破棄されます。

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

最後の改行があることを確認するのは、正しく、健全で、適切なデフォルトです。もう 1 つのオプションは、最後の改行がないテキストに関係するすべてのシェル コードを監査する必要があることを認識し、監査する時間を確保するか、テキストの最後の行が失われるリスクを負うかのいずれかです。

答え4

ファイルの末尾に改行が追加されるという他の動作は覚えていません ( vi80 年代半ばから使用しています)。

~、画面上の行がテキストの一部ではないことを示し、ファイルが改行で終わっていないことを示すものではありません。(~シェル スクリプトの最終行に を置くと、エラーの追跡が困難になることがあります)。最後に改行がある短いファイルをロードすると、 が~自分で表示され、改行で終わっていないテキストを示していると考えていたことが反証されます。

関連情報