This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Witajcie ponownie. Motywacją do stworzenia tego wpisu była moja niedawna “przygoda” z bazą danych jednego z klientów. Stuprocentowego rozwiązania problemu nie znalazłem, aczkolwiek opracowane przeze mnie “obejście” całkiem nieźle zamyka temat. Zapraszam do lektury.

Wstęp.

Otóż, dostałem w swoje ręce dosyć sporą bazę danych, w której, mówiąc krótko “nie było polskich znaków”, a technicznie rzecz biorąc jej dane były zakodowane w ISO-8859-1 (tzn. to też nie było takie oczywiste, aczkolwiek finalnie doszedłem do takiej właśnie konkluzji). Zadanie było teoretycznie proste - “naprawić” rzeczoną bazę konwertując dane do kodowania UTF-8. Problem polegał na tym, że ktoś nieopatrznie wprowadził dane w kodowaniu Latin1 [ISO-8859-1] jako UTF-8 do tabel oznaczonych jako “latin1” i żadne prośby ani groźby nie mogły zmusić danych do odwrócenia tego procesu.

Napisałem wyżej, że kodowanie, w jakim były zapisane dane w bazie danych nie było oczywiste, ponieważ próbując wykorzystać różne narzędzia do konwersji otrzymywałem w najlepszym przypadku “takie same krzaki”, jak to, co już było zapisane. Wszelkiego rodzaju próby konwersji pomiędzy różnymi kombinacjami kodowań Latin1 [ISO-8859-1], Latin2 [ISO-8859-2], UTF-8, CP-1250, CP-1252 nie powodowały absolutnie żadnej poprawy sytuacji.

W tym przypadku musiałem sięgnąć po cięższą artylerię - czyli tradycyjnie wykombinować własne rozwiązanie. Jeszcze nie było takiego problemu którego bym nie rozwiązał tym sposobem. ;]

Rozwiązanie.

Cóż takiego "wykombinowałem"? Otóż, po konsultacjach z resztą zespołu dowiedziałem się, że ogólną konwersję mogę uprościć do podmiany tylko i wyłącznie polskich znaków krzaków na polskie znaki, jako że reszta jest po prostu "nieważna". W tym przypadku szukanie rozwiązania zawęziło się do znalezienia w miarę optymalnego sposobu na przywrócenie konkretnych kilkunastu liter. Wyeksportowałem zrzut bazy danych do oddzielnego pliku i zacząłem przeszukiwać Internet w poszukiwaniu rozwiązania.

Przeglądając teksty zapisane w bazie danych szybko znalazłem “krzaki” i stworzyłem sobie tablicę ich odpowiedników w “normalnych” literach. Oto i ona:

W tym momencie potrzebne było jedynie narzędzie, które pozwoli dokonać sensownie szybkiej podmiany błędnych znaków na ich poprawne odpowiedniki.

Z pomocą przyszło narzędzie korzystające z tego, co najlepsze w Perlu - z wyrażeń regularnych, zwanych też skrótowo regexpami:

A imię jego - Stream EDitor.
Użyjemy zatem linuksowego narzędzia sed. Jego podstawowa składnia to:
1
sed [regexp] [filename]

której wywołanie spowoduje “przetrawienie” zawartości pliku [filename] przy użyciu wyrażenia regularnego [regexp] i zapisaniu wyników na standardowe wyjście. Aby wynik procesu nie poszedł do /dev/null, możemy przekazać go do pliku:

1
sed [regexp] [filename] > [output]

który zapisze nam wyniki do pliku o nazwie [output]. Zauważyliście jednak na pewno, że wymagałoby to napisania oddzielnego polecenia dla każdego ze znaków. Każde polecenie musiałoby wykonać żmudną pracę i zapisać wyniki do pliku tymczasowego, który dopiero po kilkunastu iteracjach stałby się plikiem wynikowym. Na szczęście sed posiada bardzo interesujący przełącznik -e - pozwala on na przekazanie dowolnej ilości wyrażeń regularnych, które zostaną wzięte pod uwagę przy przetwarzaniu pliku. Składnia wygląda następująco:

1
sed -e [regexp] -e [regexp] -e [regexp] (...) [filename] > [output]

W tym wypadku moje polecenie w każdym bloku -e zawierało instrukcję podmiany błędnego znaku:

1
sed -e 's/±/ą/g' -e 's/æ/ć/g' -e 's/ê/ę/g' -e 's/³/ł/g' -e 's/ñ/ń/g' -e 's/ó/ó/g' -e 's/¶/ś/g' -e 's/¿/ż/g' -e 's/¼/ź/g' -e 's/¡/Ą/g' -e 's/Æ/Ć/g' -e 's/Ê/Ę/g' -e 's/£/Ł/g' -e 's/Ñ/Ń/g' -e 's/Ó/Ó/g' -e 's/¦/Ś/g' -e 's/¯/Ż/g' -e 's/¬/Ź/g' -e 's/latin1/utf8/g' [inputfile] > [outputfile]

a cały skrypt [convert.sh]:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

EXPECTED_ARGS=2
E_BADARGS=65

if [ $# -ne 2 ]
then
 echo "Usage: `basename $0` source target"
 exit $E_BADARGS
fi
sed -e 's/±/ą/g' -e 's/æ/ć/g' -e 's/ê/ę/g' -e 's/³/ł/g' -e 's/ñ/ń/g' -e 's/ó/ó/g' -e 's/¶/ś/g' -e 's/¿/ż/g' -e 's/¼/ź/g' -e 's/¡/Ą/g' -e 's/Æ/Ć/g' -e 's/Ê/Ę/g' -e 's/£/Ł/g' -e 's/Ñ/Ń/g' -e 's/Ó/Ó/g' -e 's/¦/Ś/g' -e 's/¯/Ż/g' -e 's/¬/Ź/g' -e 's/latin1/utf8/g' $1 > $2

Wyjaśnienia nie potrzeba, jeśli podacie przy uruchomieniu za mało parametrów sam Wam o tym przypomni. ;]

Tym właśnie sposobem udało mi się doprowadzić bazę danych do stanu użyteczności publicznej. Przy okazji podszkoliłem swoje umiejętności z zakresu posługiwania się narzędziem sed, co na pewno zaprocentuje w przyszłości.

Podsumowanie.

Jeśli znacie inne, lepsze [nie wątpię, że istnieją] sposoby rozwiązania tego typu problemu, chętnie przedyskutuję je z Wami w komentarzach, do których pisania tradycyjnie zachęcam. Do zobaczenia wkrótce w kolejnym wpisie!