Perl ja CGI-ohjelmointi

Perl on ohjelmointikieli jota käytetään paljon Unix-ympäristöissä ja etenkin CGI-ohjelmien toteutukseen. Tässä dokumentissa käydään hyvin lyhyesti esimerkkien avulla läpi Perl-kielen tärkeimmät ominaisuudet WWW-sovellukset-kurssin kannalta. Tarkempaa tietoa kaipaavat voivat lukea Unix ja shell-ohjelmointi-kurssin perl-materiaalin tai perlintron. Tämän dokumentin sisältö pohjautuu hyvin paljolti perlintroon.

Perl-ohjelmien suorittaminen

Kirjoita perl-ohjelman ensimmäiselle riville perl-tulkin osoite:

#!/usr/bin/perl -w -T

-w -T -optiot asettavat päälle erittäin hyödyllisiä syntaksi ja turvallisuustarkistuksia ja edellyttävät mm. muuttujien esittelyn.

Perl-tulkin sijainnin saat selville kirjoittamalla komentoriville:

[tjlahton@karahka ~]$ whereis perl
perl: /usr/bin/perl /usr/share/man/man1/perl.1.gz

Sijainti vaihtelee konekohtaisesti. CGI-ohjelmia kirjoitettaessa perl-tulkin sijainti pitää muistaa tarkistaa www-palvelinkoneessa.

Lisäksi kannattaa ohjelman alkuun lisätä use strict;, joka suojaa pahimmilta kirjoitusvihreiltä.

#!/usr/bin/perl -w -T
use strict;

Anna perl-ohjelmalle suoritusoikeus:

chmod u+x hello.cgi

Jos www-palvelin on säädetty suorittamaan CGI-ohjelmat suEXECin avulla niin riittää kun suoritusoikeus on CGI-ohjelman omistajalla. Jos CGI-ohjelmat suorittaa www-palvelinohjelma esim. apache-käyttäjä niin suoritusoikeus täytyy antaa kaikille:

chmod a+x hello.cgi

Suorita perl-ohjelma:

./hello.cgi

Perl-syntaksi

Perlin muuttujatyypit

Perlissä on kolmea eri muotoa olevia muuttujia: numeerisia/merkkijonoja, taulukoita ja assosiatiivisia taulukoita. Perlissä ei ole C/C++/java-kielien tapaan erilaisia tietotyyppejä.

my $nimi = "Tommi Lahtonen";
my $paino = "125.7";
my @opintoviikot = ("3", "3", "4");
my %kurssit = ("itkp101", "3", "itky201", "3", "tjta270", "4");
my %kurssit = ( 
	itkp101 => "3", 
	itky201 => "3",
	tjta270 => "4"
);

Taulukot

Taulukko on lista arvoja.

Perlin taulukoiden indeksointi alkaa nollasta ( 0 ).

Taulukon yksittäiseen alkioon viitataan kuten muuttujaan.

my $p = "2";
my @opintoviikot = ("3", "5", "4");
print $opintoviikot[0]; # tulostaa taulukon ensimmäisen alkion eli 3
print $opintoviikot[1]; # tulostaa taulukon toisen alkion eli 5
print $opintoviikot[2]; # tulostaa taulukon kolmannen alkion eli 4
print $#opintoviikot; # tulostaa taulukon viimeisen alkion paikan eli 2
print $opintoviikot[$p]; # tulostaa taulukon kolmannen alkion eli 4

Taulukko on myös helppoa järjestää aakkosjärjestykseen:

my @taul = ("foo", "bar", "kukkuu", "luuruu", "möh");
my @jarjestetty_taul = sort(@taul);

Taulukoita voi käyttää hieman kuin pinoa:


my @taulu = (1,2,3 );

push(@taulu, 5); #lisää taulukon loppuun uuden arvon 5

my $arvo = pop(@taulu); #palauttaa taulukon viimeisen arvon ja poistaa sen

unshift(@taulu, 10); #lisää taulukon alkuun uuden arvon 10

$arvo = shift(@taulu); #palauttaa taulukon ensimmäisen arvon ja poistaa sen

Assosiatiiviset taulukot

Assosiatiivisessa taulukossa haluttu taulukon alkio löytyy sitä vastaavan avaimen avulla. Avain voi olla esim. numero tai merkkijono.

my %kurssit = (
	itkp101 => "3", 
	itky201 => "3",
	tjta270 => "4"
);

$kurssit("itky201") = 15; #sijoitetaan taulukkoon paikkaan 'itky201' arvo 15

