среда, 16 января 2008 г.

Пособие для начинающих по CGI-программированию

Что меня заставило взятся за этот нелегкий труд написания данного учебного пособия. Ну во первых то что практически НЕТ ничего по CGI-програмированию на русском языке, а большинству тех,кто хотел бы изучить CGI, документация на английском в отличии от тех немногих типа меня практически недоступна для понимания.Чтоб помочь им преодолеть этот в первую очередь языковый барьер я и сел писать эту хренотень...
Еще причина ,отчасти перекликающаяся с первой, это то что когда говорят об интернет-программировании обычно излагают HTML со всеми тэгами, которые всем уже по ночам в кошмарах снятся ,ну а после чего начинают долго охать и ахать над прелестями нового аппаратно и платформо-независимого,переносимого,безопасного.....и.т.д. языка Java.Иногда в еще и могут тонким краешком затронуть JavaScript.Видя эту не побоюсь этого слова безнадежную ситуацию, я как доблестный CGI-программист решил хоть что-то поправить к лучшему.

Краткое лирическое отступление насчет CGI

Итак что такое CGI- скрипты и вообще подобные вещи. Начнем с того что ваш браузер (когда вы набрали URL) соединяется по протоколу HTTP с указаным сервером и просит у него нужный файл,примерно так:

GET /~paaa/cgi-bin/guestbbok.cgi HTTP/1.0 -Вот это самое главное в запросе

