.pl .en .de .ru
Interdyscyplinarny blog badawczy pracowników Zakładu Infolingwistyki i Zakładu Przetwarzania Języka Naturalnego UAM

Anatomia pliku tekstowego — część I (wstęp)

Filip Graliński
10/16

Plik tekstowy? Cóż może być prostszego niż plik tekstowy?

Ala ma kota.

Widziałem ludzi walących głową w monitor. Widziałem zmarnowaną prace i zmarnowane tysiące dolarów. Widziałem całe pokoje pracowników kręcących się w kółko.

Wszystko dlatego że ktoś założył bezmyślnie, że plik tekstowy to najprostsza rzecz na świecie.

W tym cyklu wpisów odkryjemy tajemnice plików tekstowych i poznamy pułapki, jakie czyhają między wierszami.

Czy jesteś programistą, czy nie, ręczę, że informacje tutaj zawarte będą dla Ciebie, drogi Czytelniku, przydatne. Nadejdzie dzień, kiedy poślesz mi bombonierkę w podziękowaniu za to, że przeczytane tutaj informacje uratowały Ci czas i pieniądze, a może i życie.

Zera i jedynki

Tak naprawdę, komputer nie zna liter, ani nawet — w przeciwieństwie do nas, dziesięciopalczastych ssaków — dziesięciu cyfr. Podstawą działania komputera jest elementarna dystynkcja, binarna opozycja: zero i jeden, prawda i fałsz, tak — tak, nie — nie, jin i jang, kobieta i mężczyzna, byt i niebyt. (Prawdę mówiąc, tak naprawdę komputer — jak każde urządzenie fizyczne — operuje na ciągłych wartościach sygnału, dopiero w naszych głowach istnieją progi, powyżej których sygnał interpretujemy jako jedynkowy, a poniżej — jako zerowy, ma to przerażające konsekwencje filozoficzne, dla naszego spokoju nie będziemy się w to wgłębiać).

Wszystko w komputerze ostatecznie zapisywane jest jako bardzo długi ciąg zer i jedynek: IX symfonia Beethovena, film z wakacji, program księgowy, zdjęcie gnijącego liścia. Również plik tekstowy.

A w jaki sposób zakodować za pomocą zer i jedynek litery składające się na plik tekstowy? Może tak: weźmy kształt litery i rozłóżmy go w rastrze.

Jeśli każde zaczernione pole będziemy kodować jedynką, a puste — zerem, otrzymamy kod 01101001100111111001 (powiedzmy, że idziemy wierszami od góry do dołu).

Nie, to nie jest dobry pomysł. Przecież chcemy — w prostym pliku tekstowym — abstrahować od kształtu liter. Nie chcemy, żeby binarne zakodowanie znaków zależało od tego, czy w edytorze mamy ustawiany taki czy inny font. To, czego potrzebujemy, to arbitralne kodowanie. Po prostu przypiszemy znakom arbitralne kody — arbitralne, czyli równie dobre, jak każde inne, nic w świecie fizycznym (ani nawet Bóg) nie nakazuje nam takiego czy innego kodowania, ważne, żebyśmy wszyscy razem je konsekwentnie stosowali. (Podobnie jak arbitralnie samogłoskę otwartą przednią niezaokrągloną oznaczamy kształtem A, spółgłoskę zaś zwartą dwuwargową dźwięczną — kształtem B).

Bity i bajty

Długie ciągi zer i jedynek są trochę nieporęczne. Dlatego sekwencje zer i jedynek (bitów) układa się w większe paczki po 8 sztuk, czyli bajty (właściwie należałoby mówić o oktetach, bo historycznie bajty nie zawsze były 8-bitowe, ale nie bądźmy drobiazgowi). Oto przykładowy bajt:

01001110

Taki bajt można traktować jako liczbę zapisaną dwójkowo (binarnie). Nie jest to oczywiście liczba „milion tysiąc sto dziesięć” (nie interpretujemy, broń Boże, 0 i 1 jako cyfr dziesiętnych), lecz inna. Jaka? Musimy oprzeć się na potęgach dwójki (zamiast dziesiątki):

