Dlaczego pliki tekstowe powinny kończyć się nową linią?

1467

Zakładam, że wszyscy tutaj znają powiedzenie, że wszystkie pliki tekstowe powinny kończyć się nową linią. Znam tę „zasadę” od lat, ale zawsze zastanawiałem się - dlaczego?

Will Robertson
źródło
30
tylko nitpick. nie jest to „nowa linia” na końcu pliku. To „łamanie linii” na końcu ostatniej linii. Zobacz też najlepszą odpowiedź na powiązane pytanie: stackoverflow.com/questions/16222530/...
gcb
344
Żeby jeszcze trochę poprawić, nie napisał „nowej linii”, napisał „nową linię”, co jest poprawne.
sindrenm
5
nie jestem zaznajomiony, ale zastanawiam się, bo tak naprawdę, ponieważ liczba przypadków, w których ta zbędna nowa linia faktycznie
niszczy
2
Obecnie używam strumieni Node.js do analizowania danych tekstowych wiersz po wierszu, a brak przerwania linii terminali jest denerwujący, ponieważ muszę dodać dodatkową logikę, gdy strona wejściowa strumienia zostanie zakończona / zamknięte, aby zapewnić przetwarzanie ostatniego wiersza.
Mark K Cowan
23
Sposób, w jaki Unix postrzega swoje ogólne zachowanie na końcu plików, jest następujący: \ n znaki nie rozpoczynają linii; zamiast tego kończą je. Zatem \ n jest zakończeniem linii, a nie separatorem linii. Pierwsza linia (jak wszystkie linie) nie potrzebuje \ n, aby ją uruchomić. Ostatnia linia (jak wszystkie linie) potrzebuje \ n, aby ją zakończyć. Znak \ n na końcu pliku nie tworzy dodatkowej linii. Czasami jednak edytory tekstu dodają widoczną pustą linię. Nawet emacs robi to, opcjonalnie .
MarkDBlackwell

Odpowiedzi:

1381

Ponieważ w ten sposób standard POSIX definiuje linię :

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

Dlatego wiersze nie kończące się znakiem nowej linii nie są uważane za wiersze rzeczywiste. Dlatego niektóre programy mają problemy z przetwarzaniem ostatniego wiersza pliku, jeśli nie jest on zakończony znakiem nowej linii.

Podczas pracy na emulatorze terminali jest co najmniej jedna twarda zaleta: wszystkie narzędzia uniksowe oczekują tej konwencji i działają z nią. Na przykład podczas łączenia plików z catplikiem zakończonym znakiem nowej linii będzie mieć inny efekt niż ten bez:

$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz

I, jak pokazuje również poprzedni przykład, podczas wyświetlania pliku w wierszu poleceń (np. Przez more ) plik zakończony znakiem nowej linii powoduje poprawne wyświetlanie. Nieprawidłowo zakończony plik może być zniekształcony (druga linia).

Aby zachować spójność, bardzo pomocne jest przestrzeganie tej reguły - w przeciwnym razie będzie to wymagać dodatkowej pracy w przypadku domyślnych narzędzi uniksowych.


Pomyśl o tym inaczej: jeśli wiersze nie są kończone znakiem nowej linii, catznacznie trudniej jest sprawić, by takie polecenia były przydatne: w jaki sposób można wykonać polecenie łączenia plików w taki sposób, aby

  1. umieszcza początek każdego pliku w nowej linii, co jest tym, czego chcesz 95% czasu; ale
  2. pozwala na scalenie ostatniego i pierwszego wiersza dwóch plików, jak w powyższym przykładzie między b.txti c.txt?

Oczywiście jest to możliwe do rozwiązania, ale musisz uczynić korzystanie z catbardziej złożonego (dodając np. Argumenty pozycyjnego wiersza poleceń cat a.txt --no-newline b.txt c.txt), a teraz polecenie a nie każdy plik, kontroluje sposób wklejania go wraz z innymi plikami. To prawie na pewno nie jest wygodne.

… Lub musisz wprowadzić specjalny znak wartownika, aby zaznaczyć linię, która powinna być kontynuowana, a nie zakończona. Cóż, teraz utknąłeś w takiej samej sytuacji jak w POSIX, z wyjątkiem odwróconego (kontynuacja linii zamiast znaku zakończenia linii).


