Każdy 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 ZNAK specjalny, który informuje system, że znaki w linii właśnie się skończyły.
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ę ASCII.
To w nim zdefiniowane jest mapowanie znaków na liczby (7 bitowe).
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, ale cieszę się z powodu przyjętego standardu.
Jak podejrzeć jakie jest zakończenie linii?
MS 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 dzięki której wszystkie
(również te specjalne) znaki zostaną wyświetlone.
Linux & macOS
Na systemach unix-owych podglądanie zakończenia linii odbywa się za pomocą
narzędzia od (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 MS 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.autocrlfustawiona natrueoznacza, że wszystkie pliki w powinny mieć zakończenie linii w styluCRLF. 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
LFnaCRLF.
-
Gdy ustawimy
core.autocrlfnainputto ż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)
- albo konfiguracja zapisana w pliku
~/.gitconfigalbo~/.gitattributes
-
- projektowa
- ustawienie per projekt (liczba plików z konfiguracją == liczbie projektów)
- albo konfiguracja zapisana w pliku
.gitattributesw katalogu projektu
Polecana przeze mnie ścieżka to “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 pochylał. 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.
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.