Przydatne skrypty czyli groch z kapustą

Skrypt który umożliwia łatwiejsze przeglądanie logów serwera, zamienia ich kolejność w ten sposób, że ostatnie wpisy (w liczbie 100) pojawiają się na górze pliku.

writer = new File("serverREVERT.log").newWriter("UTF-8", false)
lines=new File("server.log").readLines().asList()

max=lines.size()-1
min=lines.size()-200

for(index in max..min){
writer.writeLine(lines.get(index--))
}
writer.close()


WinTail

Kod wyświetlający na konsoli ostatnie 200 linii z pliku logów serwera mógłby wyglądać tak:

//pathToLogFile="C:\\JAVA\\glassfish\\domains\\domain1\\logs\\server.log"
pathToLogFile="C:\\JAVA\\jboss-4.2.1.GA\\server\\default\\log\\server.log"
linesNumber=100

"cmd /c cls".execute()
lines=new File(pathToLogFile).readLines().asList()
max=lines.size()-1
min=lines.size()-linesNumber
if(min<0)min=0>Można go rozbudować o kilka dodatkowych linii, ktorych zadaniem jest wyswietlenie blokow oznaczonych przez debuggera, oczywiscie sami musimy zadbać o to, by bloki które chcemy wyświetlić były oznaczone w odpowiedni sposób. W moim kodzie blok SQL, który chce monitorować otaczam tagami [SQLDEBUG] i [END-SQLDEBUG]. Skrypt jest wykonywany jesli zmienna DEBUG jest ustawiona na 1.


DEBUG=1
if(DEBUG==1){
pattern=/\[SQLDEBUG\].*\[END-SQLDEBUG\]/

print "\n\n ************************************************************"
print "\n ************** OZNACZONE KOMUNIKATY DEBUGGERA **************"
print "\n ************************************************************\n"
content=new File(pathToLogFile).getText();
content.eachMatch(pattern){ match->
print "\n"+match[0]
}
print "\n ************************************************************\n"
}


Skrypt zmieniający duże litery na małe w danym katalogu


groovy -e "new File(\".\").eachFile{it.renameTo(new File(it.getName().toLowerCase()))}"



Skrypt listujący pliki z drzewa podkatalogów



outF=new File("output.log").newWriter("UTF-8", false)
new File(".").eachFile{
if(it.getName().matches(".*svn.*")||it.getName().matches(".*prop.*")||it.getName().matches(".*base.*"))
return
if (it.isDirectory())
listujKatalog it
}
listujKatalog new File(".")



void listujKatalog(File aKat){
outF.writeLine("--"+aKat.getName())
aKat.eachFile{
if(it.getName().matches(".*doc.*")){
outF.writeLine(it.getName())
}
}
}
outF.close()

Rekurencyjne przetwarzanie drzewa katalogów

Często okazuje się że istnieje potrzeba wykonania jakiejś operacji na wszystkich zbiorach które znajdują się na dysku i umieszczone są w różnych katalogach, jako elementy potomne mające wspólny korzeń. Wtedy wykorzystać można bardzo wygodny mechanizm przetwarzania rekurencyjnego. Jako przykład niech posłuży skrypt, który z drzewa katalogów usuwa wszystkie katalogi (wraz z zawartością) .svn zawierające pliki związane z obsługą svn.


import org.apache.commons.io.FileUtils

def basedir="kat1\\kat2" //SCIEZKA_DO_ROOT

outputLog=new File("output.log").newWriter("UTF-8", false)

dir = new File(basedir)
fileList=[]
dir.eachFileRecurse{ f ->
if((f.name=='.svn')&&(f.isDirectory())&&(f.exists())){
fileList.add(f)
}
}


for(mf in fileList){
outputLog.writeLine( "Usunieto: "+mf.canonicalPath)
FileUtils.deleteDirectory(mf)
}

outputLog.writeLine( "Calkowita liczba usunietych:"+fileList.size())
outputLog.close()

Do poprawnego działania konieczne jest sciągnięcie biblioteki commons.io.*, a następnie umieszczenie pliku commons-io-xxxxx.jar w katalogu GROOVY_HOME\lib

Groovy a wyrażenia regularne

Wprowadzenie


Teraz gdy już przebrnąłem przez podstawowe metody WE/WY zacząłem kombinować jak wykorzystać Grooviego do przetwarzania plików tekstowych. A wiadomo, że gdy mamy na myśli język skryptowy i ocenę jego przydatności we wspomnianym zakresie to koniecznie należy przyjrzeć się w jaki sposób obsługuje on wyrażenia regularne.

Wydaje mi się że o mocy która kryje się w wyrażeniach regularnych nikogo przekonywać nie trzeba. Naprawdę nie mogę zrozumieć, dlaczego tak mało edytorów tekstu tworzonych pod okna wspiera i wykorzystuje ten potężny mechanizm wyszukiwania i przetwarzania wzorców. Ale do rzeczy.