Teraz w systemach niezgodnych z POSIX (obecnie to głównie Windows) chodzi o dyskusję: pliki na ogół nie kończą się nową linią, a (nieformalna) definicja linii może na przykład być „tekstem oddzielonym nowymi liniami” (zwróć uwagę na nacisk). Jest to całkowicie ważne. Jednak w przypadku danych strukturalnych (np. Kodu programowania) parsowanie jest minimalnie bardziej skomplikowane: ogólnie oznacza to, że parsery muszą zostać przepisane. Jeśli parser został pierwotnie napisany z myślą o definicji POSIX, może być łatwiej zmodyfikować strumień tokenów niż parser - innymi słowy, dodaj token „sztucznej nowej linii” na końcu wejścia.

Konrad Rudolph
źródło
8
Chociaż teraz jest to niepraktyczne do naprawienia, wyraźnie POSIX popełnił błąd podczas definiowania linii - jako dowód liczby pytań dotyczących tego problemu. Linia powinna być zdefiniowana jako zero lub więcej znaków zakończonych przez <eol>, <eof> lub <eol> <eof>. Złożoność analizatora składni nie jest ważnym problemem. Złożoność, tam gdzie to możliwe, należy przenieść z głowy programisty do biblioteki.
Doug Coburn,
23
@DougCoburn Ta odpowiedź zawierała wyczerpującą, techniczną dyskusję wyjaśniającą, dlaczego jest to źle i dlaczego POSIX zrobił właściwą rzecz. Niestety te komentarze najwyraźniej zostały niedawno usunięte przez nadgorliwego moderatora. Krótko mówiąc, nie chodzi o analizę złożoności; raczej twoja definicja znacznie utrudnia tworzenie narzędzi, takich jak catużyteczne i spójne.
Konrad Rudolph,
8
@Leon Reguła POSIX polega na zmniejszaniu przypadków krawędzi. I robi to pięknie. W zasadzie trochę mi brakuje, że ludzie tego nie rozumieją: to najprostsza możliwa, spójna definicja linii.
Konrad Rudolph
6
@ BT Myślę, że zakładasz, że mój przykład wygodniejszego przepływu pracy jest przyczyną tej decyzji. Nie jest, to tylko konsekwencja. Powodem jest to, że reguła POSIX jest regułą, że to najprostszy, a co sprawia, że linie przeładunkowe w parser najprostszy. Jedynym powodem, dla którego prowadzimy tę debatę, jest to, że Windows robi to inaczej, i że w konsekwencji istnieje wiele narzędzi, które zawodzą w plikach POSIX. Gdyby wszyscy zrobili POSIX, nie byłoby żadnego problemu. Jednak ludzie narzekają na POSIX, a nie na Windows.
Konrad Rudolph
7
@ BT Odnoszę się tylko do systemu Windows, aby wskazać przypadki, w których reguły POSIX nie mają sensu (innymi słowy rzuciłem ci kość). Cieszę się, że nigdy więcej o tym nie wspominam w tej dyskusji. Ale wtedy twoje roszczenie ma jeszcze mniej sensu: na platformach POSIX po prostu nie ma sensu dyskutować plików tekstowych z różnymi konwencjami zakończenia linii, ponieważ nie ma powodu, aby je tworzyć. Jaka jest zaleta? Nie ma dosłownie żadnego. - Podsumowując, naprawdę nie rozumiem nienawiści wywoływanej przez tę odpowiedź (lub regułę POSIX). Szczerze mówiąc, jest to całkowicie irracjonalne.
Konrad Rudolph
282

Każda linia powinna być zakończona znakiem nowej linii, w tym ostatnią. Niektóre programy mają problemy z przetwarzaniem ostatniego wiersza pliku, jeśli nie jest on zakończony znakiem nowej linii.

GCC ostrzega przed tym nie dlatego, że nie może przetworzyć pliku, ale dlatego, że musi to być częścią standardu.

Standard języka C mówi, że plik źródłowy, który nie jest pusty, powinien kończyć się znakiem nowej linii, który nie powinien być bezpośrednio poprzedzony znakiem odwrotnego ukośnika.

Ponieważ jest to klauzula „powinien”, musimy wysłać komunikat diagnostyczny dotyczący naruszenia tej zasady.