Ну тут дальше идет посылаемая браузером информация о себе и о том что более подробно ему надо.(Например Accept: */*)

Ну и если запрошен простой файл например .html то если если такой файл есть, То сервер отошлет браузеру ответ:

HTTP/1.0 200 Okay
Content-Type: text/html


Далее после пустой строки(она нужна чтоб отделить заголовок от тела) идет информация из самого URL...
Вот в принципе и весь WWW ....ходишь от ссылки к ссылке....
А что если Нужно внести в этот унылый процесс что-нибудь по настоящему интерактивное , динамическое,прекрасное и великолепное....? Чтож есть ответ и на этот вопрос. Просто что если в запрашиваемом URL указать спецыальную программу (CGI,программа Common Gateway Inteface - Общего Шлюзового Интерфейса) и то что эта прога выдаст то и отправить браузеру....Сервер запускает .cgi программу и она например обработав данные формы заносит вас куда-нибудь в свою базу данных,а вам сообщит что вы большой молодец :)
Ну надеюсь я вас заинтриговал......?

Итак ...приступим...



Краткие сведения о том что надо знать чтоб писать CGI скрипты: Ну вопервых надо знать что такое интернет и как он работает (а вы знаете? ;))) ) Ну и чуть-чуть умения прграмировать(это самое главное)
Давайте вместе писанем какой нибудь простенький скриптик а потом я вам расскажу где сдесь собака порылась....
Ну сначала в своем домашнем каталоге создайте директорию cgi-bin:

cd public_html
mkdir cgi-bin
chmod 0777 cgi-bin

Последняя строчка будет очень важна.
Возьмите редактор и наберите:
#!/usr/bin/perl
#first.cgi
print "Content-Type: text/html\n\n";
print "";
print "

Hello you!!!

";
print "";

Сохраните его в директории cgi-bin под именем first.cgi .Ну как сохранили?
А теперь сделайте его исполняемым(ведь это программа):

chmod +x first.cgi

Ну вот,подходим к торжественному моменту.... наберите в строке браузера http://www.uic.nnov.ru/~твой_логин/cgi-bin/first.cgi
и посмотрите чо будет. Будет одно из двух ,либо скрипт заработает и вы увидите сгенерированую им страничку (поздравляю,в нашем полку прибыло!) либо Internal Server Error -тогда не расстраивайтесь,вы что-то сделали не так. Вам тогда пригодится пособие по ловле блох. Ну вопервых проверку синтаксиса можно осуществить следующим образом:

perl -с first.cgi

Perl вам сразу выдаст либо сообщения об ошибках(ну бывает,точку с запятой пропустили, скобочки или кавычки забыли закрыть...) это сразу по ходу дела поправимо.
Более грубая с логической точки зрения это пропустить вывод пустой строки, которая отделяет заголовок от тела:
print "Content-Type: text/html\n\n"; #Все Правильно
print "Content-Type: text/html\n"; #ОШИБКА!!!

Разберем скрипт:
Первая строка #!/usr/bin/perl Просто указывает где в системе расположен Perl. Вторая это просто коментарий -вы можете тыкать чо угодно после знака #
Затем идет print "Content-Type: text/html\n\n"; Это заголовок указывающий тип содержимого все что скрипт печатает в свой стандартный вывод STDOUT идет на обработку к серверу. Пустая строка отделяет заголовок от тела,которое в нашем случае представляет собой

Hello you!!!



Сервер обработает ответ скрипта и на базе него сформирует и пошлет браузеру ответ.(Сервер обычно не изменяет тела сообщения,он только дополняет заголовок нужными для протокола HTTP полями)

Ну вот азы уже освоены,все не так трудно и удручающе как могло показаться на первый раз
Вы теперь можете сами потренироваться в написании таких вот простеньких скриптов чтоб набить руку.

Переменные среды CGI


Предыдущий скрипт не содержал ничего особенно замечательного,так просто вываливал HTMLый текст который благополучно и отбражался на екране браузера.Но По настоящему мощь придает CGI возможность обработки параметров,которые переданы скрипту.например вы можете набрать
http://www.somehost.ru/somedir/cgi-bin/my_cgi.cgi?param=value
то есть вы хотите чтоб скрипт my_cgi.cgi обработал для вас параметер param со значением value (ну это например) или когда вы заполнили запрос в форме (в например yahoo или altavista).Ну это с точки зрения пользователя... А на сервере при запуске CGI-скрипта сервер формирует среду окружения в которой скрипт может найти всю доступную информацию о HTTP-соединении и о запросе.
Вот эти переменные:

REQUEST_METHOD
Это одно из самых главных поле используемое для определения метода запроса HTTP Протокол HTTP использует методы GET и POST для запроса к серверу.Они отличаются тем что при методе GET запрос является как-бы частью URL т.е. http://www..../myscript.cgi?request а при методе POST данные передаются в теле HTTP-запроса (при GET тело запроса пусто) и следовательно для CGI тоже есть различие при GET запрос идет в переменную QUERY_STRING а при POST подается на STDIN скрипта.
Пример:REQUEST_METHOD=GET
QUERY_STRING
Это строка запроса при методе GET. Вам всем известно что запрос из формы кодируется браузером поскольку не все символы разрешены в URL некоторые имеют специальное назначение. Теперь о методе urlencode: неплохо бы чисто формально напомнить,что все пробелы заменяются в URL на знак '+', а все специальные и непечатные символы на последовательность %hh ,где hh-шестнадцатиричный код символа,разделитель полей формы знак '&',так что при обработке форм надо произвести декодирование.
Пример:QUERY_STRING= name=quake+doomer&age=20&hobby=games
CONTENT_LENGTH
Длина в байтах тела запроса.При методе запроса POST необходимо считать со стандартного входа STDIN CONTENT_LENGTH байт,а потом производить их обработку.Обычно методом POST пользуются для передачи форм,содержащих потенциально большие области ввода текста TEXTAREA.При этом методе нет никаких ограничений,а при методе GET существуют ограничения на длину URL .
Пример:CONTENT_LENGTH=31
CONTENT_TYPE
Тип тела запроса(для форм кодированых выше указаным образом он application/x-www-form-urlencoded)
GATEWAY_INTERFACE
Версия протокола CGI.
Пример:GATEWAY_INTERFACE=CGI/1.1
REMOTE_ADDR
IP-Адрес удаленого хоста,делающего данный запрос.
Пример:REMOTE_ADDR=139.142.24.157
REMOTE_HOST
Если запрашивающий хост имеет доменное имя,то эта переменная содержит его, в противном случае -тот же самый IP-адресс что и REMOTE_ADDR
Пример:REMOTE_HOST=idsoftware.com
SCRIPT_NAME
Имя скрипта,исполизованое в запросе.Для получения реального пути на сервере используйте SCRIPT_FILENAME
Пример:SCRIPT_NAME=/~paaa/guestbook.cgi
SCRIPT_FILENAME
Имя файла скрипта на сервере.
Пример:SCRIPT_FILENAME=/home/p/paaa/public_html/cgi-bin/guestbook.cgi
SERVER_NAME
Имя серера ,чаще всего доменное как www.microsoft.com ,но в редких случаях за неимением такового может быть IP-адресом как 157.151.74.254
Пример:SERVER_NAME=www.uic.nnov.ru
SERVER_PORT
TCP-Порт сервера используюшийся для соединения .По умолчаниию HTTP-порт 80, хотя может быть в некоторых случаях другим.
Пример:SERVER_PORT=80
SERVER_PROTOCOL
Версия протокола сервера.
Пример:SERVER_PROTOCOL=HTTP/1.1
SERVER_SOFTWARE
Програмное обеспечение сервера.
Пример:Apache/1.0
AUTH_TYPE, REMOTE_USER
Эти переменные определены в том случае,когда запрошеный ресурс требует аутентификации пользователя.
Переменные заголовка HTTP-запроса.
За исключением тех строк из заголовка HTTP-запроса которые были включены в другие переменные,сервер приделывает строкам префикс HTTP_ и заменяет знаки '-' на '_':
HTTP_ACCEPT
Давая запрос на сервер браузер обычно расчитывает получить информацию определеного формата,и для этого он в заголовке запроса указывает поле Accept:,Отсюда скрипту поступает cписок тех MIME,которые браузер готов принять в качестве ответа от сервера.
Пример:HTTP_ACCEPT=text/html,text/plain,image/gif
HTTP_USER_AGENT
Браузер обычно посылает на сервер и информацию о себе,чтоб базируясь на знании особеностей и недостатков конкретных браузеров CGI-скрипт мог выдать информацию с учетом этого. Например,разные браузеры могут поддерживать или не поддерживать какие-то HTMLые тэги.
Пример:HTTP_USER_AGENT=Mozila/2.01 Gold(Win95;I)


Ну,начнем применять на практике усвоеные уроки.
#!/usr/bin/perl
#vars.cgi
sub urldecode{ #очень полезная функция декодирования
local($val)=@_; #запроса,будет почти в каждой вашей CGI-программе
$val=~s/\+/ /g;
$val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;
return $val;
}
print "Content-Type: text/html\n\n";
print "CGI-Variables\n";
print "\n";
print "Enter here something:
\n";
print "Your request is:$ENV{'REQUEST_STRING'}
\n";
print "Decoded request is:urldecode($ENV{'REQUEST_STRING'})
\n";
print "
\n";
print "Variables:
\n";
print "REQUEST_METHOD=$ENV{'REQUEST_METHOD'}
\n";
print "QUERY_STRING=$ENV{'QUERY_STRING'}
\n";
print "CONTENT_LENGTH=$ENV{'CONTENT_LENGTH'}
\n";
print "CONTENT_TYPE=$ENV{'CONTENT_TYPE'}
\n";
print "GATEWAY_INTERFACE=$ENV{'GATEWAY_INTERFACE'}
\n";
print "REMOTE_ADDR=$ENV{'REMOTE_ADDR'}
\n";
print "REMOTE_HOST=$ENV{'REMOTE_HOST'}
\n";
print "SCRIPT_NAME=$ENV{'SCRIPT_NAME'}
\n";
print "SCRIPT_FILENAME=$ENV{'SCRIPT_FILENAME'}
\n";
print "SERVER_NAME=$ENV{'SERVER_NAME'}
\n";
print "SERVER_PORT=$ENV{'SERVER_PORT'}
\n";
print "SERVER_PROTOCOL=$ENV{'SERVER_PROTOCOL'}
\n";
print "SERVER_SOFTWARE=$ENV{'SERVER_SOFTWARE'}
\n";
print "HTTP_ACCEPT=$ENV{'HTTP_ACCEPT'}
\n";
print "HTTP_USER_AGENT=$ENV{'HTTP_USER_AGENT'}
\n";
print "
\n";
print "All enviroment:
\n";
foreach $env_var (keys %ENV){
print "$env_var=$ENV{$env_var}
\n";
}
print "\n";
Так как все ваши .cgi -файлы должны быть исполняемыми то чтоб облегчить себе жизнь заведите себе в директории cgi-bin командный файл mkcgi ,содержащий
chmod +x *.cgi
и сделайте его в свою очередь исполняемым chmod +x mkcgi -он сильно упростит вам жизнь.
Ну а теперь запускайте скрипт......
Изучив информацию,выдаваемую данным скриптом вы сможете лучше ориентироваться в переменных окружения CGI.

Прекрасный язык Perl


Вы наверное обратили свое внимание что CGI скрипты пишутся обычно на языке Perl (Practical Extraction and Report Language)- очень удобном языке,впитавшем из других все лучшие черты.Может у вас возникнуть сомнение :Ну вот!Изучать новый язык программирования!? Спешу вас успокоить,изучение Perl не будет в тягость (я сужу по своему опыту!). Вы даже сами не заметите как выучите его.Если вы хоть когда-нибудь программировали скажем на C и использовали утилиту grep для поиска регулярных выражений в тексте,то вам будет еще легче.Мое изучение Perl началось с того что я скачал Perl под Windows и изучения той HTMLой документации которая к нему прилагалась хватило чтоб этот язык стал моим любимым....
Все в нем сделано для удобства программиста (в отличии например от Java;( )
Начнем с переменных,они в Perl бывают 3х типов скаларные,списковые(массивы) и хэши(ассоциативные массивы). Для указания компилятору(да и для немалого удобства программиста) перед именем скалярной переменной стоит знак '$' перед массивом '@',перед хешем '%'. т.е. например $scalar_var,@array_var,%hash_var Скалярные переменные могут быть как числовые так и строковые,но это не надо указывать Perl сам по контексту в зависимости от операций может привести одно к другому.
Например: "123"+"4" будет 127 (или "127") так как операция '+' действует над числами а вот если применить операцию конкатенации строк '.' то строковое "test" . 1 будет "test1"
Ну а вот операции над скалярными переменными:
ОперацыиОписаниеПример
+ - * / %Арифметическиеprint 2*7+4/(8%3);
print int(127/15); #целая часть
**Возведение в степеньprint 2**16;
++ --Инкремент-декремент$i++;
& | ^ ~ << >>Побитовые$x=3;$y=4;
print $x|$y;
print $x&$y;
== != < > <= >= <=>Числовые операции сравненияif($x==9){print "Ok!";}
eq ne lt gt le ge cmpстрковые операции сравненияif($game eq 'doom'){print "You are doomer!\n";}
|| && !Логическиеif(($x==9)||($game eq 'doom')){print "hello you!\n";}
?:Условный оператор$x=($game eq 'quake'?9:8);
,Последовательное вычисление$x=10,$y=20;
.Конкатенация$x='http://'.'www.uic.nnov.ru';
xПовторение$x='1234'x5; #$x='12341234123412341234'
=~Сопоставление с образцомif($url=~/http/){print "HTTP";}
!~То же но с отрицаниемif($url!~/http/){print "No HTTP";}
= += -= *= /= %= **= |= &= ^= ~= <<= >>= .= x=Присваивание$x+=$y;



Пусь это будет вам справочником ,да кстати насчет строк,вы заметили,что они могут быть в двойных и одинарных кавычках, разница между ними состоит в том ,что в одинарных не осуществляется подстановка переменных, а в двойных осущестляется, Например:
$x='qwerty';
print 'my var is $x'; #выведет my var is $x
print "my var is $x"; #выведет my var is qwerty
Списки: Спискочные переменные начинаются с символа '@' конструируются следующим образом
@List1=(1,2,5,70);
@List2=(12,23,@List1); #12,23,1,2,5,70
@Rgb=($r,$g,$b);
Также можно список использовать как lvalue:
@List=(1,2,3..8,15);
($x,$y,$z)=@List; #$x=1,$y=2,$z=3
($x,$y,$z,@list2)=@List; #$x=1,$y=2,$z=3,@list2=(4,5,6,7,8,15);
($r,$g,$b)=@Rgb;
Можно обращаться к нескольким,выбраным элементам массива(срезу массива):
@list=(1..10);
@list[2,3,5,9]=(100,200,300,400); #@list=(1,100,200,4,300,6,7,8,400,10)
@list[1,10]=@list[10,1];#меняет местами элементы
Обратится к скаларному значению -элементу массива можно $имя_массива[индекс], сдесь обратите внимание на знак '$'- мы ведь обращаемся к скаляру-элементу.
Теперь немного о хешах:
хеш это такой массив который состоит из пар ключ-значение, весь хеш обозначается %хеш ,к отдельным элементам доступ $хеш{скалярное выражение} конструируется хеш так:
$my_hash{1}="doom";
$my_hash{'quake'}="www.idsoftware.com";
$my_hash{1+2}=100;
Хеш может быть также сконструирован из массива с четным числом элементов где пары превращаются в ключ-значение
%hash=(1,20,2,100);#аналогично $hash{1}=20;$hash{2}=100;
удаление из хеша -операция delete:
delete $hash{1};
есть функции выдающие ключи и значения соответственно.
%hash=(1,20,2,100,3,'doom');
@k=keys %hash; #@k=(1,2,3);
@v=values %hash;#@v=(20,100,'doom');
Операторы:
Набор операторов в Perl Очень широк,многие из них прямые аналоги имеющихся в других языках,например if,for,while;но есть и значительные улучшения имеюшихся и конечно новые...
Тот же самый оператор if имеет две формы (как когда удобнее):
if(условие)оператор;
оператор if условие;
В пару к оператору if имеется оператор unless : означающий if с отрицанием:
unless(($method eq 'GET')||($method eq 'POST')){print "Unsupported method";}
print "Ok" unless $x < $y;
Также в пару while существует until
синтаксис оператора for полностью аналогичен C:
for($i=0;$i<10;$i++){
print $i;
}
новшеством(и приятным) является foreach позволяющий пройтись по всем элементам массива,присваивая по очереди его элементы какой-то переменной, его синтаксис такой:
foreach $переменная (@массив){
блок операторов;
}
или
foreach (@массив){
операторы;
}
Последний пример особенно важен для упрощения вашего тяжкого труда програмиста и демонтстрирует интересную особенность Perl-переменную по умолчанию $_: в оргомном количестве операторов и функций при опускании аргумента она подразумевается по умолчанию. Она также по умолчанию сопоставляется с регулярным выражением:
следующий пример
@Data=;
foreach(@Data){
chomp;
print if /^From:/;
}
аналогичен такому:
@Data=;
foreach $_ (@Data){
chomp($_);
print $_ if $_ =~ /^From:/;
как видите затраты труда значительно сокращаются,благодаря этому маленькому трюку. Регулярные выражения.
регулярное выражение записывается между двух слэшей /рег_выр/
if(/abc/){
print '$_ содержит abc\n';
}
это самый простой пример применения регулярного выражения а теперь посложнее вот тут в табличке (из того что я помню наизусть):
СимволЗначениеПример применения
.Соответствует любому символуprint if /ab.c/;
[мн-во симв]Соответствует любому символу из данного мн-ва/[abc]d/;#соответствует ad,bd,cd
[^мн-во]Отрицание мн-ва символов/[^xyz]/;#
(....)Группировка элементов(и также запоминание в переменных $1 $2 $3 ...)/(xyz)*/
/([abc].[^xy]qwerty)/
(..|..|..)Одна из альтернатив
*повторение образца 0 или более раз/.*/;#соответствует всему
?Повторение 0 или 1 раз/(http:\/\/)?.*\.cgi/
+Повторение 1 или более раз
{n,m}повторение от n до m раз
{n}повторение точно n раз
{n,}повторение n и более раз
Спец символы:

\t \r \n ...Управляющие символы:табуляции,возврат каретки,перевод строки.....
\dСоответствует цифре,Аналог [0-9]
\DСоответствует нецифровому симсволу,аналог[^0-9]
\wСоответствует букве
\WСоответствует небуквеному символу
\sСоответствует пробельным символам(пробелы,табуляции,новые строки..)
\SСоответствует непробельному символу
\bСоответствует границе слова$test1="this is test";
$test2="wise";
if($test1=~/\bis\b/){print "1";}#соответствует
if($test2=~/\bis\b/){print "2";}#нет
\BСоответствует не границе слова/\Bis\B/ соответсвует 'wise' но не 'is'
Для того чтоб поместить в регулярное выражение любой специальный символ,поставьте реред ним обратный слэш Заставить Perl игнорировать регистр можно поставив i после регулярного выражения
print "Are you sure?:";
$answer=;
if($answer=~/Y/i){
#че-нибудь сделаем...
}

Полезные функции.
В Perl очень много различных функций ,как говорится на все случаи жизни,все о них я конечно не опишу,но обо многих. Начну с тех,которые больше относятся к операторам. Операция замены s/рег.выражение/строка/ игнорировать регистр - опция i глобальная(по всей строке) замена -опция g; Пример:
 $x="This is test";
$x=~s/ /_/g;
print $x; #This_is_test
Очень полезная опция у s/// e -она означает что вторая строка не строка а выражение, результат которого и будет подставлен. Например,у вас есть файл в котором все записи о возрасте через год надо менять
 open OLD,"oldfile.txt" || die "Cannot open oldfile.txt $!\n";
open NEW,">newfile.txt" || die "Cannot open newfile.txt $!\n";
foreach(){
s/(\d+)(\s+год)/($1+1).$2/gie;
s/(\d+)(\s+лет)/($1+1).$2/gie;
print NEW $_;
}
close NEW;
close OLD;
или более показательным примером послужит функция urldecode,которая будет встречатся в каждой вашей программе,обрабатывающей формы:
sub urldecode{
local($val)=@_;
$val=~s/\+/ /g;
$val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;
return $val;
}
Также важным удобством в Perl являются операции для работы с файлами для выполнения схожих функций в других языках приходиться проделывать огромную массу работы. Аргументами могут быть как Файловые переменные,так и строки,представляющие имя файла.
ОперацияОписаниеПример использоввания
-rДоступен для чтенияunless(-r "myfile"){print "Cannot read myfile\n";}
-wДоступен для записи
-xДля исполнения
-oПринадлежит пользователюif(-o "index.htm"){chmod 0777,"index.htm";}
-RДоступен для чтения реальным
пользователем,а не только "эффективным".
Имеет значения для set-uid -скриптов
if(-r FILE){unless(-R FILE){die "Its not allowed to read this\n";}}
-WДоступен для записи реальным пользователем
-XДоступен для исполнения реальным пользователем
-OПринадлежит реальному пользователю
-eФайл или каталог Существуетunless(-e $htmlfile){
open HTML,">$htmlfile";
print HTMLFILE "";
close HTMLFILE;
}
-zСуществует,но имеет нулевую длинуif(-z 'tmpfile'){unlink 'tmpfile';}
-sРазмер файла в байтахsystem "rar m -m5 archive.rar $myfile" if -s $myfile > 1000;
-fФайл существует и является простым файлом
-dФайл существует и является каталогомif(-d 'public_html'){chdir 'public_html';}
-lСимволической ссылкой
-pКаналом FIFO
-uИмеет бит установки пользователя
-gИмеет бит установки группы
-kУстановлен sticky-бит
-tЯвляется терминальным устройством
-MВремя с последнего изменения (в днях)while(defiled($file=glob('*'))){
if(-M $file >= 7.0){
unlink($file);#удаляем слишком старые файлы
}
}
-AВремя последнего доступа(в днях)if(-A "$ENV{'HOME'}/public_html/index.html" < -A "$ENV{'HOME'}/.last"){print "Кто-то ходил на твою домашнюю страничку пока тебя не было!!!\n";}
-CВремя последнего обновления файлового индекса(в днях)



Еще есть и другие
функция open открывает файл
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,"имя файла";  #открыть файл для чтения
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,">имя файла"; #для записи
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,">>имя файла";#для записи в конец
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,"+<имя файла";#для чтения и записи
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,"|комманда"; #направить информацию на вход программы
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,"комманда|"; #считать информацию с выхода программы
open ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,"|комманда|"; #и то и другое вместе
Что какается открытия файлов,то вам как програмистам все очевидно, но с коммандами тоже все здорово,что пояснит хороший пример(из практики):
open MAIL,"|mail paaa@uic.nnov.ru";#Пошлем информацию по почте
print MAIL "Hello\n";
print MAIL "...\n";
print MAIL "...\n";
close MAIL;
когда вы открыли файл вы можете считать из него строку в скалярную переменную Вот так:$str=
избавиться от символа новой строки на конце поможет функция chomp, ведь этот символ может помешаться например в имени файла или при выводе на экран
print "Введите имя файла:";
$fname=;
chomp($fname);
open F,$fname || die "Cannot open $fname $!\n";
.....
Если также подставить списочную переменную,то получим список строк файла от текущей строки и до конца
print "Что искать:";
$search=;
chomp($search);
@L=;
foreach(@L){
print if /$search/;
}
а можно и так:
print "Что искать:";
$search=;
chomp($search);
foreach(){
print if /$search/;
}
бинарный файл можно читать и писать функциями sysread и syswrite:
sysread ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,$скалярная_перемменая,сколько_байт
syswrite ФАЙЛОВАЯ_ПЕРЕМЕННАЯ,$скалярная_перемменая,сколько_байт
функции split и join: @Список=split /рег.выр/,$скаляр;
$скаляр=join строка,@Список;
#Разбить строку слов,разделенных пробелами в список вы можете
@WordList=split / /,$String;
#После обработки снова обьединить
$String=join ' ',@WordList;
Встроеные функции Perl можно вызывать со скобками или без (как вам удобно), скобки программисты указывают или для красоты,или чаще,что устранить возможную неоднозначность в выраженнии:
printf "x=%d",$x;
printf ("x=%d",$x);#аналогично
Надеюсь что я вас позабавил примерами функций ;).
Примеры применения Perl для различных нужд...
Следующая программа переводит текстовый файл в формат HTML (вспомните сколько хлопот вам доставит отлов во всем файле '<', '>' и '&' чтоб заменить их на &tl; , > и & а как неплохо чтоб автоматически все http://www.... превратились в http://www....)
#!/usr/bin/perl
#txt2html
die "Usage: txt2html Infile OutFile\n" unless(@ARGV);
open IN,"$ARGV[0]" || die "Cannot open $ARGV[0] $! \n";
open OUT,">$ARGV[1]" || die "Cannot open $ARGV[1] $! \n";
while(){
s/&/&/g;
s/s/>/>/g;
s/\n/
\n/g;
s/(http:\/\/\S+)/$1<\/A>/g;
print OUT $_;
}
close IN;
close OUT;
Более подробную информацию о Perl вы можете получить по адресам:
http://www.perl.com
http://www.metronet.com/0/perlinfo/perl5/manual/perl.html
http://www.ActiveWare.com/

Заголовки запросов и ответов


Даже если вы и знаете кое-что о HTTP все равно не лишне будет вспомнить о том как это все работает тем более на эту информацию придется ориентироваться при написании CGI скриптов.
Этапы соедирения.
Первый этап это когда HTTP -клиент(браузер) соединяется с сервером.для этого он использует протокол TCP/IP соединение происходит к известному клиенту TCP-порту (80 -номер порта HTTP) (другие сервисы сидят на других портах ,например FTP и SMTP на 21 и 25)
Вторым этапом идет запрос клиента:клиент передает заголовок запроса и возможно(в зависимости от метода) тело сообщения запроса.В заголовке обязательно указывается метод ,URI,и версия HTTP,и может быть еще несколько необязательных полей
Третий этап -ответ сервера,который опять таки состоит из заголовка,в котором сервер указывает версию HTTP и код статуса, который может говорить о успешном или неуспешном результате и его причинах.Далее идет тело ответа.
Четвертым этапом происходит разрыв TCP/IP соединения.
HTTP -запрос.
Запрос состоит из Строки запроса(она обязательна) и остальных полей. Синтаксис строки :МЕТОД URI HTTP/версия
где -пробел , -переход на новую строку
Методы HTTP.
GET
Самый часто применяемый метод,в протоколе HTTP/0.9 был единственным методом,и применяется для извлечения информации по заданому URI Может быть условным если в заголовке указано поле If-Modified-Since:

HEAD
Почти идентичен GET но отличается тем что сервер не возвращает тело обьекта а только его заголовок (метаинформацию) программы могут применять его для проверки гиперссылок на правильность,доступность и изменения.

POST
передает данные для обработки их программой ,указаной в URIсдесь обязательно указывается поле Content-Length:

Сушествуют и другие ,реже применяемые методы,например PUT -для сохранения передавемых данных в указаном URI и DELETE для удаления ресурса.

Поля заголовка запроса.
После строки запроса идут поля заголовка запроса. Поля общего(general-header) заголовка (он общий как для запросов так и для ответов):
Date:
Указывает дату запроса,например:
Date: Sun, 20 Nov 1994 08:12:31 GMT

MIME-version:
Указывает версию MIME (по умолчанию 1.0)
MIME-version: 1.0

Pragma:
Содержит указания для таких промежуточных агентов как прокси и шлюзы,
Pragma: no-cache


Поля относящиеся к запросу(Request-Header):
Authorization:
Содержит информацию аутентификации
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

From:
Браузер может посылать адрес пользователя серверу
From: quake@doom.ru

If-Modified-Since:
используется при методе GET ресурс возвращается ,если он был изменен с указаного момента, может использоваться при кешировании.
If-Modified-Since:Mon 15 Jul 1997 00:15:24 GMT

Referer:
Содержит URL предшествующего ресурса.
Referer: http://www.uic.nnov.ru/~paaa/index.html

User-Agent:
Програмное обеспечение клиента.
User-Agent: Mozilla/3.0

Заголовок информации сообщения (Entity-Header) применяется как в запросах так и в ответах (при этом некоторые поля только в ответах):
Allow: (в ответе сервера)
Список методов,поддерживаемых ресурсом.
Allow: GET, HEAD

Content-Encoding:
идентифицирует метод кодировки,которым был закодирован ресурс
Content-Encoding: x-gzip

Content-Length:
Длина тела сообщения
Content-Length: 102

Content-Type:
Содержит тип ресурса(MIME),для текстовых еще и кодировку символов(необязательно)
Content-Type: text/html; charset=windows-1251

Expires: (в ответе сервера)
Дата окончания действия ресурса,применяется в кешировании для запрета кеширования устаревших ресурсов (в ответе)
Expires: Tue, 24 Sep 1998 23:00:15 GMT

Last-Modified: (в ответе сервера)
Время последнего обновления ресурса
Last-Modified: Tue, 23 sep 1998 13:48:40 GMT

Другие поля:
Поля Accept: указывают серверу выдавать только указаные форматы данных,которые клиент может распознать.
Accept: text/html
Accept: text/plain
Accept: image/gif
Примеры запросов:
Простейший запрос:
GET /index.html HTTP/1.0

Посложнее:
GET /somedir/somedoc.html HTTP/1.0
User-Agent: Mozilla/2.0
Accept: text/html
Accept: text/plain
Accept: image/gif

Передача данных CGI- скрипту через метод GET
GET /~paaa/cgi-bin/test.cgi?name=Dmitry&organization=%D3%ED%E8%E2%E5%F0%F1%E8%F2%E5%F2+%CD%E8%E6%ED%E5%E3%EE+%CD%EE%E2%E3%EE%F0%EE%E4%E0&Name=&email=&comment= HTTP/1.0
User-Agent: Mozila/2.0
Accept: text/html
Accept: image/gif

Используя метод POST данные передаются в теле сообщения запроса:
GET /~paaa/cgi-bin/test.cgi HTTP/1.0
User-Agent: Mozila/2.0
Accept: text/html
Accept: image/gif
Content-Type: application/x-www-form-urlencoded
Content-Length: 131

name=Lesha
&organization=%D3%ED%E8%E2%E5%F0%F1%E8%F2%E5%F2+%CD%E8%E6%ED%E5%E3%EE+%CD%EE%E2%E3%EE%F0%EE%E4%E0&Name=
&email=
&comment=
Ответ HTTP-сервера.
Ответ идет от сервера.Состоит он из строки состояния и затем поля ответа Общий заголовок(General-Header) и заголовок тела сообщения (Entity-Header),которые уже описаны при обсуждении запроса. и еще идет заголовок ответа(Response-Header).
Строка состояния имеет следующий формат:
HTTP/version Status-Code Status-Phrase
где HTTP/version версия,Status-Code -3х значный код,и Status-Phrase текстовая фраза, поясняющая код ,пример: HTTP/1.0 200 Ok
,200 -код означающий успешную обработку запроса,что и поясняет "Ok" Заголовок ответа состоит из полей:
Location:
Содержит URI ресурса,может быть использован для переключения клиента в другое место, если например ресурс был перемещен в другое место или на другой сервер.
Location: http://www.uic.nnov.ru/newlocation/index.html

Server:
Информация о програмном обеспечении сервера
Server: Apache/1.1

WWW-Autenticate:
Параметры аутентификации.
WWW-Autenticate: Basic realm="doomsday"

Коды ответов HTTP.
Код статусаЗначение
200OK
201Успешная команда POST
202Запрос принят
203Запрос GET или HEAD выполнен
204Запрос выполнен но нет содержимого
300Ресурс обнаружен в нескольких местах
301Ресурс удален навсегда
302Ресурс отсутствует временно
304Ресурс был изменен
400Плохой запрос от клиента
401Неавторизованый запрос
402Необходима оплата за ресурс
403Доступ Запрещен
404Ресурс не найден
405Метод не применим для данного ресурса
406Недопустимый тип ресурса
410Ресурс Недоступен
500Внутренняя ошибка сервера
(это по вашу душу,юные CGI-программисты ;( )
501Метод не выполнен
502Неисправный шлюз либо перегруз сервера
503Сервер недоступен/тайм-аут шлюза
504Вторичный шлюз/тай-аут сервера
Более подробное описание всех кодов можно найти в RFC-1945
Несколько примеров:
HTTP/1.0 200 Ok
Date: Wed, 25 Sep 1998 23:00:00 GMT
Server: Apache/1.1
MIME-version: 1.0
Last-Modified: Mon 15 Nov 1996 15:20:12 GMT
Content-Type: text/html
Content-Length: 2000

Hello

......

А вот такое сервер выдаст в неудачном случае:
HTTP/1.0 404 Not Found

CGI-заголовок.
В том случае когда запрашиваемый URI есть CGI-скрипт сервер базируясь на данных запроса создает среду переменных CGI и передает управление скрипту скрипт должен выдать CGI-заголовок,после которого и идет тело ответа,сгенерированое скриптом.
Заголовок (CGI-Header) состоит из полей:
Content-Type:
Должно обязательно присутствовать,если есть тело.
Content-Type: text/html

Location:
Содержит URL ресурса на который скрипт перенаправляет запрос.Как правило,если присутствует это поле больше ничего не указывается.
Location: http://www.idsoftware.com/index.html

Status:
Позволяет CGI скрипту вернуть статус обработки,если это поле не задано,то сервер подразумевает "200 Ok"
Status: 404 Not found

На базе этой информации сервер и формирует окончательный заголовок,который и передается клиенту.
Примеры:
Обычно такое выдает скрипт: Content-Type: text/html  ....... Но иногда такое(когда он служит для перенаправления): Location: http://www.mustdie.ru/  А вот пример возврата статуса: Content-Type: image/gif Status: 190 Its seems great like a playing doom! WOW!  GIF89a........ 
nph-скрипты.
Иногда возникает необходимость чтобы CGI -скрипт сам отвечал напрямую клиенту, минуя разбор заголовка.Это во-первых уменьшает нагрузку на сервер,и во вторых, что самое главное такой прямой ответ клиенту позволяет скрипту полностью контролировать транзакцию.Для этого существуют nph-скрипты(Not Parse Header) ,имя скрипта должно начинаться с префикса "nph-" ,Например "nph-animate.cgi" .Такие скрипты сами формируют HTTP-ответ клиенту,что полезно при анимации:
#!/usr/bin/perl #nph-animate.cgi  $times = 20; #Заготовте несколько небольних gif-файлов для этой программы @files = qw(img0.gif img1.gif img2.gif img3.gif);  select (STDOUT); $|=1; #autoflush mode on #Generate header print "HTTP/1.0 200 Okay\n"; print "Content-Type: multipart/x-mixed-replace;boundary=myboundary\n\n";  print "--myboundary\n"; for ($num=1;$num<=$times;$num++) {    foreach $file (@files) {       print "Content-Type: image/gif\n\n";       open(PIC,"$file");       print ;       close(PIC);       print "\n--myboundary\n";       sleep(3);       }    } print "\n--myboundary--\n"; 
Этот пример вам выдаст анимацию ,составленую из нескольких .gif -файлов.Если же вы получили вместо анимации сообщение об ошибках,то вам следует,может быть перейти к следующей главе, которая поведает вам о правах доступа- того,без чего Unix не был бы Unixом.

Права Доступа


Я бы ни за что не написал этот раздел,если бы он не был так важен.Сидя в пределах своей домашней директории и занимаясь только тем,что качаете с Инета всякую херню,вы возможно и не задавались некоторыми вопросами....а зря.......... Ведь немного надо,чтоб попортить нервы начинающему CGI -програмисту.
Одна из таких вещей это права доступа......
Начнем с того ,что в системе Unix каждый пользователь имеет свой идентификатор- число,уникально идентифицирующее его в этой системе.(Мой логин paaa а ему соответсвует число 1818).Это число внутреннее для операционной системы,для пользования оно представлено как логин,который и соответствует пользователю.
Только не надо думать о пользователе,как о конкретном человеке сидящим за клавиатурой, пользователем может быть и какой-нибудь процесс.Важно отметить что пользователь-это определенная область прав доступа,которая ему соответствует.(Вы например не можете удалить файлы из каталога другого пользователя). Это и дает возможность стабильной работы всей системы.
Итак есть идентификатор пользователя.Также имеется идентификатор группы.
Группа служит для выделения пользователей по группам. Например у пользователей группы users (Обычные пользователи) не такие права как у группы wheels (административная группа).
Каждый процесс который вами запущен(Будь то Netscape,терминал,или текстовый редактор)получают ваши идентификаторы пользователя и группы. таким образом исполняются от вашего имени.
Теперь рассмотрим повнимательней файловую систему.В Unix с файлом связано много характеристик. Во-первых в системе нет "ничьих" файлов ,все файлы имеют владельца-пользователя и владельца-группу. Любой файл который вы создаете автоматически получает ваш идентификатор.По этому система очень легко отслеживает, чьи это файлы и каталоги.
Следующее новшество по сравнению с DOS это права доступа к файлу.Их может сменить только тот пользователь которому принадлежит файл,или супервизор.(Это в отличии от DOS где каждая дрянь типа вируса может снять атрибут readonly)
Права доступа задаются обычно числом в восьмеричном коде и разбиты на 3 части по 3 бита: Каждая часть задает права доступа для конкретной группы:
1я -права доступа для пользователя,которому принадлежит файл
2я -для группы которой принадлежит файл
3я -для всех остальных
В каждой такой категории выделяются 3 права: Право на чтение,Право на запись,и право на исполнение. (все права и аттрибуты очень наглядно показаваютя командой ls с ключом -l) Так как исполнять каталоги бессмыслено,то право на исполнение для них означает право обращатся к файлам из этого каталога.
БитОписание
8Право на чтение для пользователя
7Право на запись для пользователя
6Право на исполнение для пользователя
5Право на чтение для группы
4Право на запись для группы
3Право на исполнение для группы
2Право на чтение для всех остальных
1Право на запись для всех остальных
0Право на исполнение для всех остальных
Изменяются права командой chmod,ее синтаксис такой:
chmod [u|g|o]{+|-}{r|w|x} file
chmod number file
,где u-user,g-group,o-other,r-read,w-write,x-execute;--удалить,+-установить
Примеры:
chmod +r file.txt #разрешает всем право на чтения файла
chmod u+w file.txt #устанавливает для владельца файла право на запись в него
chmod +x gbook.cgi #право на исполнение для всех,как для ползователя,группы,и для других
chmod 0777 cgi-bin #Разрешает самые широкие права доступа для cgi-bin

Приоткрытии файла программой,операционная система сравнивает идентификатор пользователя с идентификатором пользователя владельца файла, если они равны,то действуют права пользователя,если не равны то сравниваются идентификаторы группы,если и они не равны,то действуют права доступа для остальных остальных.В том случае если у процесса нет достаточных прав,система возвращает ошибку. Следует заметить ,что для супервизора root права доступа не проверяются.
Можно выполнить скрипт,только если есть права на его исполнение. Вот почему следует давать chmod +x *.cgi иначе ваши скрипты станут просто недоступными. Но и это еще не все.....
Ваш скрипт может обращатся к вашим файлам (например ведет базу данных гостевой книги). Все выглядит нормально,но только ничего не работает,файл в который вы намеревались писать,
не открывается,знакомая проблема ;(( ?.Так вот чтобы вы не мучались в догадках Ваш скрипт не может получить доступ к вашим файлам,потому что он выполняется не вами (не с вашим идентификатором), а от имени nobody (непривелигированый пользователь).Это мера предосторожности направлена на то, чтоб скрипты ,взбесившись из-за неправильно переданых параметров(или вообще от глюков) не могли ничего повредить ценного и важного на сервере.
Поэтому к тем файлам,к которым скрипт по смыслу должен обращатся нужно присвоить самые широкие права доступа 0777
Например в случае гостевой книги chmod 0777 guestbook.dat
Если также важно чтоб скрипты могли заводить новые файлы в cgi-bin то надо дать также права на это chmod 0777 cgi-bin
Если вы видите что ваш скрипт не может обратится к какому-то файлу,то это в 99% случаев из-за вашей забывчивости.!!!
На самый крайний случай воспользуйтесь setuid-скриптами (к этому делу ,если вы на это решились,отнеситесь ОЧЕНЬ серьезно,так как целые тома по безопасности в Unix посвящены именно setuid-скриптам). Хочу сразу предупредить ,сам я таких не писал,да и вам не особенно советую.Но для общего как говорится развития,имейте в виду следующую информацыю.
Кроме указания прав доступа,существуют специальные биты у файла.Это биты установки пользователя и группы. Когда процесс выполняется(простой процесс) то его реальный и эффективный идентификаторы пользователей совпадают,идентификаторы групп тоже. На самом деле значение имеют как раз эффективные значения пользователя и группы,они учавствуют в сравнении прав доступа. Нельзя ли их как-то изменить,когда уж совсем нужда заставит? Можно! .На этот вопрос дают ответ программы с установленым битом пользователя.Когда система запускает такую программу,она присваивает новому процессу не идентификатор того пользователя,что запустил ее, а идентификатор пользователя-владельца исполняемого файла.
Самый классический пример setuid-программ это программа passwd ,предназначеная для смены пароля пользователя. Такие данные как пароль и прочие характеристики пользователей хранятся в специальном файле,который имеет огромное значение при входе в систему. Так как это системный файл,то открыть к нему доступ на запись всем-значит подвергнуть ВСЮ систему риску,ведь любое неправильное изменение его повлечет катастрофические последствия(в конце концов бывает просто хулиганство). Поэтому доступ к этому файлу закрыт для всех пользователей.А что если надо сменить пароль? Запускаем программу passwd ,если глянуть на ее аттрибуты ,то видно что она принадлежит root -супервизору, и еще имеет установленый бит setuid. Так корректно обходится эта проблема.
Если вы все-же решили попытаться ,то знайте ,что сделать программу setuid можно
коммандой : chmod +s myprogramm

И как всгда Примерчик напоследок:
Эта программа выдает содержимое вашей директории public_html в том случае,если она доступна для чтения,и для каждого файла указывает ,можно ли его читать,писать и исполнять. Попробуйте ее сделать setuid и посмотрите как изменится результат.
#!/usr/bin/perl #listmydir.cgi print "Content-Type: text/html\n\n"; if(!(-r '..')){  print ".. is not allowed for reading ;)))))\n";  } else{  @list=glob('../*');  foreach(@list){    print "$_";    print " readable" if -r;    print " writable" if -w;    print " executable" if -x;    print "
\n"; } }

Генерация ответа


Большую часть того что нужно знать о генерации ответа,я сказал в разделе Заголовки запросов и ответов.Нет,не угадали! Я не буду сдесь говорить о всяком дизайне того что вы выдаете.Этому вы успели напрактиковатся на HTML -страничках.
Я поговорю о MIME (Multipurpose Internet Mail Extension).И о разных браузерах.
Стандарт MIME появился в электронной почте (e-mail) потому что остро стала проблемма пересылки по e-mail различных данных в различных форматах.Так как HTTP тоже работает с различными типами данных то поэтому тоже использует MIME для своих нужд. Типы MIME состоят из Типа и подтипа (например text/plain,где text-указывает на наличие текстового содержимого,а plain-уточняет его как простой текст) приведеный ниже список (он далеко не полн,типов MIME огромное количество) описывает некоторые часто встречающиеся типы.: text/html text/plain text/richtext image/gif image/jpeg image/tiff audio/basic audio/32kadpcm audio/ video/mpeg video/quicktime multipart/mixed multipart/alternate multipart/ application/octet-stream application/msword application/postscript message/digest
Информация о MIME больше возможно пригодится вам в том случае если вы собираетесь работать из ваших скриптов с электронной почтой,но и для WWW она не повредит. Онобенно знание Content-Type:
Content-Type:
Состоит из типа и подтипа типы могут быть как стандартные так и экспериментальные начинающиеся с префикса 'x-':

text
Текстовые данные.Первым подтипом который включен сюда это plain,что значит простой текст. сюда же включен самый ходовой формат html .У типа text как и у многих типов могут быть параметры,главным из них является charset он как раз и указывает на раскладку символов, которая применена в тексте, так что если вы хотите указать браузеру какую раскладку применять, то просто укажите charset:
Content-Type: text/plain; charset=us-ascii
Content-Type: text/html; charset=iso-8859-1
Content-Type: text/html; charset=koi8-r
multipart
Данные которые могут состоять из нескольких частей,различных типов данных.Поэтому параметром multipart служит boundary, позволяюший указать разделитель.Каждый фрагмент в многочастевом сообщении имеет свой Content-Type: (Он может быть также multipart,т.е. допускаются вложеные multipart,главное чтоб boundary были разными).В электронной почте применяется больше multipart/mixed (основной подтип) и multipart/alternative (Он отличается тем что показывается одна из альтернатив,например сообщение шлется в простом и HTMLом форматах,и почтовая программа показывает либо часть,которую она способна отобразить). В WWW -програмировании распостранен x-mixed-replace ,который означает что следующая часть должна заменить предыдущую после подгрузки, что применяется для анимации.
Теперь о разделителе,его надо выбирать так,чтоб он не встретился где-то в данных (т.е. что-то вроде "diUr344rnmvforgefvrg923rghyj2").Когда вы задали разделитель,например boundary="boundary" то когда закончилась одна часть,вы должны выдать строку --boundary,последняя часть --boundary--,причем эти разделители должны быть на отдельной строке,а не сливаться с текстом:
Пример:
 MIME-Version: 1.0  Content-Type: multipart/alternative; boundary="w23renff491nc4rth56u34-9449"   --w23renff491nc4rth56u34-9449  Content-Type: text/plain; charset="koi8-r"   Hello,World!!  --w23renff491nc4rth56u34-9449  Content-Type: text/html; charset="us-ascii"   

Hello,Word!!


Hello people! --w23renff491nc4rth56u34-9449--
message
Представляет инкапсулированое почтовое сообщение.Используется в e-mail ,а не в WWW.
image
Некоторое Графическое изображение.(чаще всего image/gif и image/jpeg)
audio
Аудиоданные.
video
Видеоданные.
application
бинарные данные какого-нибудь приложения.В том случае если данное приложение может быть запущено,Браузер запускает его.Например при поступлении данных application/msword Браузер спросит,нужно ли запустить Word для просмотра досумента.При отсутствии нужного приложения браузер спросит в каком файле сохранить данные.Подтип octet-stream как раз и означает поток байт информации,который и используется по умолчанию.(К сожалению не все так гладко,известен глюк в Netscape Navigator'е который вместо того чтоб сохранить application/octet-stream пытается его показать как text/plain что если это сгенерировано из CGI,ни к чему хорошему не приводит ;(()
Что касается application ,то Вы можете тут смело извращатся,используя x- типы данных,
Например application/x-fuck-to-netscape-navigator. ;)))))
Часто используемый параметр name позволяет указать имя файла.Например:
Content-Type: application/msword; name="readme.doc"
Что полезно при полученнии файлов через HTTP,причем этот параметр может применятся и для других типов таких image или audio ,Например:
Content-Type: image/gif; name="myfoto.gif"