0 * 27 + 1 * 26 + 0 * 25 + 0 * 24 + 1 * 23 + 1 * 22 + 1 * 21 + 0 * 20 =
= 0 * 2*2*2*2*2*2*2 + 1 * 2*2*2*2*2*2 + 0 * 2*2*2*2*2 + 0 * 2*2*2*2 + 1 * 2*2*2 + 1 * 2*2 + 1 * 2 + 0 * 1 =
= 0 * 128 + 1 * 64 + 0 * 32 + 0 * 16 + 1 * 8 + 1 * 4 + 1 * 2 + 0 * 1 =
= 64 + 8 + 4 + 2 =
= 78

więc 01001110 dla komputera to 78 dla małp człekokształtnych. Informatycy to trochę osobny gatunek — wolą zapis szesnastkowy niż dziesiętny (dlaczego? bo 16 jest potęgą dwójki, jedna cyfra szesnastkowa to dokładnie 4 bity, a jeden bajt to dokładnie dwie cyfry szesnastkowe). W zapisie szesnastkowym (heksadecymalnym, jak kto woli) używamy 10 cyfr arabskich plus 6 liter (A, B, C, D, E, F).

Nasz bajt zapisany szesnastkowo to:

4E

Dlaczego? Bo 4 * 16 + 14 * 1 = 78. (Skąd tutaj 14? A reprezentuje 10, B — 11, C — 12, D — 13, E — 14!).

Ile różnych liczb może reprezentować 8 bitów? „Ostatni” bajt to: 11111111, czyli dziesiętnie 255 (szesnastkowo: FF), a zatem mamy 256 (nie 255 — zaczynamy od zera!) możliwości. (Inaczej: bo 28 = 2*2*2*2*2*2*2*2 = 256, podobnie jak w Księdze przemian — też opartej na kodzie binarnym — mamy dla 6 bitów 26=64 możliwości).

Kod ASCII

Wróćmy do liter.

Ponieważ bajt jest podstawową (większą niż bit) jednostką, na której działa komputer, to jest rzeczą rozsądną reprezentować litery (i inne znaki) jako pojedyncze bajty. 256 możliwości wystarczy z naddatkiem dla małych i wielkich liter alfabetu łacińskiego (właściwie angielskiego — Rzymianie nie znali przecież J i W oraz nie odróżniali U i V).

Notabene, rozróżniamy tutaj małe i wielkie litery — małe „a” to inny znak niż duże „A” (a zatem znakom tym przypiszemy inne kody). Innymi słowy, „wielkość” (ładniej: kasztowość — zecer wyciągał małe litery z dolnej kaszty, wielkie zaś z górnej) liter ma znaczenie.

No dobrze, ale jakie konkretnie ośmiobitowe kody przypiszemy literom? Najpowszechniej stosowane obecnie kodowanie znaków to ASCII (American Standard Code for Information Interchange, czyli Amerykański Standardowy Kod na potrzeby Wymiany Informacji). Dawno temu istniały zupełnie inne standardy kodowania znaków (np. EBCDIC), ale odeszły już do lamusa.

W starych dobrych czasach (lata 80. i 90. ubiegłego wieku) umieszczanie na końcu tabelki kodów ASCII było rutynową metodą powiększania książki informatycznej (więcej stron, wyższa cena, więcej pieniędzy dla autora!). Ja też nie mogę się powstrzymać, by nie przytoczyć w całości tabeli kodów ASCII:

 binarnie    dziesiętnie    szesnastkowo    znak