Znajduje się to w sekcji 2.1.1.2 normy ANSI C 1989. Sekcja 5.1.1.2 normy ISO C 1999 (i prawdopodobnie również norma ISO C 1990).

Odniesienie: Archiwum poczty GCC / GNU .

Bill jaszczurka
źródło
17
proszę napisać dobre programy, które albo pozwalają wstawić nowy wiersz w razie potrzeby podczas przetwarzania, albo potrafią odpowiednio obsłużyć „brakujące” ... których tak naprawdę nie brakuje
tobibeer
4
@BilltheLizard, jakie są przykłady „Niektóre programy mają problemy z przetwarzaniem ostatniego wiersza pliku, jeśli nie jest on zakończony znakiem nowej linii” ?
Pacerier
4
@Pacerier wc -lnie policzy ostatniego wiersza pliku, jeśli nie jest on zakończony znakiem nowej linii. Ponadto catpołączy ostatni wiersz pliku z pierwszym wierszem następnego pliku w jeden, jeśli ostatni wiersz pierwszego pliku nie jest zakończony znakiem nowej linii. Niemal każdy program, który szuka nowych linii jako separatora, może to zepsuć.
Bill the Lizard
2
@BilltheLizard, to znaczy wcma już wspomniano ....
Pacerier
2
@BilltheLizard, My bad, aby wyjaśnić: jakie są przykłady programów, które mają problemy z przetwarzaniem ostatniego wiersza pliku, jeśli nie jest on zakończony znakiem nowej linii (oprócz tych, które zostały już wspomniane masowo w wątku, jak cati wc)?
Pacerier
116

Ta odpowiedź jest raczej próbą odpowiedzi technicznej niż opinii.

Jeśli chcemy być purystami POSIX, definiujemy linię jako:

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

Źródło: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

Niekompletna linia jako:

Sekwencja jednego lub więcej znaków innych niż <lineline> na końcu pliku.

Źródło: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195

Plik tekstowy jako:

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 wyniki tylko 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.

Źródło: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

Ciąg jako:

Ciągła sekwencja bajtów zakończona przez pierwszy bajt zerowy włącznie.

Źródło: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

Na tej podstawie możemy wywnioskować, że jedynym problemem, z którym możemy potencjalnie napotkać jakiekolwiek problemy, jest koncepcja linii pliku lub pliku jako pliku tekstowego (ponieważ plik tekstowy jest organizacją zerową lub więcej linii, a linia, którą znamy, musi kończyć się <nową linią>).

Sprawa w punkcie: wc -l filename.

Z wcpodręcznika czytamy:

Linia jest zdefiniowana jako ciąg znaków rozdzielony znakiem <nowa linia>.

Jakie są konsekwencje dla plików JavaScript, HTML i CSS, ponieważ są one plikami tekstowymi ?

W przeglądarkach, nowoczesnych IDE i innych aplikacjach front-end nie ma problemów z pomijaniem EOL w EOF. Aplikacje poprawnie parsują pliki. Musi, ponieważ nie wszystkie systemy operacyjne są zgodne ze standardem POSIX, więc niepraktyczne byłoby, gdyby narzędzia inne niż OS (np. Przeglądarki) obsługiwały pliki zgodnie ze standardem POSIX (lub jakimkolwiek standardem na poziomie systemu operacyjnego).

W rezultacie możemy być stosunkowo pewni, że EOL w EOF nie będzie miał praktycznie żadnego negatywnego wpływu na poziomie aplikacji - niezależnie od tego, czy działa w systemie operacyjnym UNIX.

W tym momencie możemy śmiało powiedzieć, że pomijanie EOL w EOF jest bezpieczne, gdy mamy do czynienia z JS, HTML, CSS po stronie klienta. W rzeczywistości możemy stwierdzić, że zminimalizowanie któregokolwiek z tych plików, które nie zawiera <newline>, jest bezpieczne.

Możemy pójść o krok dalej i powiedzieć, że jeśli chodzi o NodeJS, to również nie może on być zgodny ze standardem POSIX, ponieważ może działać w środowiskach niezgodnych z POSIX.

Co nam zatem pozostało? Oprzyrządowanie na poziomie systemu.

