Czy vi po cichu dodaje nowy wiersz (LF) na końcu pliku?

36

Mam problem ze zrozumieniem dziwnego zachowania: vi wydaje się dodawać nową linię (ASCII: LF, ponieważ jest to system uniksowy ( AIX )) na końcu pliku, kiedy NIE specjalnie go wpisałem.

Plik jako taki edytuję w vi (uważając, aby nie wprowadzić nowego wiersza na końcu):

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

Spodziewam się, że vi zapisze go „tak, jak jest”, więc powinien mieć 39 bajtów: 10 znaków ASCII w każdym z pierwszych trzech wierszy (cyfry od 1 do 9, po których następuje nowa linia (LF w moim systemie)) i tylko 9 w ostatnim linia (znaki od 1 do 9, bez kończącej nowej linii / LF).

Ale pojawia się, kiedy go zapisuję, ma 40 bajtów (zamiast 39), a od pokazuje końcowy 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?

Jeśli utworzę plik z printf, który robi dokładnie to, co zrobiłem w vi, działa zgodnie z oczekiwaniami:

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

Oba pliki (foo (40 znaków) i foo2 (39 znaków) wyglądają dokładnie tak samo, jeśli ponownie je otworzę za pomocą vi ...

A jeśli otworzę foo2 (39 znaków, bez kończącego nowego wiersza) w vi i po prostu zrobię to :wqbez edycji, to napisa , że zapisuje 40 znaków, i pojawia się wiersz!

Nie mogę uzyskać dostępu do nowszej wersji vi (robię to w systemie AIX, vi (nie w Vimie ) w wersji 3.10? Myślę, że (brak „-wersji” lub innych sposobów jej poznania)).

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

Czy to normalne, że vi (a może nie w nowszej wersji? Lub Vim?) Cicho dodaje nowy wiersz na końcu pliku? (Myślałem, że ~ wskazuje, że poprzednia linia NIE kończyła się nową linią).

-

Edycja: kilka dodatkowych aktualizacji i trochę podsumowania, wielkie dzięki poniższym odpowiedziom:

  • vi cicho dodaje końcowy znak nowej linii w momencie, gdy zapisuje plik, który go nie ma (chyba że plik jest pusty).

  • robi to tylko w momencie pisania! (tzn. dopóki nie w: możesz użyć: e, aby sprawdzić, czy plik jest nadal otwarty, gdy go otworzyłeś ... (tj .: nadal pokazuje „nazwę pliku” [ostatnia linia nie jest kompletna] N linia, znak M). Kiedy zapisujesz, nowy wiersz jest dodawany w trybie cichym, bez specjalnego ostrzeżenia (mówi, ile bajtów zapisuje, ale w większości przypadków to nie wystarczy, aby wiedzieć, że dodano nowy wiersz) (dzięki @jiliagre za rozmowę ze mną o otwierając komunikat vi, pomógł mi znaleźć sposób, aby wiedzieć, kiedy zmiana rzeczywiście nastąpi)

  • To (cicha korekta) jest zachowaniem POSIX ! (patrz odniesienia @ odpowiedź boso-io)

Olivier Dulac
źródło
Dla kompletności, która wersja systemu AIX (pełna wersja).
EightBitTony
2
Nie wiem, czy Vi w systemie AIX mają tę opcję - pojawia się tylko w vimie
Jeff Schaller
1
@JeffSchaller: dzięki za link. Niestety natywny vi nie ma „: set noeol” ani nawet opcji -b, aby otworzyć się w trybie binarnym ...
Olivier Dulac
1
Możesz uzyskać viwersję lub przynajmniej wskazówkę dotyczącą jej pochodzenia, uruchamiając :vepolecenie.
jlliagre
1
@ThomasDickey Indeed. Z jakiegoś powodu IBM usunął exstronę podręcznika, na której :verpolecenie jest zwykle dokumentowane.
jlliagre

Odpowiedzi:

28

To jest oczekiwane vizachowanie.

Twój plik ma niekompletny ostatni wiersz, więc ściśle mówiąc (tj. Zgodnie ze standardem POSIX), nie jest to plik tekstowy, ale plik binarny.

vi który jest edytorem plików tekstowych, a nie binarnym, z wdziękiem naprawia go podczas zapisywania.

Pozwala to innym narzędziom plików tekstowych, takim jak i podobnym wc, sedzapewnić oczekiwany wynik. Pamiętaj, że vinie milczy na ten temat:


$ 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

Uwaga: aby uzyskać wskazówki dotyczące viużywanej wersji, możesz użyć :vepolecenia. To pokazuje, że tutaj używam starszego SVR4, zdecydowanie nie vim:

:ve
Version SVR4.0, Solaris 2.5.0

Najwyraźniej twój twierdzi:

:ve
Version 3.10

To prawdopodobnie oznacza, że ​​AIX vijest oparty na kodzie źródłowym SVR3.

W każdym razie takie zachowanie i [Incomplete last line]komunikat ostrzegawczy znajdują się w starszym vikodzie źródłowym Billa Joya od co najmniej 1979 r. I AFAIK, zachowane we wszystkich gałęziach utworzonych z wersji kodu źródłowego Systemu V, z których zbudowano zastrzeżony system uniksowy, taki jak AIX.

Chronologicznie rzecz biorąc, takie zachowanie nie jest zatem konsekwencją zgodności z POSIX, ale bardziej konsekwencją pierwotnej decyzji Billa Joya o pomocy użytkownikom w edytowaniu fałszywych plików tekstowych, a następnie, dekadę później, decyzji komitetu POSIX o zachowaniu tej tolerancji.

Jeśli użyjesz edzamiast vi, zauważysz, że ten pierwszy jest bardziej szczegółowy w kwestii, przynajmniej jeśli edpochodzi z SVR3 lub nowszej gałęzi źródłowej:

$ ed file
'\n' appended
8
q

Zauważ też, że pusty plik jest prawidłowym plikiem tekstowym, który zawiera zero linii. Ponieważ nie ma wtedy niezakończonej linii do naprawienia, vinie dołącza nowej linii podczas zapisywania pliku.

jlliagre
źródło
1
Uważam, że pomyliliście vima z vi;) dziedzictwo vi jest znacznie mniej gadatliwe niż to ...
Olivier Dulac
@OlivierDulac Nie mylę ich. Ten test został wykonany przy użyciu starszej wersji SVR4, vipodobnie jak OP, chociaż na innym Uniksie. To nie jest vimani inny klon. Odpowiedź zaktualizowana w celu wyjaśnienia tego.
jlliagre
@OlivierDulac Hmm, właśnie zauważyłem, że jesteś OP. Wygląda na to, że AIX używa do viimplementacji starszej gałęzi System V. Prawdopodobnie SVR3. Czy na pewno nie ma [Incomplete last line]wiadomości po otwarciu pliku?
jlliagre
@OlivierDulac Ten link wydaje się sugerować, że ten sam komunikat może zostać wyświetlony przez viimplementację systemu AIX : www-01.ibm.com/support/docview.wss?uid=isg1IZ27694
jlliagre
Ll staram się zobaczyć to jutro
Olivier DuLac
51

POSIX wymaga takiego zachowania, więc nie jest to w żaden sposób niezwykłe.

Z podręcznika POSIX vi :

PLIKI WEJŚCIOWE

Zobacz sekcję Pliki wejściowe polecenia ex, aby uzyskać opis plików wejściowych obsługiwanych przez polecenie vi.

Podążając ścieżką do instrukcji POSIX ex :

PLIKI WEJŚCIOWE

Pliki wejściowe to pliki tekstowe lub pliki, które byłyby plikami tekstowymi, z wyjątkiem niekompletnego ostatniego wiersza, który nie jest dłuższy niż {LINE_MAX} -1 bajtów i nie zawiera znaków NUL. Domyślnie każdą niekompletną ostatnią linię traktuje się tak, jakby miała końcową <nową linię>. Edycja innych form plików może opcjonalnie być dozwolona przez implementacje ex.

Sekcja PLIKI WYJŚCIOWE w podręczniku vi również przekierowuje do np .:

PLIKI WYJŚCIOWE

Dane wyjściowe z ex powinny być plikami tekstowymi.

Para definicji POSIX:

3.397 Plik tekstowy

Plik zawierający znaki zorganizowane w zero lub więcej wierszy. Wiersze nie zawierają znaków NUL i żaden z nich nie może przekraczać długości {LINE_MAX} bajtów, w tym znaku <nowa linia>. Chociaż POSIX.1-2008 nie rozróżnia plików tekstowych od plików binarnych (patrz standard ISO C), wiele programów narzędziowych generuje przewidywalne lub znaczące dane wyjściowe podczas pracy na plikach tekstowych. Standardowe narzędzia, które mają takie ograniczenia, zawsze określają „pliki tekstowe” w swoich sekcjach STDIN lub INPUT FILES.

3,206 linii

Sekwencja zero lub więcej znaków innych niż <lineline> oraz kończący znak <lineline>.

Te definicje w kontekście tych fragmentów strony podręcznika oznaczają, że chociaż zgodna implementacja ex / vi musi zaakceptować zniekształcony plik tekstowy, jeśli jedyną deformacją tego pliku jest nieobecna końcowa nowa linia, podczas zapisywania bufora tego pliku wynik musi być poprawnym plikiem tekstowym.

Chociaż ten post odnosi się do wydania standardu POSIX 2013, odpowiednie postanowienia pojawiają się również w znacznie starszym wydaniu z 1997 roku .

Na koniec, jeśli uznasz, że nowa wersja eks byłaby niepożądana, poczujesz się głęboko naruszony przez nietolerancyjną edycję Seventh Edition UNIX (1979). Z instrukcji :

Podczas czytania pliku ed odrzuca znaki ASCII NUL i wszystkie znaki po ostatniej nowej linii. Odmawia odczytu plików zawierających znaki spoza ASCII.

Boso IO
źródło
dzięki, to odpowiada na moje pytanie. Poczekam jeszcze kilka dni na wypadek, gdyby jakieś lepsze odpowiedzi się pojawiły, ale teraz czuję, że możesz być akceptowaną odpowiedzią.
Olivier Dulac
Bardzo dobrze zrobione na dokładnie udokumentowanej odpowiedzi, prosto ze specyfikacji! :)
Wildcard
1
@Wildcard, zachowanie poprzedziło specyfikację.
jlliagre
@ jlliagre, chyba że masz pamiętnik Billa Joya lub twórcy ex(nie znam jego imienia), myślę, że specyfikacje POSIX są tak dobre, jak można się spodziewać. ;) W tym momencie najbliższy „oryginalnemu źródłu”, chociaż to prawda, że ​​zaczęli jako mniej więcej opisy istniejącej funkcjonalności.
Wildcard,
3
@Wildcard exzostał napisany wspólnie przez Billa Joya i Chucka Alleya ( web.cecs.pdx.edu/~kirkenda/joy84.html .) Nie kwestionuję specyfikacji POSIX i fakt, że obecne viwersje ją śledzą, po prostu stwierdzam zachowanie długo to poprzedza.
jlliagre
1