Do przetwarzania wyrażeń regularnych Groovy wykorzystuje biblioteki dostarczane z JDK. Oczywiście wprowadza szereg udogodnień, które sprawiają, że przeszukiwanie tekstów pod kątem występowania w nich jakiegoś wzorca jest dużo prostsze i przyjemniejsze niż w samej Javie.
Dzięki mechanizmom wyrażeń regularnych Groovy pozwala na:
  • określenie czy (a jeśli tak to ile razy) w tekście wystąpił ciąg znaków zgodny ze wzorcem
  • wykonanie określonych operacji na dopasowanym łańcuchu znaków
  • dokonanie zastąpień dopasowanych łańcuchów znakowych
  • podział dopasowanego ciągu na podciągi

Definiowanie wzorców



Przetwarzanie tekstów z wykorzystaniem wyrażeń regularnych wiąże się z koniecznością definiowania wzorców dopasowań. Są to instrukcje dla mechanizmu przetwarzającego zawierające informacje czego ma szukać i w jaki sposób to przetwarzać. Mają one postać ciągów znakowych, zawierających pewne symbole o specjalnym znaczeniu. Dzięki nim możemy określić pewne grupy znaków, liczb, które mają zostać wyszukane w tekście. Najważniejsze z nich to:

  • . (kropka) - dowolny znak
  • ^ - początek linii
  • $- koniec linii
  • \d- dowolna liczba
  • \D- dowolny znak nie będący liczbą
  • \s- biały znak
  • \S- dowolny znak nie będący znakiem białym
  • \w- słowo
  • \W-znaki nie układające się w słowa
  • \b-granica słowa
  • ()-operator grupowania
  • (a|b)-warunek logiczny "lub" (a lub b)
  • * - zero lub więcej wystąpień poprzedzającego znaku(grupy)
  • + - jeden lub więcej wystąpień poprzedzającego znaku(grupy)
  • ? - dokładnie jedno wystąpienie poprzedzającego znaku(grupy)
  • x{m,n} - co najmniej m i co najwyżej n znaku(grupy) poprzedzajacej
  • x{m} - dokladnie m wystapien znaku (grupy) poprzedzajacej
Wzorce dopasowań, rozpoznawane przez Grooviego ograniczane są znakami "/"(slash). Przykładowe definicje masek wyglądają następująco:
  • /\d\d-\d\d-\d\d\s?\d\d:\d\d/ lub w uproszczeniu \d{2}-\d{2}-\d{2}\s?\d{2}:\d{2} - dopasuje się do wszystkich dat zapisanych w formacie "liczba liczba-liczba liczba- liczba liczba SPACJA liczba liczba:liczba liczba", czyli np. 01-02-07 10:22
  • /href=".*?"/ - maska pozwalająca na wyszukanie w tekście wystąpień adresów URL
Pod adresem http://www.nvcc.edu/home/drodgers/ceu/resources/test_regexp.asp znaleźć można proste narzędzie, które służy do testowania wrażeń regularnych.

Dopasowanie zachłanne

Operatory rozszerzające (takie jak * i +) mają pewną cechę, o której zawsze należy pamiętać. Chodzi o to, że dążą one do dopasowania jak najszerszego możliwego ciągu znakowego pasującego do zadanego wzorca.