Oznacza to, że jedyne problemy, które mogą się pojawić, dotyczą narzędzi, które starają się dostosować swoją funkcjonalność do semantyki POSIX (np. Definicja linii, jak pokazano w wc).

Mimo to nie wszystkie powłoki będą automatycznie dostosowywać się do POSIX. Na przykład Bash nie domyślnie zachowuje się w POSIX. Jest to przełącznik, aby włączyć go: POSIXLY_CORRECT.

Zastanów się nad wartością EOL jako <newline>: https://www.rfc-editor.org/old/EOLstory.txt

Pozostając na torze narzędziowym, we wszystkich praktycznych celach i celach, zastanówmy się nad tym:

Pracujmy z plikiem, który nie ma EOL. W chwili pisania tego pliku w tym przykładzie jest zminimalizowanym JavaScript bez EOL.

curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js
curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js

$ cat x.js y.js > z.js

-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 x.js
-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 y.js
-rw-r--r--  1 milanadamovsky  15810 Aug 14 23:18 z.js

Zauważ, że catrozmiar pliku jest dokładnie sumą jego poszczególnych części. Jeśli łączenie plików JavaScript stanowi problem dla plików JS, bardziej odpowiednie byłoby uruchomienie każdego pliku JavaScript z średnikiem.

Jak ktoś wspomniany w tym wątku: co zrobić, jeśli chcesz catdwa pliki, których dane wyjściowe stają się tylko jedną linią zamiast dwóch? Innymi słowy, catrobi to, co powinien.

manZ cattylko wspomina czytanie wejście do EOF, a nie <nowalinia>. Zauważ, że -nprzełącznik catwypisze również linię nie zakończoną <nowąw>> (lub linię niekompletną ) jako linię - ponieważ liczenie zaczyna się od 1 (zgodnie z man.)

-n Numeruj linie wyjściowe, zaczynając od 1.

Teraz, gdy rozumiemy, jak POSIX definiuje linię , to zachowanie staje się niejednoznaczne lub w rzeczywistości niezgodne.

Zrozumienie celu i zgodności danego narzędzia pomoże w określeniu, jak ważne jest zakończenie plików za pomocą EOL. W C, C ++, Java (JAR) itp. ... niektóre standardy będą dyktować nowy wiersz ważności - nie ma takiego standardu dla JS, HTML, CSS.

Na przykład, zamiast korzystać z wc -l filenamejednego, można zrobić awk '{x++}END{ print x}' filenamei mieć pewność, że powodzenie zadania nie jest zagrożone przez plik, który możemy chcieć przetworzyć, którego nie napisaliśmy (np. Bibliotekę strony trzeciej, taką jak zminimalizowany JS curld) - chyba że nasz naprawdę chodziło o policzenie wierszy w sensie zgodnym z POSIX.

Wniosek

Będzie bardzo niewiele rzeczywistych przypadków użycia, w których pominięcie EOL w EOF dla niektórych plików tekstowych, takich jak JS, HTML i CSS, będzie miało negatywny wpływ - jeśli w ogóle. Jeśli polegamy na obecności <newline>, ograniczamy niezawodność naszego narzędzia tylko do plików, które tworzymy i otwieramy się na potencjalne błędy wprowadzone przez pliki stron trzecich.

Morał tej historii: oprzyrządowanie inżynierskie, które nie ma słabości polegania na EOL w EOF.

Publikuj przypadki użycia, które dotyczą JS, HTML i CSS, gdzie możemy zbadać, w jaki sposób pomijanie EOL ma niekorzystny wpływ.

Milan Adamovsky
źródło
2
POSIX nie jest oznaczony w pytaniu ... wat o zakończeniach linii MVS / OS? czy zakończenia linii MS-DOS? Nawiasem mówiąc, wszystkie znane systemy posix zezwalają plikom tekstowym bez końcowego końca linii (nie znaleziono przypadku w systemie roszczenia zgodnym z posix, w którym „plik tekstowy” ma specjalne traktowanie w jądrze, aby wstawić odpowiednią nową linię w przypadku, gdy nie ma it)
Luis Colorado,
62

Może to być związane z różnicą między :

  • plik tekstowy (każda linia ma kończyć się na końcu linii)
  • plik binarny (nie ma prawdziwych „linii”, o których należy mówić, a długość pliku musi zostać zachowana)

