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:

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:

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:

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:

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]:

#!/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!