my $foo = "tjta270";
$kurssit{$foo} = 33; # sijoitetaan paikkaan 'tjta270' luku 33

print $kurssit{"itky201"}; # tulostaa 15
print $kurssit{"itkp101"}; # tulostaa 3
print $kurssit{"tjta270"}; # tulostaa 33
print keys %kurssit; # tulostaa taulukon avaimet tuntemattomassa järjestyksessä
print values %kurssit; # tulostaa taulukon arvot tuntemattomassa järjestyksessä
my @avaimet = keys %kurssit;
my @arvot = values %kurssit;

Ehtolauseet

Perl tuntee if-else-rakenteen:

if ( ehtolause  ) {
  ...
} else {
 ...
}

Vertailuoperaattorit

Perlissä on vertailuoperaattorit erikseen numeroille ja merkkijonoille. Tapauksesta riippuen pitää valita oikea operaattori.

Vertailuoperaattori
NumerotMerkkijonotSelitys
==eqyhtäsuuri
!=neerisuuri
<ltpienempi kuin
>gtsuurempi kuin
<=leyhtä suuri tai pienempi
>=geyhtä suuri tai suurempi
my $paino = "125";
my $nimi = "Tommi Lahtonen";
if ( $paino > 100 ) {
 print "Olet aika painava\n"; #tämä rivi tulostuu
} else {
 print "Noin kevyet punnitaan neuvolassa\n";
}

if ( $nimi eq "tommi lahtonen" ) { #merkkijonojen yhtäsuuruus
 print "Taidat olla ope\n"; #tämä rivi tulostuu
} else {
 print "Lienet opiskelija\n";
}

Boolen logiikka

Perlissä on käytössä normaalit loogiset operaattorit joko java-tyyppisesti merkittyinä tai sanallisessa muodossa:

&&and
||or
!not
if ( $nimi eq "tommi lahtonen" and $paino> 100 ) {
 print "Taitaa olla Tommin paino nousussa";
}
if ( $nimi eq "tommi lahtonen" && $paino> 100 ) {
 print "Taitaa olla Tommin paino nousussa";
}

Muut operaattorit

+
yhteen
-
vähennys
*
kerto
/
jako
=
Sijoitusoperaattori
.
Merkkijonojen yhdistäminen
my $nimi = "tommi lahtonen";
my $lause = "Minun nimeni on " . $nimi . " ja asun Jyväskylässä\n";

Silmukat

Perlistä löytyy useita silmukkarakenteita joista tärkeimmät ovat foreach- ja while-silmukat.

foreach käy läpi kaikki alkiot:

my @taulukko = (1, 2, 3, 4, 5, 6 );

 # jokaisella silmukan kierroksella muuttuja $p
 # sisältää käsiteltävän taulukon arvon
foreach my $p ( @taulukko ) {
  print $p . "\n"; #tulostaa 
}

my %kurssit = ( 
	itkp101 => "3", 
	itky201 => "3",
	tjta270 => "4"
);

 #assosiatiivisen taulukon läpikäynti
foreach my $p ( keys %kurssit ) {
  print "Avaimella $p löytyy arvo " . $kurssit{$p} . "\n";
}


my @taulukko2 = ("auto", "mopo", "pyora" );
my $luku = 1;
my %tuotteet;

foreach my $p ( @taulukko2 ) {
  $tuotteet{$p} = $luku;
  $luku++;
}

foreach my $p ( keys %tuotteet ) {
  print "Tuote $p : " . $tuotteet{$p} . "\n";

}

while-silmukka toimii kuten javassa.

my $count = 0;
my @taulukko = (1, 2, 3, 4, 5, 6 );

while ( $count < @taulukko ) {
  print $taulukko[$count];
  $count = $count + 1;
}

 # tai toinen versio:

