Przejdź do treści

EOL w prostych słowach

Każda plik tekstowy składa się z linii. Każda linia kiedyś się kończy. Ale skąd system wie, że linia się skończyła? Istnieje specjalny ZNAK, który informuje system, że znaki w linii właśnie się skończyły.

Baner promujący artykuł

Problem

I tym znakiem jest… no właśnie. Nie ma jednego znaku cross-platformowego, który by informował, że linia została “zakończona”. Wszystko ze względu na problem ze zróżnicowaniem poglądów na to, co to jest zakończenie linii.

Wyróżniamy 3 tryby EOL (end-of-line):

  • CR - Carriage Return
    znak końca linii m.in. na systemie: macOS (do wersji 9)
    reprezentacja w tablicy ASCII: \r
  • LF - Line Feed
    znak końca linii m.in. na systemach: Linux oraz macOS (od wersji 10)
    reprezentacja w tablicy ASCII: \n
  • CR LF - Carriage Return Line Feed
    znak końca linii m.in. na systemach: MS-DOS oraz MS Windows
    reprezentacja w tablicy ASCII: \r\n

Istnieje standard opisujący znaki w systemach komputerowych. Nazywa się on ASCII. To w nim zdefiniowane jest mapowanie znaków na liczby - 7 bitowe kody.

Specyfikacja znaku końca wiersza

Niestety, ale w standardzie ASCII nie ma opisanego znaku końca linii w systemach plików. Jest za to opisany standard znaku końca linii w komunikacji sieciowej!

W ubiegłym wieku, kiedy były ustalane protokoły sieciowe, przyjęto że wszystkie komunikaty sieciowe będą kończyły się sekwencją znakową CR LF. I bardzo dobrze! Tzn. nie jestem zwolennikiem tej sekwencji. Cieszę się z powodu przyjętej zasady. Wreszcie będzie porządek 🎉

Jak podejrzeć jakie jest zakończenie linii?

Windows

Świetnym sposobem na pogląd znaków specjalnych w pliku jest wykorzystanie sposobu opisanego tutaj, czyli posłużenie się narzędziem Notepad++.

W edytorze dostępna jest opcja View all characters (Wyświetl wszystkie znaki) dzięki której wszystkie znaki (również te specjalne) zostaną wyświetlone.

Zrzut ekranu z programu Notepad++
Dr. Palaniraja Copyright @ 2017

Linux & macOS

Na systemach unix-owych podglądanie zakończenia linii odbywa się za pomocą narzędzia od. Jego definicja w man pages:

OD(1)                   BSD General Commands Manual                  OD(1)

NAME
    od -- octal, decimal, hex, ASCII dump
SYNOPSIS
...

Aby przeczytać plik znak po znaku, wywołaj następujące polecenie:

Copy + paste

od -c ../.gitignore

Oczywiście zamiast pliku .gitignore wpisz ścieżkę do pliku jaki chcesz przeczytać. Narzędzie wyświetli KAŻDY ZNAK osobno (będą spore przerwy między znakami).

Wynik wywołania powyższego polecania załączam poniżej:

0000000    n   o   d   e   _   m   o   d   u   l   e   s   /  \n   n   p
0000020    m   -   d   e   b   u   g   .   l   o   g  \n
0000034

Jak widać w listingu, znaki specjalne zakończenia linii zostały zastąpione na reprezentację czytelniejszą dla użytkownika, czyli \n.

Różnica w Git-cie

Różne znaki końca linii powodują - poza niespójnością - też inne problemy. Osobiście uważam, że największy problem polega na wyświetlaniu różnic w systemach kontroli wersji, które źle skonfigurowane potrafią wyświetlić, że linijka została zmieniona, podczas gdy nie została.

Wszystko to się dzieje, z tego powodu, że pliki w systemie kontroli wersji zapisane są w formacie LF. Edytory na systemie Windows, podczas dowolnej zmiany automatycznie konwertują znaki końca linii na CRLF.

…czyli de facto nastąpiła pewna zmiana, dlatego też system kontroli wersji wyświetlił rożnicę.

Ignorowanie zakończenia linii w Git

Jak się ustrzec wyżej opisanego problemu? Bardzo łatwo!

Najpopularniejszy system kontroli wersji posiada opcję konfiguracyjną odnośnie znaków końca linii: core.autocrlf.

  • Opcja core.autocrlf ustawiona na true oznacza, że wszystkie pliki w powinny mieć zakończenie linii w stylu CRLF. Jeśli nie mają, odbędzie się konwersja znaków końca linii.

      git config --global core.autocrlf true
    

Uwaga

Po ustawieniu tej opcji na systemie macOS (domyślny jest LF) a następnie edycji dowolnego pliku, przed wyświetleniem różnicy otrzymamy komunikat:

warning: LF will be replaced by CRLF in README.md.
The file will have its original line endings in your working directory.

Spowodowane jest to konwersją znaku końca linii z LF na CRLF.

  • Gdy ustawimy core.autocrlf na input to żadna konwersja nie zostanie dokonana. Jest to domyślne ustawienie.

      git config --global core.autocrlf input
    

Istnieją dwie drogi normalizacji zakończenia linii:

  • systemowa
    ustawienie per użytkownik komputera (tylko w jednym pliku)
    konfiguracja zapisana w pliku ~/.gitconfig tudzież ~/.gitattributes
  • projektowa
    ustawienie per projekt (liczba plików z konfiguracją == liczbie projektów)
    konfiguracja zapisana w pliku .gitattributes w katalogu projektu

Polecana przeze mnie ścieżka to oczywiście ścieżka “systemowa”. Nie ma sensu konfigurować per projekt. Nie widzę żadnych profitów z tego powodu, a tylko minus, że w każdym projekcie musi znaleźć się plik .gitattributes.


Istnieją jeszcze inne opcje konfiguracyjne:

Copy + paste

git config --global core.eol lf|crlf|native
git config --global core.safecrlf true|false|warn

Nie będę się tutaj nad nimi rozwodził. Chętnych zapraszam do manuala;

git help config

Normalizacja za pomocą odpowiednich narzędzi

Czy istnieję możliwość konwersji między CRLF a LF? Oczywiście! Poniżej przedstawiam jak poradzić sobie z konwersją między tymi końcami linii na dwóch systemach: macOS oraz Linux.

macOS

Poniższe polecenia będą dostępne na systemie macOS po instalacji odpowiedniego narzędzia za pomocą polecenia: brew install dos2unix albo jeśli ktoś korzysta z innego managera paczek port install dos2unix.

Z CRLF na LF (macOS)

Konwersja z trybu Windows na tryb unix-owy:

Copy + paste

dos2unix README.md

Z LF na CRLF (macOS)

Konwersja z trybu unix-owego na tryb Windows:

Copy + paste

unix2dos README.md

Linux

Na systemie Linux aby skonwertować plik można wykorzystać narzędzie sed:

Z CRLF na LF (linux)

Copy + paste

sed -e 's/\r$//' inputfile > outputfile

Z LF na CRLF (linux)

Copy + paste

sed -e 's/$/\r/' inputfile > outputfile

Więcej przykładów jak skonwertować pliki znajduje się tutaj

Historia end-of-line

W archiwum RFC opisana jest historia ustalania znaku zakończenia linii. Jeśli chciałbyś poczytać to wejdź tutaj.