Spójrzmy na następującą maskę: /href=".*"/.
Należy rozumieć ją następująco:
dopasuj się do ciągu znaków, rozpoczynającego się słówkiem "href", po którym występuje znak "=" i cudzysłów ("), w jego wnętrzu ma znaleźć się zero lub więcej dowolnych znaków kończących się cudzysłowem.

Wydawać by się mogło, że taki wzorzec prawidłowo wyciągnie nam wszystkie adresy stron w postaci href="adres". Tak rzeczywiście by było gdyby nie zachłanność dopasowania operatora *.
Problem występuje w sytuacji gdy wyrażenie regularne zostanie zastosowane np. do następującego tekstu:

To jest (href="www.pierwszy.adres.url") pierwszy rozdzial (href="www.drugi.adres.url")

Dopasowanie nastąpi w następujący sposób: znaleziony zostanie następujący ciąg znaków: href=", i jak dotąd wszystko jest zgodne z oczekiwaniami, jednak potem zaczynają się problemy, bo każdy znak, który pojawi się potem, aż do napotkania OSTATNIEGO cudzysłowu zostanie uznane jako pasujące do ".*", a więc wynikiem zastosowania reguły wzorca będzie ciąg znaków:

href="www.pw.edu.pl") pierwszy rozdzial (href="www.bla.bla.pl"

Czy o to mi chodziło ? Niekoniecznie....

SPOSÓB OGRANICZENIA ZACHŁANNOŚCI:

Aby ograniczyć zachłanność operatora, należy we wnętrzu reguły, bezpośrednio po znaku "*", a przed znakiem ograniczającym zakres dopasowania (u nas cudzysłów) znaku "?". Spowoduje to dopasowanie się nie do maksymalnie najdłuższego łańcucha, tylko do najkrótszego.

A więc nasza reguła wybierająca powinna wyglądać następująco: /href=".*?"/. Wynik jej działania jest już zgodny z naszymi oczekiwaniami :

href="www.pierwszy.adres.url"
href="www.drugi.adres.url"

Wykorzystywanie wyrażeń regularnych


Metody find() i matches()
Wyrażenia regularne w Groovym obsługiwane są przez klasę java.util.regex.Matcher, która udostępnia dwie bardzo użyteczne metody- find() i matches().
  • matcher.find()- pozwala na określenie czy w określonym ciągu znakowym, da się wyszukać podciąg zgodny ze wzorcem dopasowania
  • matcher.matches()- przeszukuje łańcuch wejściowy i próbuje dopasować cały wzorzec
Dla uproszczenia Groovy definiuje dwa pomocne operatory:
  • =~ jest tożsamy z uruchomieniem metody matcher.find()
  • ==~ - jest tożsamy z uruchomieniem metody matcher.matches()
Metoda find() zwraca obiekt matcher. Aby dostać się do wszystkich zwróconych przez nią obiektów można wywołać jej metodę each() - wraz z zastosowaniem domknięcia. Przykładem działania niech będzie skrypt wyszukujący w łańcuchu znaków wystąpień cyfry 4.

text='1 2 4 8 64'
(text=~/4/).each{match->print match}

(text=~/4/) - zwraca obiekt matcher, który zawiera w sobie dopasowane ciągi znaków ( w tym przypadku wystąpienia znaku 4)

Metoda String.eachMatch(regex)Alternatywnym i w moim przekonaniu bardziej intuicyjnym sposobem dotarcia do obiektów otrzymanych w wyniku zastosowania wzorca, jest metoda String.eachMatch(wyrazenie_regularne).
Dzięki niej i wykorzystaniu mechanizmu domknięć możliwe jest przetworzenie każdego podzbioru znaków, który na podstawie wzorca wyrazenie_regularne zostani wyodrębniony z obiektu String, na którym wywoływana jest metoda. Np.

pattURL=/href=".*?"/
content=new File("regularExp_source.txt").getText();
content.eachMatch(pattURL){ match->
print "\n"+match[0]
}

Powyższy skrypt powoduje że w pliku regularExp_source.txt wyszukane zostaną wszystkie adresy url. Należy zwrócić uwagę na ciekawą rzecz. Mianowicie obiekt matcher wyniki dopasowania zwraca nie w postaci pojedynczego łańcucha znaków, lecz tablicy, pod której zerowym indeksem znajduje się wynik dopasowania. Jeśli we wzorcu wystąpiło n operatorów grupowania () to każda z dopasowanych grup znajdzie swe miejsce pod kolejnym indeksem.

Metoda String.replaceAll(regex)
Inną ciekawą metodą, jest String.replaceAll(regex). Pozwala ona na zamianę jednych ciągów znakowych na inne.

Schemat jej działania jest następujący:
content.replaceAll(/wzorzec/){it - 'ciągZastępowany'+'ciągZastępujący'}. Aby zamiana nastąpiła ciągZastępowany musi zostać dopasowany przez wzorzec.

Przykładem zastosowania jest skrypt zamieniający polskie znaki na ich liczbowe reprezentacje w utf.

myFile=new File("znakiUTF.txt") myFile.write("\n")
content=new File("znakiAscii.txt").text
content=content.replaceAll(/Ą/){it - 'Ą'+'\\\\u0104'}
content=content.replaceAll(/ą/){it - 'ą'+'\\\\u0105'}
content=content.replaceAll(/Ć/){it - 'Ć'+'\\\\u0106'}
content=content.replaceAll(/ć/){it - 'ć'+'\\\\u0107'}
content=content.replaceAll(/Ę/){it - 'Ę'+'\\\\u0118'}
content=content.replaceAll(/ę/){it - 'ę'+'\\\\u0119'}
content=content.replaceAll(/Ł/){it - 'Ł'+'\\\\u0141'}
content=content.replaceAll(/ł/){it - 'ł'+'\\\\u0142'}
content=content.replaceAll(/Ó/){it - 'Ó'+'\\\\u00D3'}
content=content.replaceAll(/ó/){it - 'ó'+'\\\\u00F3'}
content=content.replaceAll(/Ś/){it - 'Ś'+'\\\\u015A'}
content=content.replaceAll(/ś/){it - 'ś'+'\\\\u015B'}
content=content.replaceAll(/Ź/){it - 'Ź'+'\\\\u0179'}
content=content.replaceAll(/ź/){it - 'ź'+'\\\\u017A'}
content=content.replaceAll(/Ż/){it - 'Ż'+'\\\\u017B'}
content=content.replaceAll(/ż/){it - 'ż'+'\\\\u017C'}
myFile.write(content,"UTF-8")