Nie przypominam sobie żadnego innego zachowania, które dodaje się nową linię na końcu pliku (używając viod połowy lat 80.).

~Wskazuje, że linia na ekranie, który nie jest częścią tekstu, a nie, że plik nie kończy się w nowej linii. (Śledzenie błędów może być trudne, jeśli umieścisz ~w ostatnim wierszu skryptów powłoki). Jeśli załadujesz krótki plik z nową linią na końcu, zobaczysz ~siebie i obalisz się, że twoja myśl wskazuje, że tekst nie kończy nowej linii.

Anthon
źródło
zaskakuje mnie dodanie nowej linii ... Spodziewam się, że vi nie dodam jej po cichu, ale wygląda na to, że tak ... Szukam wyjaśnienia tej postawy (niepokojące jest to, że otwieram foo2 (bez trailing LF) i po prostu: wq, ZMIENIA jego zawartość ... więc pokazuje mi coś, ale zapisuje kolejną rzecz ... dziwnie mówiąc, co najmniej ^^
Olivier Dulac
w jego poprzedniku ( ed) można było tworzyć linie i edytować je, nie dodając znaków. Zawsze myślałem o vi jako edytorze zorientowanym liniowo. Ale rozumiem twoją niespodziankę.
Anthon
1

Tekst niepoprawnie pozbawiony ostatecznego nowego wiersza przebiegającego przez whilepętlę powłoki powoduje, że ostatni wiersz jest dyskretnie odrzucany.

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

Zapewnienie ostatecznej nowej linii jest właściwym, rozsądnym i właściwym domyślnym. Inna opcja obejmuje znajomość i czas na sprawdzenie całego kodu powłoki, który dotyka tekstu bez ostatecznego nowego wiersza, lub ryzykuje utratę ostatniej linii tekstu.

gałązka
źródło