Hlavní navigace

Programovací jazyk TCL (4)

9. 8. 2005
Doba čtení: 11 minut

Sdílet

V dnešním pokračování seriálu o programovacím jazyce Tcl se budeme věnovat především práci s řetězci a regulárním výrazům. Neopomeneme však další důležité součásti standardní knihovny Tcl - práci se soubory, použití systémově orientovaných souborových příkazů a v neposlední řadě také komunikaci po počítačových sítích.

Obsah

1. Základ práce s řetězci
2. Regulární výrazy
3. Funkce pro práci s řetězci
4. Řetězce a příkaz switch
5. Práce se soubory
6. Systémově orientované příkazy pro práci se soubory
7. Síťově orientované příkazy
8. Další volně dostupné moduly
9. Obsah dalšího pokračování tohoto seriálu

1. Základ práce s řetězci

Programovací jazyk Tcl obsahuje velkou podporu pro práci s řetězci. Je to ostatně logické – všechny proměnné i příkazy jsou interně v tomto jazyku řetězcem reprezentovány, proto je pro jejich snadné a rychlé zpracování zapotřebí používat větší množství sofistikovaných funkcí. Před popisem jednotlivých funkcí si řekneme důležitou a užitečnou informaci – poslední hlavní verze Tcl (tj. verze 8.x) plně podporuje kódování Unicode, ve všech řetězcích je tedy možné používat znaky prakticky všech světových abeced (samozřejmě včetně češtiny) – Tcl je tedy pro práci s řetězci výhodnější než dnes mnohdy mylně upřednostňovaný Perl :-). Dokonce je možné (příliš to však nedoporučuji) používat češtinu pro pojmenování proměnných a funkcí – viz následující příklad:

proc čeština {} {
    set českýtext "příliš žluťoučký kůň úpěl ďábelské ódy"
    puts $českýtext
}

# zavoláme funkci čeština:
čeština 

Mnoho funkcí, které v Tcl pracují s řetězci, se opírá o takzvané regulární výrazy. Popis regulárních výrazů v mnohém přesahuje kapacitu tohoto článku (je dokonce jediným tématem několika rozsáhlých knih), proto si ve druhé kapitole povíme pouze nejnutnější pravidla, která se nám mohou při programování v Tcl hodit. Regulární výrazy totiž bezesporu představují jeden z nejsilnějších programových nástrojů velkého množství dynamických skriptovacích jazyků.

2. Regulární výrazy

Tcl jsou podporovány dva typy regulárních výrazů. První typ, nazývaný také glob matching, v mnohém odpovídá regulárním výrazům známým z unixového shellu (typické použití tohoto typu regulárních výrazů představují příkazy ls, mv, cp atd.). Druhý typ regulárních výrazů, který je složitější (už jen tím, že používá více „magických“ znaků), zhruba odpovídá regulárním výrazům použitým v textovém editoru Vim, vyhledávací utilitě grep atd.

V regulárních výrazech prvního typu se mohou vyskytovat následující zástupné znaky:

Zástupné znaky v regulárních výrazech prvního typu (ala shell)
Znak Význam znaku
* libovolně dlouhá sekvence znaků
? libovolný znak (pouze jeden)
[…] libovolný znak ze zadané množiny (lze použít i interval – viz příkazy shellu)
\<znak> odstranění speciálního významu výše zmíněných zástupných znaků
další znaky další znaky se s řetězcem porovnávají bez dalšího zpracování

Regulární výrazy druhého typu jsou použity například v dále uvedených příkazech regexp a regsub. Možností pro specifikaci řetězců zde existuje více, význam některých magických znaků se dokonce změnil (hvězdička atd.):

Zástupné znaky v regulárních výrazech druhého typu (ala Vim)
Znak Význam znaku
. libovolný znak
* nula či více výskytů předchozí položky
+ jeden či více výskytů předchozí položky
| volba mezi dvěma výrazy (or)
[…] libovolný znak ze zadané množiny (lze použít i interval – viz příkazy shellu)
^ začátek řetězce (uvnitř závorek funguje jako negace)
$ konec řetězce
\<znak> odstranění speciálního významu zástupného znaku
další znaky další znaky se s řetězcem porovnávají bez dalšího zpracování

3. Funkce pro práci s řetězci