Jeśli każda linia kończy się na końcu linii, pozwala to na przykład uniknąć sytuacji, w której konkatenacja dwóch plików tekstowych sprawiłaby, że ostatni wiersz pierwszego byłby uruchomiony w pierwszym wierszu drugiego.

Dodatkowo, edytor może sprawdzić przy ładowaniu, czy plik kończy się na końcu linii, zapisuje go w lokalnej opcji „eol” i używa tego podczas zapisywania pliku.

Kilka lat temu (2005) wielu redaktorów (ZDE, Eclipse, Scite, ...) „zapomniało” o ostatecznym EOL, co nie było bardzo doceniane .
Nie tylko to, ale nieprawidłowo zinterpretowali ten końcowy EOL jako „rozpocznij nowy wiersz” i faktycznie wyświetlają inny wiersz, jakby już istniał.
Było to bardzo widoczne w przypadku „właściwego” pliku tekstowego z dobrze zachowującym się edytorem tekstu, takim jak vim, w porównaniu do otwierania go w jednym z powyższych edytorów. Wyświetliła dodatkową linię poniżej rzeczywistej ostatniej linii pliku. Widzisz coś takiego:

1 first line
2 middle line
3 last line
4
VonC
źródło
11
+1. Znalazłem to SO pytanie podczas tego samego problemu. Pokazanie tej „fałszywej” ostatniej linii jest bardzo denerwujące dla Eclipse, a jeśli ją usunę, wtedy git (i wszystkie inne narzędzia unixowe, które oczekują EOL) narzeka. Zauważ też, że dotyczy to nie tylko roku 2005: Eclipse 4.2 Juno nadal ma ten problem.
MestreLion
@MestreLion, kontynuacja na stackoverflow.com/questions/729692/…
Pacerier
46

Niektóre narzędzia tego oczekują. Na przykład wcoczekuje:

$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
Flimm
źródło
22
Nie powiedziałbym „niektóre”, mówię, że większość narzędzi oczekuje tego w przypadku plików tekstowych, jeśli nie wszystkie. kot, git diff, wc, grep, sed ... lista jest ogromna
MestreLion
Być może można powiedzieć, że wctego się nie spodziewa , ponieważ działa on w ramach definicji POSIX-a „linii”, w przeciwieństwie do intuicyjnego rozumienia „linii” przez większość ludzi.
Guildenstern
@Guildenstern Intuicyjna definicja służy wc -ldo drukowania1 w obu przypadkach, ale niektórzy ludzie mogą powiedzieć, że druga skrzynka powinna zostać wydrukowana 2.
Flimm
@Flimm Jeśli myślisz o \nterminatorze linii, a nie o separatorze linii, tak jak robi to POSIX / UNIX, to oczekiwanie na drugi przypadek wydrukowania 2 jest absolutnie szalone.
średnik
21

Zasadniczo istnieje wiele programów, które nie przetwarzają poprawnie plików, jeśli nie otrzymają ostatecznego EOL EOL.

GCC ostrzega cię przed tym, ponieważ jest to oczekiwane jako część standardu C. (najwyraźniej sekcja 5.1.1.2)

Ostrzeżenie kompilatora „Brak nowego wiersza na końcu pliku”

cgp
źródło
5
GCC nie jest w stanie przetworzyć pliku, musi dać ostrzeżenie jako część standardu C.
Bill the Lizard
IIRC, MSVC 2005 narzekało na pliki C, które kończyły się niekompletnymi liniami i prawdopodobnie odmówiły ich skompilowania.
Mark K Cowan
16

Wynika to z bardzo wczesnych dni, kiedy używane były proste terminale. Znak nowej linii został użyty do uruchomienia „opróżnienia” przesłanych danych.

Dziś znak nowej linii nie jest już wymagany. Oczywiście, wiele aplikacji nadal ma problemy, jeśli nie ma nowej linii, ale uważam, że błąd w tych aplikacjach.

Jeśli jednak masz format pliku tekstowego, w którym jesteś potrzebujesz nowej linii, otrzymujesz prostą weryfikację danych bardzo tanio: jeśli plik kończy się linią, która nie ma nowej linii na końcu, wiesz, że plik jest uszkodzony. Mając tylko jeden dodatkowy bajt dla każdej linii, możesz wykryć uszkodzone pliki z wysoką dokładnością i prawie bez czasu procesora.

