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.
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.
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 natrue
oznacza, ż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
LF
naCRLF
.
-
Gdy ustawimy
core.autocrlf
nainput
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.