Funkcí pro manipulaci s řetězci existuje v programovacím jazyku Tcl značné množství. Tyto funkce si můžeme rozdělit do dvou kategorií – funkce volané přímo svým jménem a „podpříkazy“ funkce string. Nejprve si uvedeme, které funkce pro práci s řetězci je možné volat přímo:

Funkce pro práci s řetězci
Název funkce Význam funkce
append pomocí této funkce je možné přidat znaky na konec řetězce
subst tento příkaz vnucuje substituci proměnných a příkazů
string funkce pro manipulaci s řetězcem s mnoha dalšími volbami, které budou uvedeny v dalším textu
regexp vyhledání shody v řetězci podle zadaného regulárního výrazu (zde se používá kompletní repertoár značek v regulárním výrazu)
regsub vyhledání shody v řetězci podle zadaného regulárního výrazu a zápis nového řetězce do zadané proměnné
format formátování řetězce ve stylu C-čkovské funkce sprintf()
scan čtení hodnot z řetězce ve stylu C-čkovské funkce sscanf()
binary formátování a získávání informací z takzvaných binárních řetězců (neprovádí se interní konverze do a z Unicode)

V předchozí tabulce jsme si mimo jiné uvedli i funkci string. Tato funkce slouží k mnoha manipulacím s řetězci, proto také obsahuje mnoho „podpříkazů“, podobně jako minule zmíněná funkce array při práci s poli. V níže uvedené tabulce si uvedeme některé základní manipulace s řetězci, které lze provádět právě pomocí funkce string:

Manipulace s řetězcem pomocí funkce string
Název funkce Význam funkce
string length tato funkce vrací, jak již samotný název napovídá, délku řetězce
string index pomocí této funkce lze získat znak na určité pozici, přičemž první znak má, podobně jako v C-čku, index nulový
string first vyhledávání podřetězce v řetězci směrem od začátku (odpovídá C-čkovské funkci strstr())
string last vyhledávání podřetězce v řetězci směrem od konce
string compare lexikografické porovnání dvou řetězců (odpovídá C-čkovské funkci strcmp(), výstupní hodnoty jsou –1, 0 a 1)
string match porovnání řetězce se vzorem, přičemž je možné použít zjednodušených regulárních výrazů (podobně jako v shellu, výstupní hodnoty jsou 0 nebo 1)
string range tato funkce vrací podřetězec zvolený dvěma indexy. Místo horního indexu lze použít i slovo end (to se vyhodnotí jako expr [string length] –1)
string tolower tato funkce vrací řetězec odpovídající původnímu řetězci, přičemž se provádí převod na malá písmena – minusky (funguje i pro češtinu)
string toupper obdoba předchozí funkce s tím, že se zde provádí převod na kapitálky (opět podporuje češtinu)
string trim tato funkce vrací podřetězec, ze kterého jsou odstraněny vybrané počáteční a koncové znaky
string trimleft obdoba předchozí funkce s tím rozdílem, že se odstraňují pouze počáteční znaky
string trimright odstranění pouze koncových znaků
string wordstart tato funkce vrací index prvního znaku slova, které obsahuje zadanou pozici
string wordend obdoba předchozí funkce, zde se však vrací index znaku, jež se nachází ihned za slovem

4. Řetězce a příkaz switch

Příkaz switch jsme si ukazovali v předchozím dílu tohoto seriálu. Zde si jenom připomeňme, že je možné ho zapsat podle následujícího schématu:

switch řetězec vzor1 větev1 vzor2 větev2 ... default větev_n 

Na rozdíl od C-čka a od něj odvozených programovacích jazyků se nemusí jednotlivé větve ukončovat žádným příkazem typu break – vždy se provede maximálně jedna větev. Větev označená slovem default je přitom brána jako implicitní, tj. pokud není splněna žádná z podmínek, provede se právě tato část programu. Nyní se vraťme k podmínkám, které je možné v příkazu switch použít. Nejedná se o klasické booleovské podmínky, ale obecně o regulární příkaz, který slouží k porovnání libovolného řetězce se zadanými vzory. Kromě toho je možné příkazu switch zadat několik voleb, kterými se ovlivňuje způsob porovnání řetězců. Jedná se o následující vol­by:

Volby příkazu switch
Volba Význam volby
-exact exaktní porovnávání řetězce se vzorem (implicitní nastavení)
-glob porovnávání na základě jednodušší formy regulárního výrazu (jako v shellu)
-regexp porovnávání na základě rozšířené formy regulárního výrazu (jako ve Vimu)

