sysops.it

Outsourcing IT pozwoli ci prowadzić biznes taniej

info@sysops.it
+48 666 930 111

Nowości z bloga

TCP Fast Open

Opis modyfikacji stosu TCP, która może przyspieszyć prędkość ładowania popularnych stron od 4% do 41%.

Wyszukiwarki i Sphinx

Słowo wstępu o Sphinksie - silniku wspierającym wyszukiwanie pełnotekstowe.

Kwestia IPv6 NAT w Linuksie

IPv6 NAT w Linuksie - walka ideologii z rzeczywistością.

Perl w służbie admina

Perl

W zależności od rodzaju i złożoności zadania administrator korzysta z różnych narzędzi. Często jest to po prostu powłoka systemu operacyjnego, np. Bash. Gdy jednak złożoność skryptu bashowego przekracza pewien poziom, warto wykorzystać język skryptowy dający nieco większe możliwości. Jednym z nich jest Perl.

Za co cenię Perla? - m.in. za wyrażenia regularne i tablice asocjacyjne (tzw. hashe). Same hashe to temat na osobny artykuł. W tym momencie powiem tylko tyle, że istnieje powiedzenie, iż "pisząc skrypt w Perlu powinno się myśleć o hashach".

Wyrażenia regularne, czyli tzw. regexpy, są nieodłączną częścią pracy z Perlem. Prawdopodobnie najłatwiej jest opowiadać i zrozumieć, operując na przykładach. Oto jeden z nich.

Przykład nr 1

Potrzebuję przeparsować output polecenia ip address show dev eth0, odnaleźć wszystkie adresy przypisane do interfejsu eth0 i zbudować z nich domeny do zapytania revDNS (czyli z adresu np. 10.1.2.3 potrzebuję zbudować 3.2.1.10.in-addr.arpa).

Output polecenia ip address show dev eth0:

99: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether aa:98:96:df:eb:ea brd ff:ff:ff:ff:ff:ff inet 46.242.129.100/24 scope global eth0 inet 46.242.129.200/24 scope global secondary eth0

Widać dwa adresy IPv4 przypisane do interfejsu eth0.

Główna pętla skryptu perlowego mogłaby wyglądać w następujący sposób:

foreach (`ip address show dev eth0`) { if (/\s+inet\s+(\d+)\.(\d+)\.(\d+)\.(\d+)\/\d+/) { print "$4.$3.$2.$1.in-addr.arpa\n"; } }

W pierwszej linii przekazuję output polecenia powłoki do pętli foreach. Pętla iteruje po kolejnych liniach outputu.

Warunek if w drugiej linii to regexp, który:

  1. dopuszcza tylko te linie outputu, które mnie interesują (pasują do wzorca),
  2. kopiują do grup poszczególne wartości kolejnych oktetów adresów IP.

Powyższy regexp jest mocno uproszczony na potrzeby przykładu, jednak produkcyjne regexpy walidujące adresy IP zapisane w różnych konwencjach potrafią być dość złożone.

Trzecia linia wyświetla na standardowe wyjście (ekran) poszczególne oktety w odwróconej kolejności i dodaje string in-addr.arpa na końcu (konstrukcja domeny revDNS).

Wynik działania skryptu:

100.129.242.46.in-addr.arpa 200.129.242.46.in-addr.arpa

Bash od wersji 3.0 posiada dodatkowy operator =~ oraz zmienną BASH_REMATCH, umożliwiającą podobną mechanikę pętli.

Powyższa pętla w Perlu przepisana do Basha z wykorzystaniem tej samej zasady działania:

