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?
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?
Odpowiedzi:
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 cat
plikiem 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, cat
znacznie 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
b.txt
i c.txt
?Oczywiście jest to możliwe do rozwiązania, ale musisz uczynić korzystanie z cat
bardziej 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.
cat
użyteczne i spójne.
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 .
wc -l
nie policzy ostatniego wiersza pliku, jeśli nie jest on zakończony znakiem nowej linii. Ponadto cat
połą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ć.
wc
ma już wspomniano ....
cat
i wc
)?
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 wc
podrę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 cat
rozmiar 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 cat
dwa pliki, których dane wyjściowe stają się tylko jedną linią zamiast dwóch? Innymi słowy, cat
robi to, co powinien.
man
Z cat
tylko wspomina czytanie wejście do EOF, a nie <nowalinia>. Zauważ, że -n
przełącznik cat
wypisze 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 filename
jednego, można zrobić awk '{x++}END{ print x}' filename
i 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 curl
d) - 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.
Może to być związane z różnicą między :
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
Niektóre narzędzia tego oczekują. Na przykład wc
oczekuje:
$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
wc
tego się nie spodziewa , ponieważ działa on w ramach definicji POSIX-a „linii”, w przeciwieństwie do intuicyjnego rozumienia „linii” przez większość ludzi.
wc -l
do drukowania1
w obu przypadkach, ale niektórzy ludzie mogą powiedzieć, że druga skrzynka powinna zostać wydrukowana 2
.
\n
terminatorze linii, a nie o separatorze linii, tak jak robi to POSIX / UNIX, to oczekiwanie na drugi przypadek wydrukowania 2 jest absolutnie szalone.
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”
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.
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 blame
sprawdzenie pliku, aby dowiedzieć się, kiedy ostatnio edytowano ten wiersz, pokaże dodanie tekstu, a nie zatwierdzenie przed tym, co naprawdę chciałeś zobaczyć.
\n
). Problem rozwiązany.
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:
wc -l
nie policzy ostatniej „linii”, jeśli nie kończy się na nowej linii.cat
po 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 c
spowoduje połączenie ostatniego wiersza pliku a
z pierwszym wierszem plikub
.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.
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 ...
Istnieje również praktyczny problem z programowaniem, w którym na końcu brakuje plików nowego wiersza: read
Wbudowane Bash (nie wiem o innych read
implementacjach) nie działa zgodnie z oczekiwaniami:
printf $'foo\nbar' | while read line
do
echo $line
done
To drukuje tylkofoo
! Powodem jest to, że gdy read
napotka ostatni wiersz, zapisuje zawartość, $line
ale zwraca kod wyjścia 1, ponieważ osiągnął EOF. To przerywa while
pętlę, więc nigdy nie osiągamy echo $line
częś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 echo
jeśli read
nie 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.
Dlaczego pliki (tekstowe) powinny kończyć się nową linią?
Dobrze wyrażone przez wielu, ponieważ:
Wiele programów nie zachowuje się dobrze lub kończy się niepowodzeniem.
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.
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?
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
...
}
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.
Zdefiniuj jasno, dokument, sposób obsługi brakującego finału przez kod '\n'
.
Nie generuj , jak to możliwe, pliku, który nie ma zakończenia '\n'
.
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 sed
i sed
pomijaliś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.txt
z json
zawartoś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ą sed
poleceniased '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ą.
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.
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.
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ć.
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ół.