Ukažme si nyní příklady použití příkazu switch:

# příkaz switch na jednom řádku s exaktním porovnáváním
set foo "abc"
switch abc a {expr 1 } b {expr 1} $foo {expr 2} default {expr 3}

# příkaz switch rozepsaný na více řádků se shellovským prohledáváním
switch -glob aaab {
   a*b     {expr 1}
   b       {expr 1}
   a*      {expr 2}
   default {expr 3}
}

# spojení dvou větví (obdoba C-čkovského stylu psaní více větví):
switch -glob aaab {
   a*b     -
   b       {expr 1}
   a*      {expr 2}
   default {expr 3}
} 

5. Práce se soubory

Základní práce se soubory je v Tcl velmi jednoduchá a přímočará. K dispozici jsou funkce pro otevření souboru, zavření souboru a pro čtení a zápis dat z a do souboru. Při běžné práci se k souborům přistupuje jako k datovým tokům (data stream) – v dokumentaci Tcl se pro ně používá výraz kanály. Podobně jako v jiných programovacích jazycích se i zde při spuštění aplikace otevřou tři implicitní kanály: stdin (standardní vstup), stdout (standardní výstup) a stderr (chybový výstup). Samozřejmě, že na systémové úrovni funguje přesměrování těchto kanálů, používání rour atd.

Mimo implicitní kanály je možné otevřít kanály další. Pro tento účel se používá příkaz open, který se volá s následujícími parametry:

open jménoSouboru přístup práva 

Jméno souboru může být zadáno buď s absolutní, nebo s relativní cestou. V řetězci „přístup“ je specifikováno, zda se soubor otevře pro čtení (read), zápis (write), či přidání na konec souboru (append). Hodnota tohoto řetězce je stejná jako u C-čkovské funkce fopen(). Řetězec „práva“ je možné použít při vytvoření nového souboru (tj. otevření neexistujícího souboru pro zápis). Tímto řetězcem se určují výchozí práva k souboru (viz příkaz chmod). Pokud otevření souboru proběhne v pořádku, vrátí se interní hodnota otevřeného kanálu. Tato hodnota se využívá v následujících funkcích.

Po přečtení či zápisu všech požadovaných dat je vhodné kanál uzavřít. Pro tento účel je k dispozici příkaz close, který se volá následovně:

close kanál 

Jediným argumentem tohoto příkazu je interní hodnota otevřeného kanálu, kterou vrátil výše popsaný příkaz open.

Je samozřejmé, že kromě otevření a zavření souborů potřebujeme načítat ze souborů data či do nich ukládat. K tomuto účelu existuje množství funkcí, my si zde ukážeme pouze funkce nejzákladnější. Budeme se zabývat hlavně textovými soubory, jejichž použití je při práci v Unixu nejčastější. První funkcí je gets, která slouží k načtení celého řádku ze souboru otevřeného pro čtení:

gets kanál proměnná 

Tato funkce vrátí počet načtených znaků. Pokud narazí na konec souboru, vrátí hodnotu –1. Všimněte si, že není zapotřebí specifikovat maximální délku řetězce (ta je teoreticky neomezená), takže chyby typu „buffer overflow“ by zde neměly nastat. Opakem funkce gets je funkce puts, jež slouží k zápisu celého řetězce do souboru. Funkce se může volat s nepovinným parametrem -nonewline, kterým je možné zakázat zápis znaku pro odřádkování (ten totiž může být součástí řetězce):

puts kanál řetězec
puts -nonewline kanál řetězec 

Typickým příkladem ve většině příruček k programovacím jazykům je funkce pro zkopírování obsahu souboru. Tuto funkci je možné v Tcl zapsat následovně:

proc copyFile { inFileName outFileName } {
    set inFile [open $inFileName "r"]
    set outFile [open $outFileName "w"]
    while { [gets $inFile temp] != -1 } {
        puts $outFile $temp
    }
    close $inFile
    close $outFile
} 

Kromě toho je pro čtení ze souboru možné použít funkci read, která načítá zadaný počet znaků (tuto funkci je možné aplikovat i na binární soubory, protože se nepřevádí znaky pro konce řádků). Pokud není zadán maximální počet znaků, načte se veškerý obsah souboru:

read kanál početZnaků
read kanál 

Příkazů pro práci se soubory je samozřejmě více, jejich seznam je uveden v následující tabulce:

Práce se soubory
Název funkce Význam funkce
open otevření souboru pro čtení/zápis/přidání na konec
close uzavření souboru
read čtení bytů ze souboru
gets načtení řádku ze souboru
puts zápis řetězce do souboru
format formátovaný zápis do řetězce, většinou se používá spolu s funkcí puts
scan čtení hodnot z řetězce, většinou se používá spolu s funkcí gets

6. Systémově orientované příkazy pro práci se soubory

V následující tabulce jsou uvedeny funkce, které pracují se soubory jako celky, tj. nikoli s jejich obsahem. Jedná se o funkce, které jsou do určité míry systémově orientované (a nemusí tak být nutně přenositelné na různé platformy).

Systémově orientované příkazy pro práci se soubory
Název funkce Význam funkce
file size soubor vrací délku souboru v bytech
file atime soubor vrací poslední čas přístupu k souboru
file mtime soubor vrací čas poslední úpravy souboru
file dirname soubor vrací část jména souboru reprezentujícího adresář (na Unixech jde o rozdělení řetězce před posledním lomítkem)
file extension soubor vrací příponu souboru (na Unixech se jedná o rozdělení řetězce před poslední tečkou)
file rootname soubor vrací jméno souboru bez přípony
file exists soubor vrací hodnotu 1, pokud soubor existuje
file isdirectory soubor vrací hodnotu 1, pokud je názvem adesář (v newspeaku složka :-)
file isfile soubor vrací hodnotu 1, pokud je názvem běžný soubor (ne adresář ani roura)
file readable soubor vrací hodnotu 1, pokud lze soubor číst (test práv uživatele)
file writable soubor vrací hodnotu 1, pokud lze do souboru zapisovat (test práv uživatele)
file executable soubor vrací hodnotu 1, pokud je soubor spustitelný (pro daného uživatele)

7. Síťově orientované příkazy

Práce se sítí je v Tcl velmi jednoduchá, zejména v porovnání s dostupnými příkazy v C-čkových knihovnách. Základem komunikace po síti jsou zde sockety, které se chovají podobně jako soubory (resp. datové kanály) – lze je otevírat, zavírat, číst z nich data a naopak do nich data zapisovat. U socketů se rozlišuje mezi klientem a serverem, většinou se však komunikace nastavuje tak, že server naslouchá požadavkům klienta a posléze tyto požadavky plní.

Na straně klienta se socket otevírá příkazem:

socket host port 

Kde host je jméno serveru a port číslo TCP portu, na kterém server naslouchá.

Na straně serveru se používá stejný příkaz, ale s jedním volitelným parametrem navíc:

socket -server příkaz port 

Kde příkaz nastaví obslužnou rutinu, která se zavolá v případě, že se k serveru připojí nějaký klient. Vyvolání rutiny proběhne asynchronně k běhu programu. Pokud je zapotřebí program během požadavku pozastavit a požadavek obsloužit, je možné použít příkaz vwait, který akceptuje jeden parametr, jímž je kanál vytvořený příkazem socket.

8. Další volně dostupné moduly

K programovacímu jazyku Tcl je dodáváno poměrně velké množství rozšiřujících modulů – ty jsou dostupné ve formě knihoven. Jedním z nejznámějších a nejpoužívanějších rozšíření Tcl je program expect Dona Libese, který slouží k automatizaci řízení interaktivních programů. Tento program je ideální pro testování programů, inicializaci modemového připojení (různé reakce na stavy linky při připojování) atd.

Dalším rozšířením je knihovna BLT určená zejména pro práci s grafikou, [incr Tcl] je zase objektové rozšíření původního procedurálního Tcl (všimněte si pěkné slovní hříčky, která paroduje název C++ – oba názvy mají v daném jazyku korektní syntaxi a stejnou sémantiku).

Absolutně nejznámějším modulem je však knihovna Tk pro tvorbu grafického uživatelského rozhraní, kterou se budu podrobněji zabývat ve zbývajících částech tohoto seriálu.

CS24_early

9. Obsah dalšího pokračování tohoto seriálu

V dalším pokračování tohoto seriálu se již budeme zabývat knihovnou Tk, která slouží k jednoduchému a rychlému návrhu grafického uživatelského rozhraní (GUI). Tato knihovna byla v minulosti důvodem velké oblíbenosti Tcl, proto se také můžeme často setkat se spojením Tcl/Tk.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.