IFS=$'\n' for line in `ip address show dev eth0`; do if [[ "$line" =~ ^[[:space:]]+inet[[:space:]]+([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)/[[:digit:]]+ ]]; then echo "${BASH_REMATCH[4]}. ${BASH_REMATCH[3]}.${BASH_REMATCH[2]}. ${BASH_REMATCH[1]}.in-addr.arpa" fi done unset IFS

Skrypt wymaga dodatkowo modyfikacji wewnętrznej zmiennej IFS (Internal Field Separator) żeby for iterował po liniach zamiast po słowach.

Perlowe jednolinijkowce czyli tzw. one-linery.

Po pierwsze zdaję sobie sprawę, że praktycznie każde zadanie możliwe jest do wykonania w dowolnym języku skryptowym. Sęk w tym, że jedne z nich nadają się bardziej, a inne mniej do wykonania danego zadania.

Po drugie binarka Perla jest kilkukrotnie „cięższa” od chociażby awk-a, który jest głównym konkurentem w temacie jednolinijkowców. Jednak szybciej potrafię napisać odpowiednią składnię dla narzędzi, z których korzystam częściej, a w pracy administratora w sytuacjach krytycznych bywa, że szybciej znaczy lepiej.

Po trzecie uważam, że wykorzystanie Perla do pisania regexpow ułatwia pisanie bardziej dopasowanych i mniej „zachłannych” wyrażeń regularnych.

grep -P
pozwala na stosowanie perlowych regexpów.

Przykład nr 2

Potrzebuję sparsować zawartość wirtualnego pliku /proc/drbd (status urządzeń blokowych DRBD) i uzyskanie statusu lokalnego urządzenia blokowego (pierwsza wartość UpToDate przed slashem).

Zawartość /proc/drbd:

version: 8.3.11 (api:88/proto:86-96) built-in 0: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate A r----- ns:669580212 nr:0 dw:669602344 dr:800029245 al:17672132 bm:4216 lo:0 pe:0 ua:0 ap:0 ep:1 wo:d oos:0

Zakładam, ze wartości pól cs, ro i ds nie są stałe i nie powinienem ich literalnie wykorzystywać w wyrażeniu regularnym.

Jednolinijkowiec mógłby wyglądać w następujący sposób:

cat /proc/drbd | perl -lne 'print $1 if /^\s+0:\scs:\w+\s+ro:\w+\/\w+\s+ds:(\w+)\/\w+/'

Wartość zwracana, czyli status lokalnego dysku:

UpToDate

Wykorzystane parametry do Perla:
-l – dodaj znaki nowej linii na końcu linii,
-n – tryb pracy Perla działający w pętli: sczytuje kolejne linie jako <> i przypisuje je do specjalnej zmiennej perlowej $_,
-e – treść kodu Perla.

Przykład nr 3

Kolejnym przykładem jest sumowanie liczb w kolumnach:

perl -lane '$sum += $F[0]; END { print $sum }'

Sporym ułatwieniem jest w tym przypadku przełącznik -a, który sprawia, że linia jest dzielona i umieszczana w zmiennej tablicowej @F. Parametr -F może dodatkowo definiować delimiter.

Przykład nr 4

Poniżej praktyczny przykład sumowania wszystkich pakietów w kierunku przychodzącym do maszyny na podstawie łańcucha INPUT w iptables:

iptables -nvxL INPUT | perl -lane '$pkts += $F[0] if ($F[0] =~ /^\d+/); END {print $pkts}'

Dodatkowy warunek sprawdza czy wartość w pierwszej kolumnie jest liczbowa, gdyż mogą się tam pojawić ciągi tekstowe.

Przykład nr 5

Następnie po dwa warianty perlowych one-linerów realizujących konwersje adresu IPv4 do liczby całkowitej:

perl -le 'print unpack("N", pack("C4", split(/\./, "127.0.0.1")))' perl -le 'use IO::Socket; print unpack("N",inet_aton("127.0.0.1"))'

wynik:

2130706433

i odwrotnie:

perl -le 'print join ".", unpack("C4", pack("N", 2130706433))' perl -le 'use IO::Socket; print inet_ntoa(pack("N", 2130706433))'

wynik:

127.0.0.1

Pierwszy wariant wykorzystuje perlowe funkcje do konwersji danych pack/unpack. Drugi one-liner dodatkowo korzysta z gotowych funkcji inet_aton i inet_ntoa z modulu perlowego IO::Socket.

Przykład nr 6

Na koniec przykład operatora stanu ..:

cat file | perl -lne 'print if /-----BEGIN RSA PRIVATE KEY-----/../-----END RSA PRIVATE KEY-----/'

W wyniku działania tego polecenia, wyświetlona zostanie jedynie zawartość klucza prywatnego umieszczonego w pliku file, czyli wszystko między określonymi liniami włącznie. Reszta zostanie pominięta.

Służba
Dodano dn. 27 maja 2012 roku przez rob.