Stefan
źródło
15
obecnie nowa linia EOF dla plików tekstowych może nie być wymagana, ale jest to przydatna konwencja, która sprawia, że ​​większość narzędzi uniksowych współpracuje ze spójnymi wynikami. To wcale nie jest błąd.
MestreLion
14
Wielu z nas w ogóle nie używa narzędzi uniksowych i nie obchodzi nas to.
DaveWalley,
12
To nie tylko narzędzia uniksowe, każde narzędzie będzie działało lepiej i / lub będzie kodowane prościej, jeśli przyjmie sensowne formaty plików.
Sam Watkins,
2
@Sam Watkins Zgadzam się, że proste, dobrze zdefiniowane formaty są dobre. Jednak kod nadal musi weryfikować, a nie zakładać, że dane są zgodne z formatem.
chux - Przywróć Monikę
8
@MestreLion To bezużyteczne dziedzictwo od zestawu złych narzędzi zgodnych z głupimi standardami. Te artefakty ekstremistycznego programowania (tj. Plik wszystkiego! Wszystko powinno mówić zwykłym tekstem!) Nie umarły wkrótce po ich wynalezieniu, ponieważ były jedynymi dostępnymi tego rodzaju narzędziami w pewnym momencie historii. C został zastąpiony przez C ++, nie jest częścią POSIX, nie wymaga EOL w EOF, a jego użycie jest (oczywiście) odradzane przez * nix luddists.
polkovnikov.ph
14

Osobny przypadek użycia: gdy plik tekstowy jest kontrolowany pod względem wersji (w tym przypadku konkretnie pod git, chociaż dotyczy to również innych). Jeśli zawartość zostanie dodana na końcu pliku, linia, która była poprzednio ostatnią linią, zostanie poddana edycji w celu włączenia znaku nowej linii. Oznacza to, że blamesprawdzenie pliku, aby dowiedzieć się, kiedy ostatnio edytowano ten wiersz, pokaże dodanie tekstu, a nie zatwierdzenie przed tym, co naprawdę chciałeś zobaczyć.

Robin Whittleton
źródło
1
diff i winę należy po prostu zaktualizować, aby wykryły „nowe linie” zamiast „nowych linii” ( \n). Problem rozwiązany.
Andrew
1
Możesz użyć znacznika -w, aby zignorować zmiany białych znaków, ale nie są one domyślne.
Robin Whittleton,
11

Oprócz powyższych praktycznych powodów, nie zaskoczyłoby mnie, gdyby twórcy Unixa (Thompson, Ritchie i inni) lub ich poprzednicy Multics zdali sobie sprawę, że istnieje teoretyczny powód, aby używać terminatorów linii zamiast separatorów linii: Z linią terminatory, możesz zakodować wszystkie możliwe pliki linii. W przypadku separatorów linii nie ma różnicy między plikiem zerowym a plikiem zawierającym pojedynczą pustą linię; oba są zakodowane jako plik zawierający zero znaków.

Przyczyny są następujące:

  1. Ponieważ tak definiuje to POSIX.
  2. Ponieważ niektóre narzędzia tego oczekują lub „zachowują się” bez niego. Na przykład wc -lnie policzy ostatniej „linii”, jeśli nie kończy się na nowej linii.
  3. Ponieważ jest to proste i wygodne. W Uniksie catpo prostu działa i działa bez komplikacji. Po prostu kopiuje bajty każdego pliku, bez potrzeby interpretacji. Nie sądzę, że istnieje odpowiednik DOS cat. Użycie copy a+b cspowoduje połączenie ostatniego wiersza pliku az pierwszym wierszem plikub .
  4. Ponieważ plik (lub strumień) zerowych linii można odróżnić od pliku jednej pustej linii.
jrw32982 obsługuje Monikę
źródło
11

Zastanawiam się nad tym od lat. Ale dzisiaj spotkałem dobry powód.

Wyobraź sobie plik z zapisem w każdej linii (np. Plik CSV). I że komputer zapisuje zapisy na końcu pliku. Ale nagle się zawiesił. Gee, czy ostatnia linia była kompletna? (niezła sytuacja)