while ( $count < ($#taulukko+1) ) {
  print $taulukko[$count];
  $count = $count + 1;
}

Tiedostojen käsittely

Tiedoston avaaminen:

my $INFILE;
my $OUTFILE;
my $LOGFILE;

open($INFILE,  "luku.txt")   or die "luku.txt ei aukea: $!";
open($OUTFILE, ">kirjoitus.txt") or die "kirjoitus.txt ei aukea: $!";
open($LOGFILE, ">>lisaa.log")    or die "lisaa.log ei aukea: $!";

Tiedostosta lukeminen:

    
my $line  = <$INFILE> #luetaan yksi rivi;
my @lines = <$INFILE> #luetaan koko tiedosto;

    
while ($line = <$INFILE>) {     
        print "Rivi: $line";
}
    
close $INFILE;

tiedostoon kirjoittaminen:

open($OUTFILE, ">kirjoitus.txt") or die "kirjoitus.txt ei aukea: $!";
print $OUTFILE "Kirjoitetaan tämä teksti tiedostoon";

Merkkijonojen käsittely

Perl on erityisen hyvä kieli merkkijonojen käsittelyyn.

Tyypillisessä tapauksessa merkkijonoja pitää pilkkoa osiin:

my $rivi = 'Tommi Lahtonen:yliopistonopettaja:2746:tommi.j.lahtonen@jyu.fi';
my @kentat = split(/:/, $rivi); #pilkotaan taulukkoon
my ($nimi, $nimike, $puhnro, $email) =  split(/:/, $rivi); #pilkotaan muuttujiin

Joskus pitää merkkijonon sisältä etsiä toista merkkijonoa ja ottaa merkkijonosta jokin osa:

my $email = 'tommi.j.lahtonen@jyu.fi';

 #etsitään merkkijonon '@' paikka
my $paikka = index($email, '@'); 

 #otetaan merkkijonosta pala paikasta 0 kohtaan $paikka saakka
my $tunnus = substr($email, 0, $paikka); 

 #otetaan merkkijonosta pala kohdasta $paikka+1 alkaen merkkijonon loppuun saakka
my $host = substr($email, $paikka+1);

Aliohjelmat ja funktiot

Perlin aliohjelmiin tuodaan parametrit taulukkona:


sub summa {
  my ($luku1, $luku2) = @_; #luetaan funktion parametrit muuttujiin

  return $luku1 + $luku2;
}

Jos haluaa tuoda aliohjelmalle parametrina taulukoita niin ne täytyy tuoda referenssinä, joka monimutkaistaa asioita jonkun verran...

Säännölliset lausekkeet (regular expressions)

Perlin hienoimpia ominaisuuksia ovat monipuoliset säännölliset lausekkeet joiden avulla voi tehdä lähes mitä tahansa. Näihin voi perehtyä parhaiten lukemalla PERL Regular Expressions (With Snippets) ja regular expressions.

CGI-ohjelmat

Perlillä tehtyjen CGI-ohjelmien perusrakennusaineena on CGI-kirjasto

#!/usr/bin/perl -w -T
use strict;
use CGI;
my $query = new CGI;

 # sivun mediatyyppi on pakko tulostaa. www-palvelin
 # lisää muut tarvittavat http-otsikot. 
 # mediatyypin jälkeen pitää tulla yksi tyhjä rivi jolloin
 # www-palvelin tietää että otsikoita ei ole tulossa
 # enempää
print "Content-type: text/html\n\n";

CGI-kirjaston mukana saa valmiit funktiot joilla pääsee käsiksi lomakkeilta tulleisiin kenttiin. CGI-kirjaston mukana tulevia mahdollisuuksia HTML-koodin tuottamiseen ei ole mikään pakko käyttää.

my @nimet = $query->param(); # antaa listan lomake-elementtien nimistä

my $nimi = $query->param('nimi'); # antaa nimi-nimisen kentän arvon

 #antaa taulukossa kaikkien 'nimi'-nimisten kenttien arvot
my @arvot = $query->param('nimi'); 

 # listataan kaikki kentät ja niiden arvot
 # jos samannimisellä kentällä on useita arvoja niin listataan
 # vain ensimmäinen arvo
foreach my $p ( $query->param() ) {
  print "Kenttä $p sai arvoksi: " . $query->param($p) . "\n";
}

 # listataan varmasti kaikki kentät ja niiden arvot
foreach my $p ( $query->param() ) {
 # otetaan arvo/arvot taulukkon
  my @arvot = $query->param($p);
 #käydään kaikki taulukon arvot läpi
  foreach my $u ( @arvot ) {
    print "Kenttä $p sai arvoksi: " . $u . "\n";
  }
}

Koko CGI-objektin voi myös tallentaa tiedostoon tai lukea tiedostosta:

my $INFILE;
    
open($INFILE,  "lue.txt")   or die "lue.txt ei aukea: $!";
    
my $query = new CGI($INFILE);
    
my $OUTFILE;
open($OUTFILE, ">kirjoitus.txt") or die "kirjoitus.txt ei aukea: $!";
    
$query->save($OUTFILE);

    
 #käsitellään useita      
    
my $count = 0; #lasketaan montako tallennettu

 #eof-funktio palauttaa tosi kun tiedosto on loppu    
while ( !eof($INFILE) ) {
     $query = new CGI($INFILE);
     print $count++ . "\n";
}

Funktiot

CGI-kirjastosta löytyy muutama hyödyllinen funktio:

escapeHTML
Muuttaa <, > ja &-merkit entiteeteiksi.
escapeHTML("<h1>Kalle & Ville</h2>");

Muita CGI-kirjaston funktioita

Esimerkki CGI-kirjaston funktioista

Urlien koodaamiseen löytyy sopivat funktiot URI::Escape-kirjastosta:

use URI::Escape;
print uri_escape("http://tässä.urlissa.on/laittomia merkkejä"); #koodataan
print uri_unescape("http://appro.mit.jyu.fi/%20%e4/"); #palautetaan koodaus

Puhdista ylim. whitespace

Seuraava aliohjelma puhdistaa sille tuodusta merkkijonosta alku- ja loppupään välilyönnit, tabulaattorit ja rivinvaihdot.


sub trim
{
        my $string = $_[0];
 #Käytetään regexpejä merkkijonon siivoamiseen
        $string =~ s/\n//g;
        $string =~ s/^\s+//;
        $string =~ s/\s+$//;
                        
        return $string;
}

Evästeet

Evästeitä voi asettaa ja lukea helpoiten CGI-kirjaston evästefunktioilla:

#!/usr/bin/perl -w -T
use strict;
use CGI;

my $q = new CGI;

 #  name ja value ovat pakollisia kenttiä
 # muut kentät ovat vapaavalintaisia
 # expires-kenttään ilmoitetaan evästeen vanhemisaika
 # esim. +5h = viiden tunnin kuluttua tai +1d = vuorokauden kuluttua
my $cookie = $q->cookie(-name=>'nimi',
	-value=>'tommi',
	-expires=>'+5h',
	-path=>'/foo/bar',
	-domain=>'appro.mit.jyu.fi'
);

 # tulostaa Content-type-rivin ja evästeen:
print $q->header(-cookie=>$cookie);

 #Selaimen palauttamien evästeiden arvoihin pääsee käsiksi samaan tapaan
 #kuin lomake-elementteihin:

 # pyydetään 'nimi'-nimisen evästeen arvoa
my $nimi = $q->cookie('nimi');

 # käydään läpi kaikki evästeet:
foreach my $p ($q->cookie()) {
            
  print "Löytyi $p-niminen eväste jonka arvo on " . $q->cookie($p);
        
}

Laskuri toteutettuna evästeellä

Lisätietoja artikkelista PERSISTENT CLIENT STATE HTTP COOKIES.

CGI-ohjelmien testaus

CGI-ohjelmia pitää yleensä ensimmäisenä testata komentoriviltä. Komentorivin kautta voi myös simuloida lomakkeelta tulevia tietoja kirjoittamalla lomakekenttien nimet ja niiden arvot komennon parametreiksi:

./foo.cgi nimi=tommi huone=agc431.2 puhrno=2746

Laajempia CGI-esimerkkejä

Seuraavana muutama isompi esimerkki CGI-ohjelmista

tilaukset.cgi

Kopioi lomake ja cgi-ohjelma haluamallesi palvelimelle samaan kansioon ja kokeile.

Dynaaminen sisältö

Kopioi seuraavat tiedostot haluamallesi palvelimelle samaan kansioon ja kokeile.

Lisätietoja

Käyttäjien kommentit

hassumiesi ( 2009-11-21 02:56:32 )

virhe taulukot kohdassa. 'print $opintoviikot[$p]; # tulostaa taulukon toisen alkion eli 5' pitäisi olla 'print $opintoviikot[$p]; # tulostaa taulukon kolmannen alkion eli 4'

AE ( 2009-11-24 09:34:02 )

Virhe korjattu.

Kommentoi tätä sivua Lisää uusi kommentti
Kurssimateriaalien käyttäminen kaupallisiin tarkoituksiin tai opetusmateriaalina ilman lupaa on ehdottomasti kielletty!
http://appro.mit.jyu.fi/doc/perl/
© Antti Ekonoja (antti.j.ekonoja@jyu.fi) <http://users.jyu.fi/~anjoekon/>
Tommi Lahtonen (tommi.j.lahtonen@jyu.fi) <http://hazor.iki.fi/>
Jukka Mäntylä (jmantyla@iki.fi) <http://www.iki.fi/jmantyla/>
2011-03-01 11:48:29
Informaatioteknologia - Jyväskylän yliopiston informaatioteknologian tiedekunta