Content-Transfer-Encoding:
Применяется больше в системе электронной почты и обозначает метод кодирования, которым были закодированы данные при передаче сообщения.Например:
7bit 8bit quoted-printable base64 binary x-типы
MIME-Version:
Указывает версию MIME .


Теперь поговорим о разных браузерах вы знаете что браузеры бывают разные,разных версий на разных платформах, поддерживают и не разные тэги и глюки у них тоже разные.....;((( .
Это могло попортить много нервов WEB-дизайнерам и конечно же нам ,CGI-програмистам. Профессионально написаный сайт от просто хорошего отличается тем что хорошо выглядит Не только на экране того браузера,которым пользуется сам его автор,а на других тоже.
Если вы используете JavaScript для своих страничек,то вы уже наверно использовали (или хотя бы вам в голову приходила мысль использовать)свойства navigator.AppName navigator.AppCodeName navigator.appVersion navigator.userAgent:
 или   
Your Name:
Your age:
Ну вот ,на этом можно закончить это краткое введение в HTMLые формы.
Итак,У нас на входе скрипта данные формы,закодированые методом urlencode Положеные в Переменную QUERY_STRING или подаваемые на STDIN.Мы должны вопервых их получить.
 if($ENV{'REQUEST_METHOD'} eq 'GET'){#Анализируем метод,GET или POST    $query=$ENV{'QUERY_STRING'};    }  elsif($ENV{'REQUEST_METHOD'} eq 'POST'){    sysread STDIN,$query,$ENV{'CONTENT_LENGTH'};    } 
Вот,мы уже считали наш запрос в переменную $query.Теперь пришло самое время ее обработать. Мы знаем что поля разделены символом '&' значит используем его в качестве разделителя функции split:
 @formfields=split /&/,$query;        
Вот разделили,а теперь организуем цикл foreach по полученым полям @formfields
 foreach(@formfields){    if(/^Name=(.*)/){$name=urldecode($1);}    if(/^Age=(.*)/){$age=urldecode($1);}    } 
Сдесь выражение в регулярном выражении в круглых скобках (.*) после знака '=',запоминается в скалярную переменную $1 ,которая затем и декодируется нашей старой и знакомой функцией urldecode (я предупреждал,что она будет почти в каждой вашей CGI-программе)
sub urldecode{    #очень полезная функция декодирования  local($val)=@_;  #запроса,будет почти в каждой вашей CGI-программе  $val=~s/\+/ /g;  $val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;  return $val;  } 
Так мы проходим по всем полям,которые нам переданы.Это стандартный подход,он годится в качестве шаблона.У вас может возникнуть вопрос,а что делать если вам переданы данные от списка у которого задана возможность выбора нескольких элементов и данные поступают в таком виде: Sel=opt1&Sel=opt2&Sel=opt9. Тут тоже нет никаких проблем,просто запихиваем эти поступающие значения в массив.
 foreach(@formfields){    .....    if(/^Sel=(.*)/){push @Sel,urldecode($1);}    .....    } 
И потом спокойно оперируем с Полученым Массивом @Sel.
На этом можно так сказать заканчивается шаблонная часть скрипта и начинается содержательная, которая зависит только от вашей фантазии.....
Вы можете сколько угодно анализировать полученые значения,обращатся при этом к различным файлам .Если вы к этому приложите фантазию,то кто знает что получится....
А Пока Ради примера я вам напишу скрипт,который ведет социологическое исследование насчет курения и отношения к нему.Может он слишком массивен для данного пособия, но зато он наглядно показывает как достаточно простыми средствами можно проводить социологические исследования.