Ale jeśli zawsze zakończymy ostatnią linię, wtedy będziemy wiedzieć (po prostu sprawdź, czy ostatnia linia jest zakończona). W przeciwnym razie prawdopodobnie będziemy musieli odrzucić ostatnią linię za każdym razem, aby być bezpiecznym.

symbiont
źródło
10

Przypuszczalnie po prostu ten kod parsujący oczekiwał, że go tam będzie.

Nie jestem pewien, czy uznałbym to za „regułę” iz pewnością nie jest to coś, do czego stosuję się religijnie. Najbardziej rozsądny kod będzie wiedział, jak parsować tekst (w tym kodowanie) wiersz po wierszu (dowolny wybór zakończeń linii), z lub bez nowego wiersza w ostatnim wierszu.

Rzeczywiście - jeśli skończysz z nową linią: czy (teoretycznie) jest pusta linia końcowa między EOL a EOF? Do rozważenia ...

Marc Gravell
źródło
12
To nie jest reguła, to konwencja: linia to coś, co kończy się na końcu linii . Więc nie, nie ma „pustej linii końcowej” między EOL a EOF.
MestreLion
4
@MestreLion: Ale znak, o którym mowa, nie ma nazwy „end-of-line”, lecz „newline” i / lub „linefeed”. Separator linii, a nie terminator linii. Rezultatem jest ostatnia pusta linia.
Ben Voigt,
2
Żadne (rozsądne) narzędzie nie liczyłoby ostatniego EOL (CR, LF itp.) Pliku jako dodatkowej pustej linii. A wszystkie narzędzia POSIX nie będą liczyć ostatnich znaków pliku jako linii, jeśli nie będzie końca EOL. Bez względu na to, że nazwą znaku EOL jest „przesunięcie wiersza” lub „powrót karetki” (nie ma znaku o nazwie „nowa linia”), dla wszystkich praktycznych uczniów rozsądne narzędzia traktują je jako zakończenie linii , a nie jako separator linii .
MestreLion
2
@MestreLion, czy jesteś pewien, że terminator linii jest rozsądny? Złap kilku nieprogramiści i wykonaj krótką ankietę. Szybko zorientujesz się, że koncepcja linii jest bliższa koncepcji „separatorów linii”. Pojęcie „terminatora linii” jest po prostu dziwne .
Pacerier
4
@ Sahuagin: To nie jest mój pogląd, tak POSIX Standard definiuje linię. Pusty plik z 0 bajtów ma 0 wierszy, stąd nie EOL, a plik należy traktować jako mające tylko jeden, pusty wiersz, to nie wymaga EOL. Zauważ też, że jest to istotne tylko wtedy, gdy chcesz policzyć wiersze w pliku, ponieważ oczywiście każdy edytor pozwoli ci „przejść” do następnego (lub pierwszego) wiersza, niezależnie od tego, czy jest tam już EOL.
MestreLion,
10

Istnieje również praktyczny problem z programowaniem, w którym na końcu brakuje plików nowego wiersza: readWbudowane Bash (nie wiem o innych readimplementacjach) nie działa zgodnie z oczekiwaniami:

printf $'foo\nbar' | while read line
do
    echo $line
done

To drukuje tylkofoo ! Powodem jest to, że gdy readnapotka ostatni wiersz, zapisuje zawartość, $lineale zwraca kod wyjścia 1, ponieważ osiągnął EOF. To przerywa whilepętlę, więc nigdy nie osiągamy echo $lineczęści. Jeśli chcesz poradzić sobie z tą sytuacją, musisz wykonać następujące czynności:

while read line || [ -n "${line-}" ]
do
    echo $line
done < <(printf $'foo\nbar')

To znaczy, wykonaj echojeśli readnie powiodło się z powodu niepustej linii na końcu pliku. Oczywiście w tym przypadku na wyjściu będzie jeden dodatkowy nowy wiersz, którego nie było na wejściu.

l0b0
źródło
9

Dlaczego pliki (tekstowe) powinny kończyć się nową linią?

Dobrze wyrażone przez wielu, ponieważ:

  1. Wiele programów nie zachowuje się dobrze lub kończy się niepowodzeniem.

  2. Nawet programy, które dobrze obsługują plik, nie mają zakończenia '\n', funkcjonalność narzędzia może nie spełniać oczekiwań użytkownika - co może być niejasne w tym narożnym przypadku.

  3. Programy rzadko zabraniają finału '\n'(nie znam żadnego).