00000000 0 00 znak specjalny
00000001 1 01 znak specjalny
00000010 2 02 znak specjalny
00000011 3 03 znak specjalny
00000100 4 04 znak specjalny
00000101 5 05 znak specjalny
00000110 6 06 znak specjalny
00000111 7 07 znak specjalny
00001000 8 08 znak specjalny
00001001 9 09 tabulacja
00001010 10 0A przejście do nowego wiersza
00001011 11 0B znak specjalny
00001100 12 0C znak specjalny
00001101 13 0D powrót karetki
00001110 14 0E znak specjalny
00001111 15 0F znak specjalny
00010000 16 10 znak specjalny
00010001 17 11 znak specjalny
00010010 18 12 znak specjalny
00010011 19 13 znak specjalny
00010100 20 14 znak specjalny
00010101 21 15 znak specjalny
00010110 22 16 znak specjalny
00010111 23 17 znak specjalny
00011000 24 18 znak specjalny
00011001 25 19 znak specjalny
00011010 26 1A znak specjalny
00011011 27 1B znak specjalny
00011100 28 1C znak specjalny
00011101 29 1D znak specjalny
00011110 30 1E znak specjalny
00011111 31 1F znak specjalny
00100000 32 20 spacja
00100001 33 21 !
00100010 34 22
00100011 35 23 #
00100100 36 24 $
00100101 37 25 %
00100110 38 26 &
00100111 39 27
00101000 40 28 (
00101001 41 29 )
00101010 42 2A *
00101011 43 2B +
00101100 44 2C ,
00101101 45 2D -
00101110 46 2E .
00101111 47 2F /
00110000 48 30 0
00110001 49 31 1
00110010 50 32 2
00110011 51 33 3
00110100 52 34 4
00110101 53 35 5
00110110 54 36 6
00110111 55 37 7
00111000 56 38 8
00111001 57 39 9
00111010 58 3A :
00111011 59 3B ;
00111100 60 3C <
00111101 61 3D =
00111110 62 3E >
00111111 63 3F ?
01000000 64 40 @
01000001 65 41 A
01000010 66 42 B
01000011 67 43 C
01000100 68 44 D
01000101 69 45 E
01000110 70 46 F
01000111 71 47 G
01001000 72 48 H
01001001 73 49 I
01001010 74 4A J
01001011 75 4B K
01001100 76 4C L
01001101 77 4D M
01001110 78 4E N
01001111 79 4F O
01010000 80 50 P
01010001 81 51 Q
01010010 82 52 R
01010011 83 53 S
01010100 84 54 T
01010101 85 55 U
01010110 86 56 V
01010111 87 57 W
01011000 88 58 X
01011001 89 59 Y
01011010 90 5A Z
01011011 91 5B [
01011100 92 5C \
01011101 93 5D ]
01011110 94 5E ^
01011111 95 5F _
01100000 96 60 `
01100001 97 61 a
01100010 98 62 b
01100011 99 63 c
01100100 100 64 d
01100101 101 65 e
01100110 102 66 f
01100111 103 67 g
01101000 104 68 h
01101001 105 69 i
01101010 106 6A j
01101011 107 6B k
01101100 108 6C l
01101101 109 6D m
01101110 110 6E n
01101111 111 6F o
01110000 112 70 p
01110001 113 71 q
01110010 114 72 r
01110011 115 73 s
01110100 116 74 t
01110101 117 75 u
01110110 118 76 v
01110111 119 77 w
01111000 120 78 x
01111001 121 79 y
01111010 122 7A z
01111011 123 7B {
01111100 124 7C |
01111101 125 7D }
01111110 126 7E ~
01111111 127 7F znak specjalny

(Znaki specjalne to niedrukowalne znaki sterujące, nie musimy się nimi w tej chwili przejmować).

Jeszcze raz powtórzmy, kod jest arbitralny — to, że A ma kod 65, to wynik przypadkowych decyzji amerykańskich standaryzatorów w latach 60.

Nasuwają się dwa pytania: dlaczego wykorzystano tylko 128 możliwości spośród 256 oferowanych przez 8 bitów? i co z polskimi znakami?

128 możliwości, bo ASCII jest kodem 7-bitowym (nie 8-bitowym!). Po prostu w zamierzchłych czasach, gdy powstawał kod ASCII, istniały jeszcze urządzenia 7-bitowe, więc twórcy tego standardu postanowili się ograniczyć do 7 bitów. Ludzkość miała od tego czasu różne pomysły, jak wykorzystać te pozostałe 128 kodów (od 128 do 255) i często te 8-bitowe kodowania określa się mianem ASCII, ale to nadużycie (bo precyzyjniej były to rozszerzenia ASCII).

A polskie znaki (ą, ć, ę, ł, ń, ó, ś, ź, ż i ich odpowiedniki z górnej kaszty)? Niestety, informatyka rozwinęła się w USA, gdzie nikt nie przejmował się polskimi diakrytykami (nie obrażajmy się — ani grażdanką, ani hangulem, ani nawet hebrajszczyzną). Gdyby II wojna światowa wybuchła parę lat później niż wybuchła, Rejewski, Różycki, Zygalski, Ulam, Tarski i polscy radioinżynierowie skonstruowaliby pierwszy komputer i mielibyśmy standard z polskimi znakami, niestety losy potoczyły się inaczej.

Łącząc obie kwestie, naturalny pomysł to upchać polskie znaki wśród kodów od 128 do 255. To rzeczywiście uczyniono (na dwadzieścia parę sposobów, niech żyje tradycyjna polska anarchia!). Poznamy jednak lepszy standard, dzięki któremu możemy pisać teksty nie tylko z ogonkami, lecz także z (prawie) wszystkimi znakami świata. Wrócimy do sprawy w III odcinku naszego cyklu.

Weźmy prosty plik tekstowy bez ogonków:

Ala ma kota.

Jak komputer widzi ten plik? A tak:

010000010110110001100001001000000110110101100001001000000110101101101111011101000110000100001010

Mało czytelnie? Zaznaczmy teraz chociaż granice bajtów:

01000001|01101100|01100001|00100000|01101101|01100001|00100000|01101011|01101111|01110100|01100001|00001010

Albo zapiszmy bajty dziesiętnie:

65, 108, 97, 32, 109, 97, 32, 107, 111, 116, 97, 10

i szesnastkowo:

41, 6C, 61, 20, 6D, 61, 20, 6B, 6F, 74, 61, 0A

Zgadza się? Proszę po kolei skonfrontować liczby z tabelką — powinno się zgadzać.

Zauważmy, że spacja też jest traktowany jako osobny znak (tak jak litera) i ma swój kod (32 dziesiętnie). Na końcu naszego pliku jest jeszcze tajemniczy znak o kodzie 10 — to znak końca wiersza. Z końcem wiersza sprawa jest bardziej skomplikowana, poświecimy temu osobny odcinek naszego cyklu.

Szesnastkowy zrzut

Jak poprosić komputer, żeby sprawdził, jak dokładnie zakodowany jest plik tekstowy? (Jest to pierwsza rzecz, jaką powinniśmy zrobić, jeśli mamy jakiś tajemniczy problem z plikiem tekstowym — być może komputer widzi go inaczej niż my). W systemie Linux służy do tego…

Tu dygresja: ludzie, którzy profesjonalnie przetwarzają tekst, pracują pod Linuksem. Po prostu przetwarzanie tekstu w Windowsie to udręka, to jak wykuwanie miecza w kaftanie bezpieczeństwa. Jeśli nie masz Linuksa pod ręką, czytaj sobie spokojnie dalej, ale warto pomyśleć w wolnej chwili o zainstalowaniu tego systemu operacyjnego.

… więc w Linuksie do zrzucania zawartości pliku w postaci szesnastkowej służy polecenie hexdump („hex” — „heksadecymalnie”, „dump” — „zrzut”), najlepiej z opcją -C, która wyświetli szesnastkowe kody bajt po bajcie. Oto przykładowe komendy wydane z wiersza poleceń:

# tworzymy na szybko plik tekstowy
$ echo 'Ala ma kota' > plik.txt
# zrzucamy szesnastkowo
$ hexdump -C plik.txt
00000000  41 6c 61 20 6d 61 20 6b  6f 74 61 0a              |Ala ma kota.|
0000000c

W pierwszej kolumnie podano adres (też zapisany szesnastkowo), ale potem zgadza się z tym, co podałem wyżej.

Plik tekstowy w zapisie szesnastkowym (który szybko można przełożyć na zapis binarny) może pokazać każdy przyzwoity edytor tekstu. Na przykład w moim ulubionym edytorze Emacs służy do tego polecenie hexl-mode. Wygląda to tak (pierwszy wiersz zawiera numery bajtów zapisane szesnastkowo):


W następnym odcinku: piekło końca wiersza.

Tagi