Ale to nasuwa kolejne pytanie:

Co kod powinien zrobić z plikami tekstowymi bez znaku nowej linii?

  1. Najważniejsze - nie pisz kodu, który zakłada, że ​​plik tekstowy kończy się znakiem nowej linii . Zakładanie, że plik jest zgodny z formatem, prowadzi do uszkodzenia danych, ataków hakerów i awarii. Przykład:

    // Bad code
    while (fgets(buf, sizeof buf, instream)) {
      // What happens if there is no \n, buf[] is truncated leading to who knows what
      buf[strlen(buf) - 1] = '\0';  // attempt to rid trailing \n
      ...
    }
    
  2. Jeśli końcowy ślad '\n'jest potrzebny, powiadom użytkownika o jego braku i podjętych działaniach. IOW, sprawdź format pliku. Uwaga: może to obejmować ograniczenie maksymalnej długości linii, kodowania znaków itp.

  3. Zdefiniuj jasno, dokument, sposób obsługi brakującego finału przez kod '\n'.

  4. Nie generuj , jak to możliwe, pliku, który nie ma zakończenia '\n'.

chux - Przywróć Monikę
źródło
4

Jest tu bardzo późno, ale napotkałem tylko jeden błąd w przetwarzaniu plików, który pojawił się, ponieważ pliki nie kończyły się pustym znakiem nowej linii. Przetwarzaliśmy pliki tekstowe sedi sedpomijaliśmy ostatni wiersz z danych wyjściowych, co powodowało nieprawidłową strukturę JSON i wysyłanie pozostałej części procesu do stanu awarii.

Wszystko, co robiliśmy, to:

Jest jeden przykładowy plik: foo.txtz jsonzawartością.

[{
    someProp: value
},
{
    someProp: value
}] <-- No newline here

Plik został utworzony w maszynie dla wdów, a skrypty okna przetwarzały ten plik za pomocą poleceń PowerShell. Wszystko dobrze.

Kiedy przetwarzaliśmy ten sam plik za pomocą sedpoleceniased 's|value|newValue|g' foo.txt > foo.txt.tmp

Nowo wygenerowany plik to

[{
    someProp: value
},
{
    someProp: value

i boom, zawiodło pozostałe procesy z powodu niepoprawnego JSON.

Dlatego zawsze dobrą praktyką jest kończenie pliku pustą nową linią.

Arpit
źródło
3

Zawsze miałem wrażenie, że reguła pochodzi z dni, kiedy parsowanie pliku bez kończącego nowego wiersza było trudne. Oznacza to, że skończyłbyś pisaniem kodu, w którym koniec linii został zdefiniowany przez znak EOL lub EOF. Po prostu łatwiej było założyć linię zakończoną EOL.

Jednak uważam, że reguła ta wywodzi się z kompilatorów C wymagających nowej linii. I jak wskazano w ostrzeżeniu kompilatora „Brak nowej linii na końcu pliku” , #include nie doda nowej linii.

he_the_great
źródło
0

Wyobraź sobie, że plik jest przetwarzany, gdy plik jest nadal generowany przez inny proces.

Może to mieć z tym związek? Flaga wskazująca, że ​​plik jest gotowy do przetworzenia.

Pippen_001
źródło
-4

Osobiście lubię nowe wiersze na końcu plików kodu źródłowego.

Może mieć pochodzenie w Linuksie lub we wszystkich systemach UNIX. Pamiętam, że wystąpiły błędy kompilacji (gcc, jeśli się nie mylę), ponieważ pliki kodu źródłowego nie zakończyły się pustą nową linią. Dlaczego zrobiono to w ten sposób, można się zastanawiać.

Użytkownik
źródło
-6

IMHO, to kwestia osobistego stylu i opinii.

W dawnych czasach nie wstawiałem tej nowej linii. Zapisana postać oznacza większą prędkość dzięki modemowi 14,4 tys.

Później umieściłem tę nową linię, aby łatwiej było wybrać ostatnią linię za pomocą Shift + Strzałka w dół.

Torben Gundtofte-Bruun
źródło