On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

CGI&Perl - Учебное пособие по CGI - программированию от Леши

Оглавление:


Пару слов от автора
Краткое лирическое отступление насчет CGI
Итак ...приступим...
Переменные среды CGI
Прекрасный язык Perl
Заголовки запросов и ответов
Права Доступа
Генерация ответа
Обработка Форм
Изображения ismap
Анимация
Несколько советов по отладке
Trics and traps
Примеры приложений:
Кто посещает мою страничку?
Гостевая книга
Счетчик посещений

Вместо заключения


Пару слов от автора


Что меня заставило взятся за этот нелегкий труд написания данного учебного пособия. Ну во первых то что практически НЕТ ничего по 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

<HTML>
<BODY>
.......

</BODY></HTML>


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

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



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

На кого ориентировано данное учебное пособие -спросите вы ? Ну в принципе на достаточно широкую аудиторию тех, кто занимается Интернет-программированием и кто хотел бы освоить премудрости интерфейса CGI. Данная книга будет весьма полезна для web-дизайнеров, системных администраторов интернет-серверов, программистов и для простых пользователей интернет, которые хотели бы сделать свой сайт по-настоящему достойным называться хорошим сайтом.
Так как интернет в основном строится на операционной системе UNIX , то изложеный сдесь материал может быть без особых модификаций реализован на практически любой UNIX-системе.
Кроме того, я также делаю предположение , что ваш web-сервер поддерживает интерфейс CGI и для вас эта поддержка включена. (на "халявных" серверах администраторы отключают CGI и SSI для пользовательских директорий - просто это такая политика - предоставлять только ОЧЕНЬ МИНИМАЛЬНЫЙ сервис.) Так что если вы хотите изучать CGI то вам нужет нормальный ,полнофункциональный сервер. Если же вы сами являетесь системным администратором на своем сервере , то для вас, естественно нет проблем, ведь включить CGI для какой-нибудь директории - это просто подправить одну строчку в файле конфигурации сервера.
Замечание:
Если же вы используете Windows NT ,то материал данной книги вам будет тоже очень полезен, однако будьте готовы к тому что в некоторые скрипты придется вносить значительные изменения. В некоторых случаях,когда сказывается то , что возможности NT по работе с сетью намного хуже, чем у UNIX,то некоторые скрипты вовсе нельзя будет использовать.

Давайте вместе писанем какой нибудь простенький скриптик а потом я вам расскажу где сдесь собака порылась....
Ну сначала в своем домашнем каталоге создайте директорию 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 "<HTML><BODY>";
print "<H1>Hello you!!!</H1>";
print "</BODY></HTML>";

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

chmod +x first.cgi

Ну вот,подходим к торжественному моменту.... наберите в строке браузера http://www.ваш.сервер.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. Обычно он находится /usr/bin/perl или /usr/local/bin/perl ,выяснить это можно одной из комманд which perl или whereis perl ну или (что очень долго) запустить полный поиск find / -name perl -print.
Вторая строка это просто коментарий -вы можете тыкать чо угодно после знака #, однако я пишу обычно во второй строке название скрипта, что очень удобно.
Затем идет print "Content-Type: text/html\n\n"; Это заголовок указывающий тип содержимого.
Все что скрипт печатает в свой стандартный вывод STDOUT идет на обработку к серверу. Пустая строка отделяет заголовок от тела,которое в нашем случае представляет собой
<HTML><BODY>
<H1>Hello you!!!</H1>
</BODY></HTML>
Сервер обработает ответ скрипта и на базе него сформирует и пошлет браузеру ответ.(Сервер обычно не изменяет тела сообщения,он только дополняет заголовок нужными для работы протокола 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=/var/www/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)
HTTP_HOST
Имя хоста к которому обращается браузер. Так как физически на одном сервере может находиться сразу много серверов (Виртуальные Хосты), то должен быть способ сообщить серверу к какому именно идет обращение. Скрипт же может тоже в зависимости от этой переменной производить различные действия, таким если он используется на сайтах сразу нескольких виртуальных хостов.
Пример:HTTP_HOST=www.nnov.city.ru


Ну,начнем применять на практике усвоеные уроки.
#!/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 "<HTML><HEAD><TITLE>CGI-Variables</TITLE></HEAD>\n";
print "<BODY>\n";
print "Enter here something:<ISINDEX><BR>\n";
print "Your request is:$ENV{'REQUEST_STRING'}<BR>\n";
print "Decoded request is:urldecode($ENV{'REQUEST_STRING'})<BR>\n";
print "<HR>\n";
print "Variables:<BR>\n";
print "<I><B>REQUEST_METHOD</B></I>=$ENV{'REQUEST_METHOD'}<BR>\n";
print "<I><B>QUERY_STRING</B></I>=$ENV{'QUERY_STRING'}<BR>\n";
print "<I><B>CONTENT_LENGTH</B></I>=$ENV{'CONTENT_LENGTH'}<BR>\n";
print "<I><B>CONTENT_TYPE</B></I>=$ENV{'CONTENT_TYPE'}<BR>\n";
print "<I><B>GATEWAY_INTERFACE</B></I>=$ENV{'GATEWAY_INTERFACE'}<BR>\n";
print "<I><B>REMOTE_ADDR</B></I>=$ENV{'REMOTE_ADDR'}<BR>\n";
print "<I><B>REMOTE_HOST</B></I>=$ENV{'REMOTE_HOST'}<BR>\n";
print "<I><B>SCRIPT_NAME</B></I>=$ENV{'SCRIPT_NAME'}<BR>\n";
print "<I><B>SCRIPT_FILENAME</B></I>=$ENV{'SCRIPT_FILENAME'}<BR>\n";
print "<I><B>SERVER_NAME</B></I>=$ENV{'SERVER_NAME'}<BR>\n";
print "<I><B>SERVER_PORT</B></I>=$ENV{'SERVER_PORT'}<BR>\n";
print "<I><B>SERVER_PROTOCOL</B></I>=$ENV{'SERVER_PROTOCOL'}<BR>\n";
print "<I><B>SERVER_SOFTWARE</B></I>=$ENV{'SERVER_SOFTWARE'}<BR>\n";
print "<I><B>HTTP_ACCEPT</B></I>=$ENV{'HTTP_ACCEPT'}<BR>\n";
print "<I><B>HTTP_USER_AGENT</B></I>=$ENV{'HTTP_USER_AGENT'}<BR>\n";
print "<I><B>HTTP_HOST</B></I>=$ENV{'HTTP_HOST'}<BR>\n";
print "<HR>\n";
print "All enviroment:<BR>\n";
foreach $env_var (keys %ENV){
 print "<I>$env_var=$ENV{$env_var}</I><BR>\n";
 }
print "</BODY></HTML>\n";
Так как все ваши .cgi -файлы должны быть исполняемыми то чтоб облегчить себе жизнь заведите себе в директории cgi-bin командный файл mkcgi ,содержащий
#!/bin/sh
#mkcgi
chmod +x *.cgi
и сделайте его в свою очередь исполняемым chmod +x mkcgi -он сильно упростит вам жизнь.
Ну а теперь запускайте скрипт......
Изучив информацию,выдаваемую данным скриптом вы сможете лучше ориентироваться в переменных окружения CGI.

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


Вы наверное обратили свое внимание что CGI скрипты пишутся обычно на языке Perl (Practical Extraction and Report Language)- очень удобном языке,впитавшем из других все лучшие черты.Может у вас возникнуть сомнение :Ну вот!Изучать новый язык программирования!? Спешу вас успокоить,изучение Perl не будет в тягость (я сужу по своему опыту!). Вы даже сами не заметите как выучите его.Если вы хоть когда-нибудь программировали скажем на C и использовали утилиту grep для поиска регулярных выражений в тексте,то вам будет еще легче. Для Perl родной платформой является Unix поэтому пользователям PC он мало известен. Мое целенаправленое доведение Perl до широкой публики началось с того что я скачал Perl под Windows (фирмы ActiveWare) К нему прилагается отличная гипертекстовая HTML- документация, даже быстрого просмотра которой хватит , чтобы начать хорошо и широко использовать его. Хоть он значительно уступает и по скорости и по эффективности своему Unix'ному аналогу, все равно самый лучший способ изучить язык это программировать на нем. Если вы как я дома используете большую часть времени не Windows а Unix то с изучением Perl у вас вообще не должно быть особых сложностей. Я же от себя могу сказать, что даже после небольшого опыта изучения его, он стал моим любимым языком программирования....

Все в нем сделано для удобства программиста (в отличии например от 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=<STDIN>;
foreach(@Data){
 chomp;
 print if /^From:/;
 }
аналогичен такому:
@Data=<STDIN>;
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=<STDIN>;
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-hA-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 "<HTML><BODY></BODY></HTML>";
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(MAIL,"|mail paaa@uic.nnov.ru");#Пошлем информацию по почте
print MAIL "Hello\n";
print MAIL "...\n";
print MAIL "...\n";
close(MAIL);
когда вы открыли файл вы можете считать из него строку в скалярную переменную Вот так:$str=<FILE>
избавиться от символа новой строки на конце поможет функция chomp, ведь этот символ может помешаться например в имени файла или при выводе на экран
print "Введите имя файла:";
$fname=<STDIN>;
chomp($fname);
open(F,$fname)|| die "Cannot open $fname $!\n";
.....
Если также подставить списочную переменную,то получим список строк файла от текущей строки и до конца
print "Что искать:";
$search=<STDIN>;
chomp($search);
@L=<F>;
foreach(@L){
 print if /$search/;
 }
а можно и так:
print "Что искать:";
$search=<STDIN>;
chomp($search);
foreach(<F>){
 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; , &gt; и &amp; а как неплохо чтоб автоматически все http://www.... превратились в <A href="http://www...." >http://www....</A>)
#!/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(<IN>){
 s/&/&amp;/g;
 s/</&lt;/g;
 s/>/&gt;/g;
 s/(http:\/\/\S+)/<A href="$1">$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 -запрос.
Запрос состоит из Строки запроса(она обязательна) и остальных полей. Синтаксис строки :МЕТОД <SP> URI <SP> HTTP/версия <CRLF>
где <SP> -пробел ,<CRLF> -переход на новую строку
Методы 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

Поле Host: служит для того , чтобы указать, к какому хосту идет обращение. Данное поле не входит в число обязательных. Однако оно является необходимым в тех случаях, когда одному физическому серверу соответствует несколько виртуальных хостов. В этом поле тогда указывается какой из виртуальных хостов имеется в виду.
Host: www.nnov.city.ru

Примеры запросов:
Простейший запрос:
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=someorg&Name=&email=&comment= HTTP/1.0
User-Agent: Mozila/2.0
Accept: text/html
Accept: image/gif

Используя метод POST данные передаются в теле сообщения запроса:
POST /~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+%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 <SP> Status-Code <SP> 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

<HTML><HEAD><TITLE>Hello</TITLE></HEAD>
<BODY bgcolor="green" text="yellow">
......
</HTML>
А вот такое сервер выдаст в неудачном случае:
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

<HTML><HEAD>.......
Но иногда такое(когда он служит для перенаправления):
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 <PIC>;
      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 "<A href=\"$_\">$_</A>";
   print "&nbsp;readable" if -r;
   print "&nbsp;writable" if -w;
   print "&nbsp;executable" if -x;
   print "<BR>\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"

 <H1>Hello,Word!!</H1>
 <HR>
 <FONT size=+1 color=red>Hello people!</FONT>
 --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:
<SCRIPT language="JavaScript">
 if(navigator.AppName=="Netscape"){
  /*Сделать чо-нибудь специфичное для Netscape*/
  }
 else if(navigator.AppName=="Microsoft Internet Explorer"){
  /*Сделать чо-нибудь специфичное для Explorer*/
  }
 else{
  /*Не делаем специфичных вещей-хрен его знает с каким браузером мы имеем дело*/
  }
</SCRIPT>
или
<SCRIPT language="JavaScript">
if((navigator.AppName=="Netscape")&&(parseFloat(navigator.appVersion)<3.0)){
   document.writeln("Пользуетесь слишком старым браузером");
   }
</SCRIPT>

Ну не волнуйтесь вы так ,мы CGI-программисты не в самых худших условиях на этот счет. Вспомните о том что браузер сам при запросе посылает вам данные о себе и о своей версии. И делает он это для того,чтобы эту информацию можно было учесть.
В запросе он указывает User-Agent: которое и попадает на сервере в переменную среды HTTP_USER_AGENT ,которую и можно использовать.
Например если в ней содержится Mozilla/3.01Gold (Win95;I) то значит вы имеете дело с Netscape (Mozilla-кодовое название Netscape Navigator'а),версии 3.01Gold и далее после имени и версии может следовать необязательная информация ,например как в приведеном примере о платформе Win95 и о том является ли версия U -для США (USA) или I -международной(International). Напомню,что такая информация необязательна.(То есть если браузер послал информацию User-Agent: то гарантировано расчитывать вы можете только на Название/Версия).
Ну вот я слишком много развел демагогии,пора переходить к практическим примерам.
Допустим ваш скрипт генерирует какие-то тэги,которые слишком старые браузеры не поддерживают,причем без них не обойдешся,они составляют всю 'изюминку' сайта.
#!/usr/bin/perl
#oldbrowser.cgi
print "Content-Type: text/html\n\n";
if(defined ($ENV{'HTTP_USER_AGENT'})){
 $browser=$ENV{'HTTP_USER_AGENT'};
 ($vers)=($browser=~/\/(\d+\.\d+)/);
 if(($browser=~/mozilla/i)&&($vers<=2.0)){
   print "<HTML><HEAD><TITLE>Too old!</TITLE></HEAD>";
   print "<BODY bgcolor=\"red\" text=\"black\">";
   print "<CENTER><H1>Ваш Netscape Слишком старый для этого сайта";
   print "(старость не радость;))</H1></CENTER>";
   print "</BODY></HTML>";
   exit;
   }
 if(($browser=~/msie/i)&&($vers<=3.0)){
   print "<HTML><HEAD><TITLE>Too old!</TITLE></HEAD>";
   print "<BODY bgcolor=\"red\" text=\"black\">";
   print "<CENTER><H1>Ваш Explorer устарел";
   print "(а не пора ли сделать апгрейт хотя бы до 4.0 версии)</H1></CENTER>";
   print "</BODY></HTML>";
   exit;
   }
 }
print "<HTML><HEAD>.........";
Ну уже почувствовали,насколько это здорово.А вот еще примерчик.Это из разряда того, что тэги бывают разные.Например в Explorer есть тэг BGSOUND предназначеный для проигрывани музыки на страничке.(В Netscape этого тега нет,и поэтому для втыкания музыки приходится использовать подключаемые модули plugin).Мутится с этими Плугинами Вам в облом,а хочется побаловать человека хорошей музыкой,если браузер позволяет.
   ...
   ...
if($ENV{'HTTP_USER_AGENT'}=~/msie/i){
  print "<BGSOUND src=\"jmj00.mid\">";
  }
elsif($ENV{'HTTP_USER_AGENT'}=~/mozilla/i){
  #Оставлю сдесь коментарий,что воткну что-нибудь типа музыки,для Netscap'а ,
  #Когда мне не будет так в облом это делать.......
  }
Ну вот вы уже можете управлять этим процессом.Только не забывайте,что если вы не получили информацию о клиенте(так может быть,если например ваш скрипт вызвал какая-нибудь поисковая машина) то не в этом случае не надо делать никаких предположений,а просто пусть ваш скрипт продолжает делать то что должен был делать.

Как всегда Примерчик на последок.Этот примерчик позволит выбирать из списка файлов. и загружать что пользователь хочет.
#!/usr/bin/perl
#download.cgi

sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
@Filelist=qw(index.html readme.txt jmj00.mid gunshot.wav foto.gif);
@Sel_list=();
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
elsif($ENV{'REQUEST_METHOD'} eq 'POST'){sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});}
if($query eq ''){
 #Если никаких данных не подано на обработку,то сгенерируем форму,
 #которую и предложим заполнить пользователю.
 print "Content-Type: text/html\n\n";
 print "<HTML><HEAD><TITLE>File Downloading</TITLE></HEAD>";
 print "<BODY bgcolor=\"white\">";
 print "Выберите файлы которые вы хотите загрузить:<BR>";
 print "<FORM METHOD=\"POST\">";
 print "<SELECT NAME=\"file\" size=4 multiple>";
 foreach(@Filelist){
   print "<OPTION value=\"$_\">$_";
   }
 print "</SELECT><BR>";
 print "<INPUT TYPE=\"Submit\" value=\"Download!\">";
 print "</FORM>";
 print "</BODY></HTML>"
 }
else{
 @formfields=split(/&/,$query);
 foreach(@formfields){
   if(/^file=(.*)/){push(@Sel_list,urldecode($1));}
   }
 unless(@Sel_list){
   print "Content-Type: text/html\n\n";
   print "<HTML><BODY><CENTER><H1>Вы должны выбрать что-то из списка";
   print "</H1></CENTER></BODY></HTML>";
   }
 else{
   print "Content-Type: multipart/mixed;";
   print "boundary=\"bhy3e23r4t34tnehtpo7678nneu4232y213vdg\"\n\n";
   print "--bhy3e23r4t34tnehtpo7678nneu4232y213vdg\n";
   foreach(@Sel_list){
     print "Content-Type: application/x-qwerty; name=\"$_\"\n\n";
     open(F,"$_");
     print <F>;
     close(F);
     print "\n--bhy3e23r4t34tnehtpo7678nneu4232y213vdg\n";
     }
   print "Content-Type: text/html\n\n";
   print "<HTML><H1>Thats all folks!</H1></HTML>";
   print "\n--bhy3e23r4t34tnehtpo7678nneu4232y213vdg--\n";
   }
 }

Обработка Форм


Ну вот ,вы уже знаете достаточно,кое в чем уже успели приобрести опыт, пришло время перейти к очень важной теме - обработке форм. При всей простоте (кажушейся) это едва ли не самое главное предназначение всего стандарта CGI . Куда бы вы не зашли на любой уважающий себя сайт,везде вы встретите формы, которые вам предложат заполнить.В этом деле можно положится только на CGI, так как Java и JavaScript ,выполняющиеся на страничке у клиента не имеют доступа к серверу,на котором находится сайт.
Коротко вспомним о том что происходит при рассматриваемом процессе поближе,так сказать на трезвую голову ;). Итак браузер требует у сервера определенный URL (это может быть как простой документ,так и сгенерированый CGI) в этом документе может содержаться форма.Отображая такой документ браузер также выводит элементы формы (кнопки, поля ввода, поля ввода пароля, переключатели, радио-кнопки, списки, текстовые области,скрытые поля). И со всем этим добром пользователь может взаимодействовать.К форме естественно имеет доступ и встроеный язык программирования JavaScript -он может как использовать форму для своих нужд,не передавая CGI,так и помогать пользователю в заполнении формы.
После того,как пользователь заполнил форму он нажимат кнопку Submit которая говорит, что форму надо отправить на сервер. Браузер собирает все имена и значения элементов формы ,кодирует их методом urlencode и в зависимости от указаного в тэге FORM метода вызывает GET или POST с указаным URL,передавая ему данные. На сервере CGI-скрипту это попадает (в зависимости от метода) либо в переменную QUERY_STRING либо на STDIN.Скрипт может проверить данные ,занести их в какую нибудь базу данных,может как yahoo выполнить какой-нибудь поиск, может что-нибудь вычислить......да мало ли что он может,все зависит только от нашей фантазии..... В конце концов скрипт выдает браузеру ответ,который он и отображает.В этом ответе может содержаться все что вашей душе угодно от сообщения об удачном или неправильном запросе до таких ответов,по сравнению с которыми yahoo и altavista подвиснут от зависти, главное чтоб вам и тем кто посещает ваш сайт это нравилось.;)

Ну а теперь немного о синтаксисе элементов форм ,их описании и самое главное особенностях при обработке CGI-скриптом.
Итак немного экскурс в HTML:
FORM
<FORM action="http://......cgi" method="GET"|"POST" enctype="encodingType"
      name="formName" target="windowName" onSubmit="Handler">
</FORM>
Атрибуты:
action
как раз и задает тот URL,который будет и обрабатывать форму, если он опущен,то текущий URL документа(а он-то может быть сгенерирован нашим скриптом).
method
задает метод GET или POST
enctype
обычно не задается,для форм он application/x-www-form-urlencoded -по умолчанию, и поддерживается всеми CGI скриптами.Но если вы уж очень хотите чтобы браузер послал вам данные в другом формате (например text/plain) то можете указать этот тип кодировки,только потом не жалуйтесь,что ваш скрипт не может разделить поля,или вообще начинает глючить когда пользователь ввел какой-то спецсимвол.
name
Задается для JavaScript,чтоб обращатся к форме по имени,а не по номеру. Для CGI не играет ни какой роли,так как внутреннее для браузера.
target
Может Определять в какой фрейм отправить полученую информацию.Имеет значение во фреймосодержащих документах.Прозрачен для CGI обработки данных.
onSubmit
Определяет JavaScript -обработчик активизации формы.Применяется для проверки JavaScript'ом правильности заполнения.Опять таки прозрачен для CGI.
Пример типичной формы:
<FORM action="http://www.uic.nnov.ru/~paaa/cgi-bin/test.cgi" method="POST">
 .........Поля формы.........
</FORM>
Форма может содержать элементы.Элементы имеют имена,которые используются дл кодирования пар имя=значение.Некоторые Элементы не передаются CGI,а используются JavaScript для управления,например кнопки.Некоторые поля передаются только в тех случаях, когда в них что-то выбрано,например списки и переключатели.Остальные поля передаются всегда, даже когда они пустые.
Например:
<FORM action="http://www.doom/cgi-bin/test.cgi">
Your Name:<INPUT name="Name"><BR>
E-Mail:<INPUT name="Email"><BR>
Are you doomer:<INPUT type="checkbox" name="doomer" value="Yes">
<INPUT type="submit" value="Send Form!">
</FORM>
Допустим вы ввели имя lesha и адрес paaa@uic.nnov.ru,при этом выбрали переключатель После нажатия кнопки будет отправлен вот такой запрос:
http://www.doom/cgi-bin/test.cgi?Name=lesha&Email=paaa@uic.nnov.ru&doomer=Yes
Если же вы не выбрали переключатель,то запрос будет таким:
http://www.doom/cgi-bin/test.cgi?Name=lesha&Email=paaa@uic.nnov.ru
,как видите элемент doomer не вошел в строку запроса
Теперь попробуйте оставить поля редактирования пустыми:
http://www.doom/cgi-bin/test.cgi?Name=&Email=
Эти элементы (Name и Email) присутствуют и сообщают что они пустые.

Кнопка(button)
<INPUT type="button" name="buttname" value="Текст На Кнопке" onClick="Handler">
В форме изображается кнопка,при нажатии которой вызывается JavaScript-обработчик заданый атрибутом onClick ,атрибут name служит для JavaScript-именования кнопки а не дл передачи CGI.Так как значение кнопки не передается CGI, value задает Текст,изображаемый на кнопке.

<FORM onSubmit="return false;">
<INPUT type="button" value="Просто Кнопочка"
onClick="alert('Нажали на кнопку!');">
</FORM>
Submit
<INPUT type="submit" name="submitName" value="Отправить Форму" onClick="Handler">
Кнопка,предназначеная для передачи формы.Опять же,сама не передается,а служит только для управления. текст на ней задается атрибутом value.
  <FORM onSubmit="alert('Нечего Посылать!');return false;">
  <INPUT type="Submit" value="Послать!">
  </FORM>
  
Reset
<INPUT type="reset" name="resetName" value="Очистить" onClick="Handler">
Кнопка очистки формы.При ее нажатиивсем измененым элементам возвращается значение по умолчанию.
  <FORM onSubmit="return false;">
  <INPUT name="something"><BR>
  <INPUT type="reset" value="Очистить!">
  </FORM>
  

Поле ввода(text)
<INPUT [type="text"] name="textName" value="textValue" size=число [обработчики]>
Применяется очень часто,поэтому тип "text" служит для INPUT по умолчанию,его не надо каждый раз указывать.Имя поля,задаваемое name является обязательным для CGI (в отличии от JavaScript,где элементы формы можно индексировать по номерам,а имена для удобства и читабельности кода служат).Можно задать значение по умолчанию атрибутом value,которое будет после загрузки докумета.атрибут size позволяет задать размер поля.Также может содержать обработчики onBlur,onChange,onFocus,onSelect.
  <FORM onSubmit="return false;">
  <INPUT name="something" size=30
    value="Введите что-нибудь">
  </FORM>
  
Текстовая Область(textarea)

<TEXTAREA name="textareaName" rows="число" cols="число" wrap="hard"|"soft">
 TextToEdit
</TEXTAREA>
Область многострочного редактирования.Размеры в строках и столбцах задаютс атрибутами rows и cols.Значения атрибута wrap "hard" и "soft" -означают соответственно мягкую или жесткую разбивку на строки (в большинстве случаев ето не существенно). На что следует действительно обратить внимание так это на символ,используемый для указания перехода на новую строку. В Windows это '\r\n' а в Unix '\n',так что если это для вас существенно,то приводите преобразование,например так:
$my_text =~ s/\r\n/\n/g;
  <FORM onSubmit="return false;">
  <TEXTAREA name="MyText" rows=7 cols=30>
  Тут можно что-нибудь написать
  </TEXTAREA>
  </FORM>
  
Поле ввода пароля(password)
<INPUT type="password" name="passName" size=число value="passValue">
Очень похоже на поле ввода,отличается тем что вместо символов в нем отображаютс символы '*'.Служит для ввода пользователем пароля.
  <FORM onSubmit="return false;">
  Пароль:
  <INPUT type="password"
    name="yourpass" size=30>
  </FORM>
  
Пароль:
Скрытое поле(hidden)
<INPUT type="hidden" name="hiddName" value="hidValue">
Поле не отображаемое на экране.Но оно имеет имя и значение и следовательно передается в форму. Служит для того (и очень часто програмисты его применяют) чтоб передавать скрипту какую нибудь информацию.Например,если ваш скрипт обрабатывает несколько форм разных типов,то в скрытом поле каждой формы можно указать с какой формой конкретно вы имеете дело. Так как это ваша внутренняя кухня то нечего пользователю мозолить глаза этой информацией.
  <FORM onSubmit="return false;">
  Этого сдесь вам не видно,поле-скрытое.
  <INPUT type="hidden" name="formNum" value="3">
  </FORM>
  
Этого сдесь вам не видно,поле-скрытое.
Переключатель(checkbox)
<INPUT type="checkbox" name="checkboxname" value="checkboxValue" [checked] onClick="Handler">Text
В отличии от кнопки,атрибут value сдесь не задает на надпись на переключателе,а его значение(внутреннее).Поэтому если надо что-то подписать,пишите рядом в ним. Может быть сразу выбраным если указан атрибут checked .Если value не указано то значение по умолчанию "on" .Передается только в том случае,когда выбран.
  <FORM onSubmit="return false;">
  <INPUT type="checkbox" name="inet" value="Yes"
    checked>Доступ к Интернет
  </FORM>
  
Доступ к Интернет
Радио-кнопка(radio)
<INPUT type="radio" name="radioName" value="radioVal1" [checked] onClick="Handler">Text
В отличие от checkbox может быть несколько радиокнопок с одинаковым параметром name ,но с разными value,из них передается только та,что выбрана.Одна из них может быть изначально выбрана по умолчанию checked.Например:
  <FORM onSubmit="return false;">
  Вы уверены?<BR>
  <INPUT type="radio" name="Radbut" checked>Yes
  <INPUT type="radio" name="Radbut">No
  </FORM>
  
Вы уверены?
Yes No
Список(select)
<SELECT name="SelectName" size=число [multiple] [обработчики] >
<OPTION value="optionValue1" [selected]>Опция 1
<OPTION value="optionValue2" [selected]>Опция 2
<OPTION value="optionValue3" [selected]>Опция 3
.....
<OPTION value="optionValueN" [selected]>Опция N
</SELECT>
Задает список,позволяющий выбрать одну (или несколько) опций из списка. Если атрибут multiple не указан,то создается простой выпадающий список,в котором можно выбрать только одну из опций.Его значение всегда передается,т.к. всегда хоть одно выбрано. Если указан атрибут multiple,то во первых можно указать размер видимой части списка атрибутом size (Если опций больше появится скролинг).Во вторых передаются только выбраные опции ,т.е.Он может передатся несколько раз ?SelectName=opt1&SelectName=opt2&SelectName=opt9 если выбраны скажем несколько опций.А может и не разу,если ничего не выбрано из списка. Можно задавать обработчики onBlur,onChange,onFocus.
  <FORM onSubmit="return false;">
  Ваш цвет:<BR>
  <SELECT name="singleSel">
   <OPTION value="white">Белый
   <OPTION value="black">Черный
   <OPTION value="magenta">Фиолетовый
   <OPTION value="green">Зеленый
   <OPTION value="red">Красный
  </FORM>
  
Ваш цвет:
  <FORM onSubmit="return false;">
  Какие сорта пива вы пили:<BR>
  <SELECT name="miltiSel" multiple size=4>
   <OPTION value="Балтика">Балтика
   <OPTION value="Толстяк">Толстяк
   <OPTION value="Премьер">Премьер
   <OPTION value="Хольстен">Хольстен
   <OPTION value="Бавария">Бавари
   <OPTION value="Coca-Cola ;)">Coca-Cola ;)
  </SELECT>
  </FORM>
  
Какие сорта пива вы пили:
Небольшая Помощь JavaScript
Для CGI-програмиста конечно JavaScript -это иной мир,вы можете спокойно пропустить этот абзац,если вы не знаете JavaScript,так как написаное в нем к CGI не относится, а скорей к самим формам и дизайну сайта.Я скажу пару слов о том как JavaScript может оказать посильную помощь в проверке правильности заполнения форм.Все проверки конечно можно и нужно делать на сервере,но когда имеешь дело с рассеяным пользователем, то для него заполнение простой формы превратится в мучение.Поясню на примере,в форме есть какие-то обязательные поля,например имя.Если пользователь забыл его указать то скрипт скажет ему об этом в сообщении он исправит это, допустим что-нибудь еще не так ввел .... Только на передачу данных по сети может уходить масса времени.А на обработку на локальной машине-доли секуды.
Вот Например как это можно применить JavaScript для предварительного контроля правильности. Допустим простейшая форма содержит имя и возраст.Имя не должно быть пустым, а возраст должен состоять из цифр.
<HTML><HEAD>
<SCRIPT language="JavaScript">
<!--
function IsNumber(data){
 var NumStr="0123456789";
 var ch;var count;
 for(var i=0;i<data.length;i++){
   ch=data.substring(i,i+1);
   if(NumStr.indexOf(ch)!=-1)count++;
   }
 if(counter==data.length)return true;
 else return false;
 }
function IsEmpty(data){
 if(data.length==0)return true;
 else return false;
 }
function IsFormOk(f){
 if(IsEmpty(f.Name.value)){
  alert('Имя не должно быть пустой строкой');
  return false;
  }
 if(!IsNumber(f.Age.value)){
  alert('Возраст должен состоять из цифр');
  return false;
  }
 return true;
 }
//--></SCRIPT></HEAD>
<BODY>
<FORM action="http://www.test.ru/cgi-bin/test.cgi" onSubmit="IsFormOk(this.form)">
Your Name:<INPUT name="Name"><BR>
Your age:<INPUT name="Age"><BR>
<INPUT type="submit" value="Послать Данные">
</FORM>
</BODY></HTML>
Ну вот ,на этом можно закончить это краткое введение в 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-hA-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
Так мы проходим по всем полям,которые нам переданы.Это стандартный подход,он годится в качестве шаблона.У вас может возникнуть вопрос,а что делать если вам переданы данные от списка у которого задана возможность выбора нескольких элементов и данные поступают в таком виде: Sel=opt1&Sel=opt2&Sel=opt9. Тут тоже нет никаких проблем,просто запихиваем эти поступающие значения в массив.
 foreach(@formfields){
   .....
   if(/^Sel=(.*)/){push @Sel,urldecode($1);}
   .....
   }
И потом спокойно оперируем с Полученым Массивом @Sel.
На этом можно так сказать заканчивается шаблонная часть скрипта и начинается содержательная, которая зависит только от вашей фантазии.....
Вы можете сколько угодно анализировать полученые значения,обращатся при этом к различным файлам .Если вы к этому приложите фантазию,то кто знает что получится....
А Пока Ради примера я вам напишу скрипт,который ведет социологическое исследование насчет курения и отношения к нему.Может он слишком массивен для данного пособия, но зато он наглядно показывает как достаточно простыми средствами можно проводить социологические исследования.
<HTML><!-- HTML файл с формой,можете повесить его себе на сайт! ->
<HEAD><TITLE>Социологический опрос насчет курения</TITLE></HEAD>
<BODY>
<CENTER><H1>Социологический опрос насчет курения</H1></CENTER>
<FORM action="cgi-bin/smoketest.cgi">
<TABLE>
<TR><TD>Ваш возраст:</TD><TD><INPUT name="age"></TD></TR>
<TR><TD>Вы курите(Y/N):</TD>
    <TD><INPUT type="radio" name="smoke" value="Yes" checked>Да
        <INPUT type="radio" name="smoke" value="No">Нет</TD></TR>
<TR><TD>Как вы относитесь если рядом кто-то курит?</TD>
    <TD><SELECT name="sm_near">
        <OPTION value="0">Резко негативно
        <OPTION value="1">Негативно
        <OPTION value="2" selected>Мне все равно
        <OPTION value="3">Позитивно
        <OPTION value="4">Резко позитивно
        </SELECT>
    </TD></TR>
<TR><TD>Сколько вы выкуриваете в день?</TD>
    <TD><SELECT name="sm_day">
        <OPTION value="0">Ни сколько
        <OPTION value="1">1 сигарету
        <OPTION value="2">2 сигареты
        <OPTION value="5">около 5
        <OPTION value="0.5pac">полпачки
        <OPTION value="pac">пачку
        <OPTION value="2pac">2 пачки
        <OPTION value="more">больше
        </SELECT>
    </TD></TR>
<TR><TD>Как давно вы начали курить?</TD>
    <TD><SELECT name="sm_stage">
        <OPTION value="noatall">Не начинал
        <OPTION value="onetime">Бросил
        <OPTION value="0.5year">Полгода
        <OPTION value="1year">Год
        <OPTION value="2year">2 Года
        <OPTION value="5year">5 Лет
        <OPTION value="more">Больше
        </SELECT>
    </TD></TR>
<TR><TD>Считаете ли вы это опасным для своего здоровья?</TD>
    <TD><SELECT name="sm_danger">
        <OPTION value="0">Очень Опасно
        <OPTION value="1">Думаю,что да
        <OPTION value="2" selected>Не знаю
        <OPTION value="3">Может самую малость
        <OPTION value="4">Нет,Безопасно.
        </SELECT>
    </TD></TR>
<TR><TD>Хотите ли вы бросить?</TD>
    <TD><SELECT name="sm_nosmoke">
        <OPTION value="0">Уже бросаю
        <OPTION value="1">Думаю бросить
        <OPTION value="2" selected>Иногда
        <OPTION value="3">Очень Редко
        <OPTION value="4">Никогда.
        </SELECT>
    </TD></TR>
<TR><TD><INPUT type="submit" value="Послать Данные"></TD>
    <TD><INPUT type="reset" value="Очистить Форму"></TD></TR>
</TABLE>
</FORM>
</BODY></HTML>
А вот скрипт для его обработки:
#!/usr/bin/perl
#smoketest.cgi
$datafile="smoke.dat";
sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
sub print_err{
 print "Content-Type: text/html\n\n";
 print "<HTML><HEAD><TITLE>Error!!</TITLE></HEAD>";
 print "<BODY><CENTER><H1>@_</H1>";
 print "</BODY></HTML>";
 exit;
 }
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
elsif($ENV{'REQUEST_METHOD'} eq 'POST')
  {sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});}
if($query ne ''){
  @formfields=split(/&/,$query);
  foreach(@formfields){
   if(/^age=(.*)/){$age=urldecode($1);}
   if(/^smoke=(.*)/){$smoke=urldecode($1);}
   if(/^sm_near=(.*)/){$sm_near=urldecode($1);}
   if(/^sm_day=(.*)/){$sm_day=urldecode($1);}
   if(/^sm_stage=(.*)/){$sm_stage=urldecode($1);}
   if(/^sm_danger=(.*)/){$sm_danger=urldecode($1);}
   if(/^sm_nosmoke=(.*)/){$sm_nosmoke=urldecode($1);}
   }
  if((!$age)||($age=~/\D/)){
   print "Content-Type: text/html\n\n";
   print "<HTML><BODY><H2>Возраст введен неправильно,должен состоять из цифр.</H2>";
   print "<FORM><INPUT type=\"button\" value=\"Вернуться назад к Анкете\"";
   print "onClick=\"history.back();\"></FORM>";
   print "</BODY></HTML>";
   }
  $anket_str=join('\t',($age,$smoke,$sm_near,$sm_day,$sm_stage,$sm_danger,$sm_nosmoke));
  open(DATA,">>$datafile") || print_err("Cannot open $datafile $!");
  print DATA "$anket_str\n";
  close(DATA);
  }
open(DATA,"$datafile") || print_err("Cannot open $datafile $!");
@AllData=<DATA>;
close(DATA);
$total=$#AllData;
foreach(@AllData){
 ($age,$smoke,$sm_near,$sm_day,$sm_stage,$sm_danger,$sm_nosmoke)=split(/\t/,$_);
 $smok_total++ if ($smoke eq 'Yes');
 $nosmok_total++ if ($smoke eq 'No');
 if($age<16){$age16_total++;
   if($smoke eq 'Yes'){$age16_sm++;}else{$age16_nosm++;}
   }
 if(($age>16)&&($age<=18)){$age16_18_total++;
   if($smoke eq 'Yes'){$age16_18_sm++;}else{$age16_18_nosm++;}
   }
 if(($age>18)&&($age<=20)){$age18_20_total++;
   if($smoke eq 'Yes'){$age18_20_sm++;}else{$age18_20_nosm++;}
   }
 if($age>20){$age20_total++;
   if($smoke eq 'Yes'){$age20_sm++;}else{$age20_nosm++;}
   }

 if($sm_near eq '0'){$near0++;}
 if($sm_near eq '1'){$near1++;}
 if($sm_near eq '2'){$near2++;}
 if($sm_near eq '3'){$near3++;}
 if($sm_near eq '4'){$near4++;}
 if($sm_day eq '0'){$day0++;}
 if($sm_day eq '1'){$day1++;}
 if($sm_day eq '2'){$day2++;}
 if($sm_day eq '5'){$day5++;}
 if($sm_day eq '0.5pac'){$dayhalfpac++;}
 if($sm_day eq 'pac'){$daypac++;}
 if($sm_day eq '2pac'){$day2pac++;}
 if($sm_day eq 'more'){$daymore++;}
 if($sm_stage eq 'noatall'){$stagenoatall++;}
 if($sm_stage eq 'onetime'){$statgeonetime++;}
 if($sm_stage eq '0.5year'){$stagehalfyear++;}
 if($sm_stage eq '1year'){$stage1year++;}
 if($sm_stage eq '2year'){$stage2year++;}
 if($sm_stage eq '5year'){$stage5year++;}
 if($sm_stage eq 'more'){$stagemore++;}
 if($sm_danger eq '0'){$danger0++;}
 if($sm_danger eq '1'){$danger1++;}
 if($sm_danger eq '2'){$danger2++;}
 if($sm_danger eq '3'){$danger3++;}
 if($sm_danger eq '4'){$danger4++;}
 if($sm_nosmoke eq '0'){$stopsmoke0++;}
 if($sm_nosmoke eq '1'){$stopsmoke1++;}
 if($sm_nosmoke eq '2'){$stopsmoke2++;}
 if($sm_nosmoke eq '3'){$stopsmoke3++;}
 if($sm_nosmoke eq '4'){$stopsmoke4++;}
 }
#########
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>Результаты обработки данных</TITLE></HEAD>";
print "<BODY bgcolor=\"yellow\">";
unless($total){print "<H1>Еще нет данных</H1></BODY></HTML>";exit;}
print "<CENTER><H1>Результаты обработки данных</H1></CENTER>";
print "<BR>\n";
print "Обработано анкет: $total<BR>\n";
print "Общие данные Всего:<BR>\n";
print "Курящие:$smok_total (".(($smok_total/$total)*100) ."%)<BR>\n";
print "Некурящие:$nosmok_total (".(($nosmok_total/$total)*100)."%)<BR>\n";
print "<TABLE>\n";
print "<TR><TD colspan=4>Возрастные группы:(<16,16..18,18..20,>20)</TD></TR>\n";
print "<TR><TD>Возраст</TD><TD>Курящие</TD><TD>Некурящие</TD><TD>Всего</TD></TR>\n";
print "<TR><TD>&lt;16:</TD><TD>$age16_sm</TD>";
print "<TD>$age16_nosm</TD><TD>$age16_total</TD></TR>\n";
print "<TR><TD>16..18:</TD><TD>$age16_18_sm</TD>";
print "<TD>$age16_18_nosm</TD><TD>$age16_18_total</TD></TR>\n";
print "<TR><TD>18..20:</TD><TD>$age18_20_sm</TD>";
print "<TD>$age18_20_nosm</TD><TD>$age18_20_total</TD></TR>\n";
print "<TR><TD>&gt;20:</TD><TD>$age20_sm</TD>";
print "<TD>$age20_nosm</TD><TD>$age20_total</TD></TR>";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Отношение когда кто-то курит рядом:(%)</TD></TR>\n";
print "<TR><TD>Резко негативно</TD><TD>".(($near0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Негативно      </TD><TD>".(($near1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Мне все равно  </TD><TD>".(($near2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Позитивно      </TD><TD>".(($near3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Резко позитивно</TD><TD>".(($near4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>В среднем выкуривают:(%)</TD></TR>\n";
print "<TR><TD>не курят:  </TD><TD>".(($day0/$total)*100)."</TD></TR>\n";
print "<TR><TD>1 сигарету:</TD><TD>".(($day1/$total)*100)."</TD></TR>\n";
print "<TR><TD>2 сигареты:</TD><TD>".(($day2/$total)*100)."</TD></TR>\n";
print "<TR><TD>5 сигарет: </TD><TD>".(($day5/$total)*100)."</TD></TR>\n";
print "<TR><TD>полпачки:  </TD><TD>".(($dayhalfpac/$total)*100)."</TD></TR>\n";
print "<TR><TD>пачку:     </TD><TD>".(($daypac/$total)*100)."</TD></TR>\n";
print "<TR><TD>2 пачки:   </TD><TD>".(($day2pac/$total)*100)."</TD></TR>\n";
print "<TR><TD>больше:    </TD><TD>".(($daymore/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Стаж курения:(%)</TD></TR>\n";
print "<TR><TD>Не начинал</TD><TD>".(($stagenoatall /$total)*100)."</TD></TR>\n";
print "<TR><TD>Бросил    </TD><TD>".(($statgeonetime/$total)*100)."</TD></TR>\n";
print "<TR><TD>Полгода   </TD><TD>".(($stagehalfyear/$total)*100)."</TD></TR>\n";
print "<TR><TD>Год       </TD><TD>".(($stage1year   /$total)*100)."</TD></TR>\n";
print "<TR><TD>2 Года    </TD><TD>".(($stage2year   /$total)*100)."</TD></TR>\n";
print "<TR><TD>5 Лет     </TD><TD>".(($stage5year   /$total)*100)."</TD></TR>\n";
print "<TR><TD>Больше    </TD><TD>".(($stagemore    /$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Курение опасно:(%)</TD></TR>\n";
print "<TR><TD>Очень Опасно       </TD><TD>".(($danger0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Думаю,что да       </TD><TD>".(($danger1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Не знаю            </TD><TD>".(($danger2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Может самую малость</TD><TD>".(($danger3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Нет,Безопасно.     </TD><TD>".(($danger4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Хотели ли вы бросить:(%)</TD></TR>\n";
print "<TR><TD>Уже бросаю   </TD><TD>".(($stopsmoke0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Думаю бросить</TD><TD>".(($stopsmoke1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Иногда       </TD><TD>".(($stopsmoke2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Очень Редко  </TD><TD>".(($stopsmoke3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Никогда.     </TD><TD>".(($stopsmoke4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "</BODY></HTML>";



Изображения ismap


После такой серьезной темы как обработка форм,я перейду к чему-нибудь веселенькому. Я познакомлю вас с изображениями ismap потому что это просто есть такой способ. Он поддерживается браузерами и естественно имеет право на жизнь.Хотя с приходом новых веяний в HTML (особенно Java-аплетов) он стал настоящей редкостью.И хот можно в 80% случаев найти ему более быструю замену,все-же вы можете в некоторых случаях найти именно ismap предпочтительней всего.
Синтаксис очень простой,почти не отличается от того,если бы вы решили оформить рисунок для якорь гиперссылки:

<A href="cgi-bin/somescript.cgi"><IMG src="somepic.gif" border=0 ismap></A>
Заметьте что все отличие заключается в том,что в тэге IMG добавлен атрибут ismap. Он говорит браузеру,что когда пользователь щелкнет на картинке то нужно перейти не просто к URL указаному в <A href="URL"> а что нужно к этому URL добавить координаты той точки по которой пользователь щелкнул мышью .
В нашем примере если пользователь щелкнул по точке x=10 ,y=15 то браузер перейдет на URL:
http://www.somehost.ru/cgi-bin/somescript.cgi?10,15
Т.е. координаты идут на скрипте в переменную QUERY_STRING ,их оттуда извлечь? Нет ничего проще:
($x,$y)=split(/,/,$ENV{'QUERY_STRING'});
Вот скрипт,который просто показывает координаты точки щелчка:
#!/usr/bin/perl
#ismap_xy.cgi
($x,$y)=split(/,/,$ENV{'QUERY_STRING'});
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>Ismap X Y</TITLE></HEAD>";
print "<BODY><H1>Вы щелкнули в точке: x=$x ,y=$y</H1></BODY></HTML>";
А что с ними делать дальше это уже чисто зависит только от вашей фантазии.Дайте ей ход и все у вас получится!.Очень часто ismap применяют для графического оглавления сайта. Когда щелкают на разные части рисунка,то переходят к разным страничкам сайта. Это легко реализуется,если скрипт выдаст нужный URL в Location: (Вспомните заголовок ответа CGI).
Вот пример и покажет это.Заготовьте файл urlmap.txt в котором будет информаци из строк в таком формате:
minx miny maxx maxy URL
где minx miny maxx maxy задают участок рисунка,а следующее за ними поле задает URL, которому этот участок соответствует.Пример:
1 1 20 50 http://www.uic.nnov.ru/~paaa/index_p.html
1 50 20 100 http://www.uic.nnov.ru/~paaa/projects.html
20 1 100 100 http://www.uic.nnov.ru/~paaa/cgi-bin/guestbook.cgi
Где нибудь на своей страничке воткните что-то вроде:
<A href="cgi-bin/testismap.cgi"><IMG src="gifs/doom2.jpg" border=0 ismap></A>
А сам скрипт testismap.cgi будет иметь вот такой простенький вид:
#!/usr/bin/perl
#testismap.cgi
$default_url="http://www.uic.nnov.ru/~paaa/";
		#URL по умолчанию,переходим к нему когда щелкнули
                #в участок,которому не сопоставлен URL
$url_map_file="urlmap.txt"; #файл с информацией об URL

($x,$y)=split(/,/,$ENV{'QUERY_STRING'});
open(F,"$url_map_file")|| print "Location: $default_url\n\n";
$url=$default_url;
foreach(<F>){
 chomp;
 ($minx,$miny,$maxx,$maxy,$URL)=split(/\s+/);
 if(($x>=$minx)&&($x<$maxx)&&
    ($y>=$miny)&&($x<$maxy)){$url=$URL;}
 }
close(F);
print "Location: $url\n\n";

Анимация


Когда говорят о каком-то популярном сайте,то частенько к преимуществам относят и анимацию. Действительно,когда изображение изменяется (и особенно к месту ;)),то это смотритс и пользователю нравится.
Говоря об анимации нужно сразу отметить что нет лучшего способа. Анимацию можно сделать ДЕСЯТКАМИ Способов,каждый хорош в своей области применения. Я перечислю только некоторые из них,которые чаще всего применяются:
Самый простой,но наименее функциональный способ это GIF с анимацией.
Потом можно воткнуть анимационный файл MPEG или AVI они более отражают суть анимации, но имеют недостаток,что для проигрывания их на некоторых браузерах нужны специальные подключаемые модули.К тому же они не интерактивны.
Можно реализовать анимацию в рамках Java-апплета,когда апплет находясь на страничке сам перерисовывается со временем.
Таким же интерактивным средством служит обращение к массиву document.images[] из JavaScript.Достоинство-помимо интерактивности,полная интегрированость с HTML -станичкой.Но может как и предыдущий использоваться только с относительно новыми браузерами,которые поддерживают Java и JavaScript.

В общем в каждом случае выбор остается за вами.Вам решать насколько тот или иной способ хорош в вашей ситуации.Я же познакомлю вас с еще одним.
Вы даже уже были знакомы с этим способом,когда я вам рассказывал о nph- скриптах Теперь когда вы уже так много знаете,можно модифицировать тот пример, добавив в него вызов картинки по случайному принципу:
#!/usr/bin/perl
#nph-animate2.cgi
$delay=3;
@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";
srand;
print "--myboundary\n";
while(1){
  $file=$files[int(rand($#files))];   #random file
  print "Content-Type: image/gif\n\n";
  open(PIC,"$file");
  print <PIC>;
  close(PIC);
  print "\n--myboundary\n";
  sleep($delay);
  }
Конечно одно из самых примитивных применений такой системы.Более мощным примером могло бы послужить отслеживание на сервере какого-нибудь периодически изменяющегося файла и пересылка пользователю обновленной версии.
Такая Система применяется например в Чате,при появлении новых сообщений. Чатовая система достаточно сложна для этого пособия и я не стал сюда ее включать.Однако,если вам очень интересно,то я с удовольствием пришлю ее вам.

Несколько советов по отладке


CGI-программы -не самые простые в отладке,по сложности отладки они способны сравнится лишь с отладкой драйверов. Вся сложность заключается в том,что скрипт выполняется не как обычная программа. Он выполняется в специальной среде сервера,которая создается при клиентском запросе, к тому же он исполняется не из под вашего аккаунта,а на непривилегированом уровне.
Если скрипт не исполняется потому,что вы допустили синтаксические ошибки,то самих этих ошибок вы не увидите,на экране будет только 'Internal Server Error' из-за чего она произошла вы можете только гадать. Также если вы забыли задать к какому-то файлу нужные права доступа ,то тоже будет трудно выяснить что же произошло и в чем причина ошибки (если конечно к этому вы не готовы).
Ну вот ,хватит вас пугать,тем более что нас не запугаешь ;) !
Приступим к отладке.Я вам опишу достаточно примитивные меры,которыми я сам пользуюсь.
Начнем с того что у нас есть скрипт test.cgi мы уже сделали его исполняемым chmod +x test.cgi Простейший способ проверить его на ошибки это команда perl -c test.cgi Ключ -c говорит Perl что надо только проверить синтаксис.Все сообщения об ошибках вы можете видеть и подправить.Более тяжелый случай состоит в том когда Perl встроен в Web -Сервер, причем версии разные.Как у до недавнего времени было на uic;(( ! Тот Perl с которым работаем в командной строке 4й версии ,а на сервере стоит 5й версии.Если ваша CGI-программа использует при этом какие-нибудь преимущества 5-й версии (например обьектно-ориентированые модули),то вы думаете отладить ее низ -ошибаетесь!.Только приготовтесь к тому, что я сейчас скажу,вы сядте,а то упадете ;)) :
Закоментируйте всю вашу программу ,т.е. перед каждой строчкой поставьте символ '#'. После чего,добавьте вот такие строчки: print "Content-Type: text/html\n\n"; print "<HTML>Test</HTML>"; exit; ,Должно получится так:
#!/usr/bin/perl
#test.cgi
print "Content-Type: text/html\n\n";
print "<HTML>Test</HTML>";
exit; #Программа как вы понимаете выполняется только до етого места
#
#if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'}}
#else{sysread STDIN,$query,$ENV{'CONTENT_LENGTH'};}
#if($query eq ''){
# @formfields=split /&/,$query;
# .......
# ........
А теперь запускайте скрипт.Естественно он выдаст Одно только слово 'Test'. Разкоментируйте несколько строчек.Еще раз запустите скрипт.Он опять выдаст 'Test'. Значит синтаксически эти только что разкоментированые строчки были правильные. И так далее....
Если очередной раз после раскоментирования вы запустили скрипт и получили 'Internal Server Error' - значит в этих строках содержалась какая-та синтаксическая ошибка. Это способ отловки синтаксических ошибок трудоемок,но к нему придется прибегнуть если ваш скрипт писан под ту версию Perl,что на сервере,а не под ту что у вас.
Узнать версию Perl можно perl -v
Ну вот мы отловили в нашем скрипте все синтаксические ошибки,он заработал, но это не значит,что он работает правильно.
Что еще можно посоветовать при отладке CGI-скриптов от ошибок возникающих во время выполнения программы. Допустим какой-то файл не открылся.Конечно показывать перепуганому пользователю эти технические подробности никчему,поэтому заведите себе специальный файл debug.txt и пусть ваши скрипты пишут в этот файл причины своих ошибок и сбоев, да и вообще о всех непредвиденых событиях.
Это можно реализовать так:
sub debug_err{
 open(DEBUGFILE,">>debug.txt");
 print DEBUGFILE $ENV{'SCRIPT_NAME'}.' '.scalar localtime.' '.@_."\n";
 close(DEBUGFILE);
 }
Примеры использования (Напомню,что встроеная переменная Perl $! содержит сообщение о причине последней ошибки,поэтому включайте ее всегда в свои сообщения):
open(F,"+<$myfile") || debug_err("Cannot open $myfile $!");
seek(F,0,0) || debug_err("Cannot seek $myfile $!");
connect(SOCKET,$paddr)|| debug_err("Cannot connect to $remote $!");
......
Потом можно периодически заглядывать в этот файл debug.txt и смотреть,какие ошибки встречались при работе ваших скриптов.Таким образом ваши скрипты сами помогать будут в своей отладке ;).

Также очень может оказать помощь (может и не оказать) просмотр http'шных логов. Все обращения к URL на сервере и все возникающие при этом ошибки идут в логи сервера httpd. Сразу хочу предупредить - размеры этих логов даже на средних размеров сервере достигает десятков мегабайт. Поэтому даже не пытайтесь их вот так просто посмотреть.- Лучше если вы уж за данное дело взялись - воспользуйтесь такими утилитами как grep,head,tail,more,less. .

Кстати я хочу сказать о причине еще одной (совсем не очевидной) ошибки.Если вы набрали скрипт у себя дома на компутере,то полученый скрипт состоит из текста в DOS'ом формате, а не в Unix'ом так что имейте это ввиду. Запускать вам его придется в системе Unix , так что следует перевести програмный текст в нужный формат.
Дело в том что в системах DOS и Windows (это уж очередной раз скажите все что вы думаете о Билле Гейтсе и Ко) для разделения строк используетс не один "символ новой строки" ("\n"),а пара символов "возврат каретки" ("\r") и "символ новой строки" ("\n"). Для простых HTML-файлов это не критично - браузеры игнорируют такие символы при выводе. Но скрипт является ПРОГРАММОЙ. А в программе никаких символов возврата каретки быть не должно!! Особенно в первой строке. Потому что когда операционная система запускает скрипт, она определяет какое приложение запустить для обработки данного скрипта именно по первой строке. В первой строке как вы знаете содержится #!/usr/bin/perl или #!/usr/local/bin/perl .Поэтому при запуске скрипта mysrcipt (это кстати вы можете сами увидеть коммандами top и ps) Система запускает комманду /usr/bin/perl mysript .А теперь представьте что будет если не убрать символ возврата каретки из Windows-файла. Получиться #!/usr/bin/perl<возврат каретки> и естественно что такого приложения нет, и следовательно попытка выполнить коммаду /usr/bin/perl<возврат каретки> mysript ни к чему не приведет!!!
Как с таким злом бороться я раскажу в моей следующей главе.

Trics and traps


Я так решил назвать эту часть,потому что это название больше всего соответствует.
Я вам в этой части расскажу о всяких тонких и неочевидных местах и о том как с этим боротся.
Продолжая тему отладки, я обнаружил что многие столкнулись с одной неочевидной проблеммой когда скрипты создают дома, помимо того, что надо перевести раскладку Windows (или DOS) в koi8-r скрипты все равно отказывались работать. Признаюсь ,хоть и разобрался в чем дело я быстро, причина весьма неочевидна: Дело в том,что текстовый файл Windows содержит перед символом перехода на новую строку еще символ возврата каретки. А вот из-за этого скрипт и отказывался выполнятся, что содержал этот символ!
Методом решения (этот процесс как и все я максимально автоматизирую) стал коротенький скрипт delcr .Просто "натравливайте" его на ваши скрипты:delcr *.cgi
#!/usr/bin/perl
#delcr
unless(@ARGV){die "Usage: delcr file ....\n";}
foreach $file(@ARGV){
 if(! -r $file || ! -w $file){print "$file: access denied\n";}
 else{
  open(F,"+<$file")|| die "Cannot open $file $!\n";
  binmode(F) || die "Cannot binmode $file $!\n";
  @D=<F>;
  seek(F,0,0);
  foreach(@D){
    s/\r//g;
    print F;
    }
  truncate(F,tell(F));
  close(F);
  }
 }
Саму же взаимную перекодировку Dos<->koi-8<->Win поможет выполнить вот такая прога. Написал я ее очень давно, даже раньше чем занялся CGI программированием и с тех давних пор она меня все время выручала, став моим по-настоящему незаменимым и верным другом. Итак, знакомьтесь: txtconv -Утилита перевода раскладок символов.
#include<stdio.h>
#include<string.h>
/*****************/
char tbldw[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
 0x5f,0x5f,0x5f,0xa6,0xa6,0xa6,0xa6,0x2b,0x2b,0xa6,0xa6,0x2b,0x2b,0x2b,0x2b,0x2b,
 0x2b,0x2d,0x2d,0x2b,0x2d,0x2b,0xa6,0xa6,0x2b,0x2b,0x2d,0x2d,0xa6,0x2d,0x2b,0x2d,
 0x2d,0x2d,0x2d,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x5f,0x5f,0x5f,0x5f,0x5f,
 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff,
 0xa8,0xb8,0xaa,0xba,0xaf,0xbf,0xa1,0xa2,0xb0,0x95,0xb7,0x5f,0xb9,0xa4,0x5f,0x5f
};
char tblwd[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,
 0x5f,0x5f,0x5f,0x5f,0x5f,0xf9,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,
 0x5f,0xf6,0xf7,0x5f,0xfd,0x5f,0x7c,0x15,0xf0,0x63,0xf2,0x11,0x2d,0x2d,0x72,0xf4,
 0xf8,0x5f,0x49,0x69,0x5f,0x5f,0x14,0xfa,0xf1,0xfc,0xf3,0x10,0x5f,0x5f,0x5f,0xf5,
 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef
};
char tbl_asc[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f
};
char tbldu[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0xE1,0xE2,0xF7,0xE7,0xE4,0xE5,0xF6,0xFA,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,
 0xF2,0xF3,0xF4,0xF5,0xE6,0xE8,0xE3,0xFE,0xFB,0xFD,0xFF,0xF9,0xF8,0xFC,0xE0,0xF1,
 0xC1,0xC2,0xD7,0xC7,0xC4,0xC5,0xD6,0xDA,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,
 0x90,0x91,0x92,0x81,0x87,0xB2,0xB4,0xA7,0xA6,0xB5,0xA1,0xA8,0xAE,0xAD,0xAC,0x83,
 0x84,0x89,0x88,0x86,0x80,0x8A,0xAF,0xB0,0xAB,0xA5,0xBB,0xB8,0xB1,0xA0,0xBE,0xB9,
 0xBA,0xB6,0xB7,0xAA,0xA9,0xA2,0xA4,0xBD,0xBC,0x85,0x82,0x8D,0x8C,0x8E,0x8F,0x8B,
 0xD2,0xD3,0xD4,0xD5,0xC6,0xC8,0xC3,0xDE,0xDB,0xDD,0xDF,0xD9,0xD8,0xDC,0xC0,0xD1,
 0xB3,0xA3,0x99,0x98,0x93,0x9B,0x9F,0x97,0x9C,0x95,0x9E,0x96,0xBF,0x9D,0x94,0x9A
};
char tblud[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0xC4,0xB3,0xDA,0xBF,0xC0,0xD9,0xC3,0xB4,0xC2,0xC1,0xC5,0xDF,0xDC,0xDB,0xDD,0xDE,
 0xB0,0xB1,0xB2,0xF4,0xFE,0xF9,0xFB,0xF7,0xF3,0xF2,0xFF,0xF5,0xF8,0xFD,0xFA,0xF6,
 0xCD,0xBA,0xD5,0xF1,0xD6,0xC9,0xB8,0xB7,0xBB,0xD4,0xD3,0xC8,0xBE,0xBD,0xBC,0xC6,
 0xC7,0xCC,0xB5,0xF0,0xB6,0xB9,0xD1,0xD2,0xCB,0xCF,0xD0,0xCA,0xD8,0xD7,0xCE,0xFC,
 0xEE,0xA0,0xA1,0xE6,0xA4,0xA5,0xE4,0xA3,0xE5,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,
 0xAF,0xEF,0xE0,0xE1,0xE2,0xE3,0xA6,0xA2,0xEC,0xEB,0xA7,0xE8,0xED,0xE9,0xE7,0xEA,
 0x9E,0x80,0x81,0x96,0x84,0x85,0x94,0x83,0x95,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,
 0x8F,0x9F,0x90,0x91,0x92,0x93,0x86,0x82,0x9C,0x9B,0x87,0x98,0x9D,0x99,0x97,0x9A
};
char tbluw[256]={
 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,

 0x2D,0xA6,0x2B,0x2B,0x2B,0x2B,0x2B,0xA6,0x2D,0x2D,0x2B,0x5F,0x5F,0x5F,0x5F,0x5F,
 0x5F,0x5F,0x5F,0xAF,0x5F,0x95,0x5F,0xA2,0xBA,0xAA,0x5F,0xBF,0xB0,0xA4,0xB7,0xA1,
 0x2D,0xA6,0x2B,0xB8,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0xA6,
 0xA6,0xA6,0xA6,0xA8,0xA6,0xA6,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2B,0x2B,0x2B,0xB9,
 0xFE,0xE0,0xE1,0xF6,0xE4,0xE5,0xF4,0xE3,0xF5,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,
 0xEF,0xFF,0xF0,0xF1,0xF2,0xF3,0xE6,0xE2,0xFC,0xFB,0xE7,0xF8,0xFD,0xF9,0xF7,0xFA,
 0xDE,0xC0,0xC1,0xD6,0xC4,0xC5,0xD4,0xC3,0xD5,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,
 0xCF,0xDF,0xD0,0xD1,0xD2,0xD3,0xC6,0xC2,0xDC,0xDB,0xC7,0xD8,0xDD,0xD9,0xD7,0xDA
};
char tblwu[256]={
 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,

 0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,
 0x5F,0x5F,0x5F,0x5F,0x5F,0x95,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,
 0x5F,0x9F,0x97,0x5F,0x9D,0x5F,0x7C,0x15,0xB3,0x63,0x99,0x11,0x2D,0x2D,0x72,0x93,
 0x9C,0x5F,0x49,0x69,0x5F,0x5F,0x14,0x9E,0xA3,0xBF,0x98,0x10,0x5F,0x5F,0x5F,0x9B,
 0xE1,0xE2,0xF7,0xE7,0xE4,0xE5,0xF6,0xFA,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,
 0xF2,0xF3,0xF4,0xF5,0xE6,0xE8,0xE3,0xFE,0xFB,0xFD,0xFF,0xF9,0xF8,0xFC,0xE0,0xF1,
 0xC1,0xC2,0xD7,0xC7,0xC4,0xC5,0xD6,0xDA,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,
 0xD2,0xD3,0xD4,0xD5,0xC6,0xC8,0xC3,0xDE,0xDB,0xDD,0xDF,0xD9,0xD8,0xDC,0xC0,0xD1
};
/*****************/
int convert(unsigned char *buff,unsigned char *Tbl,int count)
 {
 int i;
 for(i=0;i<count;i++)buff[i]=Tbl[(unsigned)buff[i]];
 return 0;
 }
/*****************/
char szHelp[]=
"Text file converter (c)lesha 1998\n"
"Usage:txtconv <options> <srcfile> <dstfile>\n"
"  options: -ud koi8->dos\n"
"           -du dos->koi8\n"
"           -uw koi8->win\n"
"           -wu win->koi8\n"
"           -dw dos->win\n"
"           -wd win->dos\n"
"           -? -This help\n";
int main(int argc,char *argv[])
 {
 FILE *f1;
 FILE *f2;
 char tmpbuff[1024];
 char *xtbl=NULL;
 int nr;
 if((argc>1)&&(strcmp(argv[1],"-?")==0)){printf(szHelp);return 0;}
 if(argc<4){printf(szHelp);return 0;}
 if     (strcmp(argv[1],"-ud")==0)xtbl=tblud;
 else if(strcmp(argv[1],"-du")==0)xtbl=tbldu;
 else if(strcmp(argv[1],"-uw")==0)xtbl=tbluw;
 else if(strcmp(argv[1],"-wu")==0)xtbl=tblwu;
 else if(strcmp(argv[1],"-dw")==0)xtbl=tbldw;
 else if(strcmp(argv[1],"-wd")==0)xtbl=tblwd;
 if(xtbl==NULL){printf("unknown option:%s",argv[1]);return 1;}
 if((f1=fopen(argv[2],"rb"))==NULL){perror(argv[2]);return 1;}
 if((f2=fopen(argv[3],"wb"))==NULL){fclose(f1);perror(argv[3]);return 1;}
 while((nr=fread(tmpbuff,1,sizeof(tmpbuff),f1))>0)
   {
   convert(tmpbuff,xtbl,nr);
   fwrite(tmpbuff,1,nr,f2);
   }
 fclose(f1);
 fclose(f2);
 return 0;
 }
Это еще одна утилита,соторая поможет вам в тяжелой реальности Интернета ;). Скомпилить ее можно под все три системы и используется она после этого очень легко, особенно когда всегда под рукой.
В общем что я могу сказать ,у вас на пути будет немало трудностей, но их можно всех преодолеть используя нехитрые приспособления.
Желаю вам удачи. А пока посмотрите на некоторые примеры приложений, возможно они вам будут полезны.

Примеры приложений:


Кто посещает мою страничку?


Вам иногда хотелось наверное узнать,кто же смотрит на вашу страничку,откуда и когда ваша страничка посещалась.
Бывают такие вопросы? Кто-то считает,что ответить на них нельзя. Но вы не верьте этому расхожему мнению.
Один раз с подобными вопросами ко мне подошел мой одногрупник, Диман. У него неплохой сайт. и туда к нему всегда валит целая куча народа. Вот как раз разговор и зашел об этой куче народа. Результатом моего непродолжительного труда стал небольшой скрипт.В страничку он втакаетс через тэг <IMG src="cgi-bin/get_ip.cgi"> он покажет вам изображение что не будет бросаться в глаза. Зато при своей работе он все запишет в файл ipdata.txt : В нем будет время и IP-адрес того,кто смотрел на вашу страничку!
#!/usr/bin/perl
#get_ip.cgi
$gif="../gifs/player.gif";
$data="ipdata.txt";
print "Content-Type: image/gif\n\n";
open(G,$gif);
print <G>;
close(G);
open(D,">>$data");
print D scalar localtime,' '.$ENV{'REMOTE_ADDR'}."\n";
close(D);

Гостевая книга


А вот еще пример того,как можно с умом использовать нехитрые знания. Гостевая книга ,в которую каждый может записать свое вам пожелание.
К ней прилагаются .gif -файлы,позволяющие указать свое настроение:
Запись происходит в базу данных guestbook.dat и при каждой новой записи в гостевую книгу скрипт извещает по почте хозяина гостевой книги, а тому кто в нее вошел по почте посылается сообщение об этом.
#!/usr/bin/perl
#guestbook.cgi
$myemail="paaa\@uic.nnov.ru";
$myname="lesha";
$mail="mail";
($sd,$sn)=($ENV{'SCRIPT_FILENAME'}=~/(.*)\/([^\/]*)/);
$datafile=$sd."\/guestbook.dat";
@Mailgifs=qw(../gifs/mood0.gif ../gifs/mood1.gif ../gifs/mood2.gif);
$Facetxt{$Mailgifs[0]}= ":)";
$Facetxt{$Mailgifs[1]}= ":|";
$Facetxt{$Mailgifs[2]}= ":(";

sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/eg;
 return $val;
 }
sub strhtm{
 local($val)=@_;
 $val=~s/&/&/g;
 $val=~s/</</g;
 $val=~s/>/>/g;
 $val=~s/(http:\/\/\S+)/<A href="$1">$1<\/A>/g;
 return $val;
 }
$cont_len=$ENV{'CONTENT_LENGTH'};
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
else {sysread(STDIN,$query,$cont_len);}
if($query eq ''){
  print "Content-type: text/html\n\n";
  print <<HTML_generating;
<HTML><HEAD><TITLE>Wellcome to my guestbook</TITLE></HEAD>
<BODY bgcolor="cyan">
<CENTER><H1>Wellcome to my guestbook</H1></CENTER>
<HR><FORM action="guestbook.cgi" METHOD="POST">
<TABLE border=0>
<TR><TD>Name:</TD><TD colspan=3><INPUT NAME="Name"></TD></TR>
<TR><TD>E-mail:</TD><TD colspan=3><INPUT NAME="Email"></TD></TR>
<TR><TD>URL:</TD><TD colspan=3><INPUT NAME="URL"></TD></TR>
<TR><TD>Message:</TD><TD colspan=3>
<TEXTAREA NAME="Message" rows=6 cols=64></TEXTAREA></TD></TR>
<TR><TD>Mood:</TD><TD><IMG src="$Mailgifs[0]"></TD><TD>
<IMG src="$Mailgifs[1]"></TD><TD><IMG src="$Mailgifs[2]"></TD></TR>
<TR><TD> </TD><TD><INPUT TYPE="radio" NAME="Mood" VALUE="$Mailgifs[0]"></TD>
                   <TD><INPUT TYPE="radio" NAME="Mood" VALUE="$Mailgifs[1]"></TD>
                   <TD><INPUT TYPE="radio" NAME="Mood" VALUE="$Mailgifs[2]"></TD></TR>
<TR><TD colspan=2><INPUT TYPE="submit" VALUE="Send"></TD>
    <TD colspan=2><INPUT TYPE="reset" VALUE="Clean"></TD></TR>
</TABLE></FORM>
<HR><BR>
HTML_generating
  open(DATAFILE,"$datafile")|| die "Cannot open $datafile $!\n";
  @GUESTDATA=<DATAFILE>;
  print @GUESTDATA;
  close(DATAFILE);
  print "</BODY></HTML>";
  }
else{
  foreach(@fields=split(/&/,$query)){
    if(/^Name=(.*)/){$Name=&urldecode($1);}
    if(/^Email=(.*)/){$Email=&urldecode($1);}
    if(/^URL=(.*)/){$URL=&urldecode($1);}
    if(/^Message=(.*)/){$Message=&urldecode($1);}
    if(/^Mood=(.*)/){$Mood=&urldecode($1);}
    }
  $MESSAGE=&strhtm($Message);
  if(-e $datafile){unless (-r $datafile && -w $datafile){die "Cannot access $datafile\n";}}
  $Newmsg="<IMG src=\"$Mood\"><BR><A href =\"mailto:$Email\">$Name</A>".
          "(<A href=\"$URL\">$URL</A>):<BR>\n$MESSAGE<HR>\n";
  open(DATAFILE,"+<$datafile") || die "Cannot open $datafile $!\n";
  @GUESTDATA=<DATAFILE>;
  @GUESTDATA=($Newmsg,@GUESTDATA);
  seek(DATAFILE,0,0);
  print DATAFILE @GUESTDATA;
  close(DATAFILE);
  print "Content-type: text/html\n\n";
  print "<HTML><HEAD><TITLE>Congratulations</TITLE></HEAD>\n";
  print "<BODY bgcolor=\"cyan\">\n<CENTER>";
  print "<H1>Congratulations:you have successfully entered to $myname\'s";
  print "guestbook.Thank you!</H1></CENTER><HR>$Newmsg</BODY></HTML>";
  open(MAIL,"|$mail $Email");
  print MAIL "Guestbook\n";
  print MAIL "You have entered to $myname\'s guestbook\n";
  print MAIL "Thank you.\n\t\t\t\t$myname";
  close(MAIL);
  format NOTIFYMAIL=
Guestbook
========================== Guestbook Entry =======================
| Time:                    |Name:                                |
| @<<<<<<<<<<<<<<<<<<<<<<<<|@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
scalar localtime,$Name
+--------------------------+-------------------------------------+
| Email:                   |URL:                                 |
| @<<<<<<<<<<<<<<<<<<<<<<<<|@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Email,$URL
+--------------------------+-------------------------------------+
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Message
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Message
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Message
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    @<<<<< |
$Message,$Facetxt{$Mood}
==================================================================
.
  open(NOTIFYMAIL,"|$mail $myemail");
  write NOTIFYMAIL;
  close(NOTIFYMAIL);
  }


Счетчик посещений


Наверное тоже одним из часто встречающихся приложений CGI являются счетчики посещений. Они стоят практически на каждой страничке, возможно даже и у вас. Но иногда вас не устраивает тот факт, что счетчик лежит где-то в другом месте.Из-за этого скажем невозможно начать счет с произвольного числа.Или еще некоторые счетчики по разному фильтруют 'Reload'. Да и мало ли? Ну а иногда вам хочется просто сделать другой дизайн цифр. То если вы CGI-програмист то возможно имеет смысл написать свой счетчик. И делать с ним что захочется. Вот я так-же написал.....
Скрипт данного счетчика обслужевает несколько счетчиков ,им вы присваиваете идентификаторы. Поэтому вы спокойно можете втыкать независимые счетчики в разные страницы сайта и даже давать это делать друзьям. В общем он прост в использовании:<IMG src="cgi-bin/counter.cgi?id=name">, Где name -любое уникальное имя идентифицирующее счетчик.Вытакже можете задать необязательный параметр dig который задает количество цифр в счетчике ,Например:
<IMG src="cgi-bin/counter.cgi?id=doom2&dig=9">.
Получится примерно вот так:
.gif'ы в счетчике с прозрачными областями.Что дает дополнительную гибкость к примеру для улучшения внешнего вида с помощью другого фона его иногда имеет смысл запихнуть в "таблицу":
<TABLE><TR><TD bgcolor="white"><IMG src="counter.gif"></TD></TR></TABLE>

Свои данные он пишет примерно в такой файл counter.dat:
doom2
4 127.0.0.1 906992351
quake2
1 127.0.0.1 906992700
quake
3 127.0.0.1 906992668
doom
1 127.0.0.1 906991960
Вы спросите,зачем столько информации? Чтобы отфильтровывать нажатия Reload. Если с одного IP-адреса между заходами промежуток меньше чем 30 секунд,то счетчик не инкрементируется (Так например поступает счетчик в Rambler'е).
Теперь об исходнике. Скрипт получился великоват,потому,что сдесь большую часть занимает генерация .gif - файлов.. Выглядит громоздко , зато пашет как трактор ;))!!
#!/usr/bin/perl
#newcount.cgi
###############
$LOCK_EX=2;
$LOCK_UN=8;
$datafile="counter.dat";
###############
$Dig[0]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[1]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x02\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[2]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x01\x01\x01\x02\x02\x02\x01".
"\x01\x01\x02\x02\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[3]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".

"\x01\x01\x01\x01\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[4]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x01\x02\x02\x01\x01\x01".
"\x01\x01\x02\x01\x02\x01\x01\x01".
"\x01\x02\x01\x01\x02\x01\x01\x01".
"\x01\x02\x02\x02\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[5]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[6]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[7]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x02\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x02\x01\x01\x01\x01".
"\x01\x01\x01\x02\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x01\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[8]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[9]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
###############
sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
sub gifcompress{
 local($bmp)=@_;
 local(@Tbl);
 local($rootsize)=(8); #bits per pixel
 local($i,$bmp_i,$c,$k,$ck,$code,$tbl_i,$comp_size);
 local($cc,$eoi);
 local($bits)=('');
 local($RV)=('');
 $bmp_i=0;
 foreach $i(0..2**$rootsize-1){$Tbl[$i]=pack('C',$i);}
 $tbl_i=2**$rootsize+2;
 $cc=2**$rootsize;
 $eoi=2**$rootsize+1;
 $comp_size=$rootsize+1;
 $c='';
 $bits.=substr(unpack('b16',pack('S',$cc)),0,$comp_size);
 if($cc==(2**$compsize -1)){$comp_size++;}
 while($bmp_i<length($bmp)){
  $k=substr($bmp,$bmp_i,1);
  $ck=$c.$k;
  $code=-1;
  for($i=0;$i<$tbl_i;$i++){if($Tbl[$i] eq $ck){$code=$i;}}
  if($code!=-1){
    $c=$ck;
    }
  else{
    $Tbl[$tbl_i]=$ck;$tbl_i++;#add
    $code=-1;for($i=0;$i<$tbl_i;$i++){if($i!=$eoi&&$i!=$cc){if($Tbl[$i] eq $c){$code=$i;}}}
    $bits.=substr(unpack('b16',pack('S',$code)),0,$comp_size);
    if($code==(2**$compsize -1)){$comp_size++;}
    if($code==4095){$bits.=substr(unpack('b16',pack('S',$cc)),0,$comp_size);
    	foreach $i(0..2**$rootsize-1){
    		$Tbl[$i]=pack('C',$i);
    	};
    	$tbl_i=2**$rootsize+2;
    	$comp_size=$rootsize+1;$c='';
    }
    $c=$k;
    }
  $bmp_i++;
  }
 $code=-1;for($i=0;$i<$tbl_i;$i++){if($i!=$eoi&&$i!=$cc){if($Tbl[$i] eq $c){$code=$i;}}}
 $bits.=substr(unpack('b16',pack('S',$code)),0,$comp_size);
 if($code==(2**$compsize -1)){$comp_size++;}
 if($code==4095){$bits.=substr(unpack('b16',pack('S',$cc)),0,$comp_size);
 	foreach $i(0..2**$rootsize-1){
 		$Tbl[$i]=pack('C',$i);
 	};
 	$tbl_i=2**$rootsize+2;
 	$comp_size=$rootsize+1;$c='';
 }
 $bits.=substr(unpack('b16',pack('S',$eoi)),0,$comp_size);
 local($bytes)=('');
 for($i=0;$i<length($bits)/8;$i++){
  $bytes.=pack('b8',substr($bits,$i*8,8));
  }
 $RV=pack('C',$rootsize);
 for($i=0;$i<length($bytes)/255;$i++){
  $block=substr($bytes,$i*255,255);
  $RV.=pack('C',length($block));
  $RV.=$block;
  }
 $RV.=pack('C',0);
 return $RV;
 }

sub gengif2{
 local($Number,$digits,$c_r,$c_g,$c_b)=@_;
 local($Ascii_Num,$Zeropad);
 $Ascii_Num=''.$Number;
 $digits=($digits>length($Ascii_Num)?$digits:length($Ascii_Num));
 $Zeropad='0' x $digits;
 substr($Zeropad,- length($Ascii_Num),length($Ascii_Num))=$Ascii_Num;
 $Ascii_Num=$Zeropad;
 local($sym,$pos,$i);
 local($bmp)="\x00" x ($digits * 8 * 8);
 foreach $pos(0..length($Ascii_Num)-1){
  $sym=substr($Ascii_Num,$pos,1);
  foreach $i(0..7){
   substr($bmp,$i*$digits*8 + $pos*8,8)=substr($Dig[$sym],$i*8,8);
   }
  }
 local($g_x,$g_y);
 $g_x=$digits*8;
 $g_y=8;
 local($transp_index)=(1);
 local($RV)=('GIF89a');
 local($lscr)=(pack('SS',$g_x,$g_y).pack('B8','11110111').pack('C',0).pack('C',0));
 local($pal)=(pack('CCC',0x0,0x0,0x0).pack('CCC',0x7f,0x7f,0x7f).pack('CCC',$c_r,$c_g,$c_b).
         pack('CCC',0x7f,0x0,0x0).pack('CCC',0x0,0x7f,0x0).pack('CCC',0x0,0x0,0x7f));
 local($tmp)=(pack('C',0) x 768);
   substr($tmp,0,length($pal))=$pal;
   $pal=substr($tmp,0,768);

 local($gr_ext)=(pack('C',0x21).pack('C',0xf9).pack('C',4).pack('B8',
 '00001001').pack('S',0).pack('C',$transp_index).pack('C',0));

 local($imgdescr)=(pack('C',0x2c).pack('SSSS',0,0,$g_x,$g_y).pack('B8','00000000'));

 local($gifdata)=(&gifcompress($bmp));
 local($gifend)=(pack('C',0x3b));
 $RV=$RV.$lscr.$pal.$gr_ext.$imgdescr.$gifdata.$gifend;
 return $RV;
 }
######################
binmode(STDOUT);
$|=1;
#print "Content-Type: image/gif\n\n";
#print &gengif2($Number,$digits,$c_r,$c_g,$c_b);
#print &gengif2(1234567890,9,100,0,0);

$query=$ENV{'QUERY_STRING'};
if($query eq ''){print "Content-Type: image/gif\n\n";print &gengif2(1234567890,10,100,0,0);}
else{
 @fields=split(/&/,$query);
 foreach(@fields){
   if(/^id=(.*)/){$id=&urldecode($1);}
   if(/^dig=(.*)/){$dig=&urldecode($1);}
   }
 $digits=$dig;
 $digits=9 unless($dig);
 $cur_ip=$ENV{'REMOTE_ADDR'};
 $cur_time=time;
 open(DATA,"+<$datafile");
 flock(DATA,$LOCK_EX);
 @Dat=<DATA>;
 chop(@Dat);
 %Counters=@Dat;
 ($count,$ip,$t)=split(/\s+/,$Counters{$id});
 $count++ if(($ip!=$cur_ip)||($cur_time-$t>30));
 $ip=$cur_ip;
 $t=$cur_time;
 $Counters{$id}=join(' ',$count,$ip,$t);
 seek(DATA,0,0);
 foreach(keys %Counters){
  print DATA "$_\n";
  print DATA "$Counters{$_}\n";
  }
 truncate(DATA,tell(DATA));
 flock(DATA,$LOCK_UN);
 close(DATA);
 print "Content-Type: image/gif\n\n";
 print &gengif2($count,$dig,100,0,0);
 }
Если вам циферки не понравились вы их легко сможете заменить.



Вместо заключения


Вот и подошла к концу моя книга. Надеюсь что она найдет поддержку в широких слоях интернет-сообщества.В ней я постарался изложить простым и понятным языком немного из того, что я знаю. Очень надеюсь, что я достиг своей цели (если вы дочитали мое учебное пособие до конца).И я также я очень надеюсь, что те цели которые вы перед собой ставили при прочтении данной книги станут осуществимыми. Если вы прочитали эту книгу, и вам понравилось, порекомендуйте ее своим знакомым web-дизайнерам и начинающим интернет-программистам. Для них она будет очень полезна. Также жду ваших отзывов и предложений. Я постараюсь ответить на интересующие вас вопросы. Если вам данная тема понравится, то я обязательно подумаю над тем, чтобы написать продолжение, в котором я расскажу о тех вопросах, с которыми сталкивается профессиональный интернет-программист в своей работе.
И еще раз хочу дать совет и пожелание: если что-нибудь у вас не получается, не огорчайтесь и не опускайте руки, всегда есть способ сделать любое, пусть даже с первого взгляда сложное дело. Тем более в наших руках такая мощная вещь как интернет, и от нас зависит, каким он будет через месяц, через год, через два, через десять лет. И если вы действительно приложите усилия , то ваш сайт станет достойным всеобщего восхищения.

Оглавление:


Пару слов от автора
Краткое лирическое отступление насчет CGI
Итак ...приступим...
Переменные среды CGI
Прекрасный язык Perl
Заголовки запросов и ответов
Права Доступа
Генерация ответа
Обработка Форм
Изображения ismap
Анимация
Несколько советов по отладке
Trics and traps
Примеры приложений:
Кто посещает мою страничку?
Гостевая книга
Счетчик посещений

Вместо заключения


Пару слов от автора


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

Леша.
paaa@uic.nnov.ru
http://www.uic.nnov.ru/~paaa/cgi-bin/contact.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

<HTML>
<BODY>
.......
</BODY></HTML>


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

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



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

На кого ориентировано данное учебное пособие -спросите вы ? Ну в принципе на достаточно широкую аудиторию тех, кто занимается Интернет-программированием и кто хотел бы освоить премудрости интерфейса CGI. Данная книга будет весьма полезна для web-дизайнеров, системных администраторов интернет-серверов, программистов и для простых пользователей интернет, которые хотели бы сделать свой сайт по-настоящему достойным называться хорошим сайтом.
Так как интернет в основном строится на операционной системе UNIX , то изложеный сдесь материал может быть без особых модификаций реализован на практически любой UNIX-системе.
Кроме того, я также делаю предположение , что ваш web-сервер поддерживает интерфейс CGI и для вас эта поддержка включена. (на "халявных" серверах администраторы отключают CGI и SSI для пользовательских директорий - просто это такая политика - предоставлять только ОЧЕНЬ МИНИМАЛЬНЫЙ сервис.) Так что если вы хотите изучать CGI то вам нужет нормальный ,полнофункциональный сервер. Если же вы сами являетесь системным администратором на своем сервере , то для вас, естественно нет проблем, ведь включить CGI для какой-нибудь директории - это просто подправить одну строчку в файле конфигурации сервера.
Замечание:
Если же вы используете Windows NT ,то материал данной книги вам будет тоже очень полезен, однако будьте готовы к тому что в некоторые скрипты придется вносить значительные изменения. В некоторых случаях,когда сказывается то , что возможности NT по работе с сетью намного хуже, чем у UNIX,то некоторые скрипты вовсе нельзя будет использовать.

Давайте вместе писанем какой нибудь простенький скриптик а потом я вам расскажу где сдесь собака порылась....
Ну сначала в своем домашнем каталоге создайте директорию 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 "<HTML><BODY>";
print "<H1>Hello you!!!</H1>";
print "</BODY></HTML>";

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

chmod +x first.cgi

Ну вот,подходим к торжественному моменту.... наберите в строке браузера http://www.ваш.сервер.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. Обычно он находится /usr/bin/perl или /usr/local/bin/perl ,выяснить это можно одной из комманд which perl или whereis perl ну или (что очень долго) запустить полный поиск find / -name perl -print.
Вторая строка это просто коментарий -вы можете тыкать чо угодно после знака #, однако я пишу обычно во второй строке название скрипта, что очень удобно.
Затем идет print "Content-Type: text/html\n\n"; Это заголовок указывающий тип содержимого.
Все что скрипт печатает в свой стандартный вывод STDOUT идет на обработку к серверу. Пустая строка отделяет заголовок от тела,которое в нашем случае представляет собой
<HTML><BODY>
<H1>Hello you!!!</H1>
</BODY></HTML>
Сервер обработает ответ скрипта и на базе него сформирует и пошлет браузеру ответ.(Сервер обычно не изменяет тела сообщения,он только дополняет заголовок нужными для работы протокола 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=/var/www/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)
HTTP_HOST
Имя хоста к которому обращается браузер. Так как физически на одном сервере может находиться сразу много серверов (Виртуальные Хосты), то должен быть способ сообщить серверу к какому именно идет обращение. Скрипт же может тоже в зависимости от этой переменной производить различные действия, таким если он используется на сайтах сразу нескольких виртуальных хостов.
Пример:HTTP_HOST=www.nnov.city.ru


Ну,начнем применять на практике усвоеные уроки.
#!/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 "<HTML><HEAD><TITLE>CGI-Variables</TITLE></HEAD>\n";
print "<BODY>\n";
print "Enter here something:<ISINDEX><BR>\n";
print "Your request is:$ENV{'REQUEST_STRING'}<BR>\n";
print "Decoded request is:urldecode($ENV{'REQUEST_STRING'})<BR>\n";
print "<HR>\n";
print "Variables:<BR>\n";
print "<I><B>REQUEST_METHOD</B></I>=$ENV{'REQUEST_METHOD'}<BR>\n";
print "<I><B>QUERY_STRING</B></I>=$ENV{'QUERY_STRING'}<BR>\n";
print "<I><B>CONTENT_LENGTH</B></I>=$ENV{'CONTENT_LENGTH'}<BR>\n";
print "<I><B>CONTENT_TYPE</B></I>=$ENV{'CONTENT_TYPE'}<BR>\n";
print "<I><B>GATEWAY_INTERFACE</B></I>=$ENV{'GATEWAY_INTERFACE'}<BR>\n";
print "<I><B>REMOTE_ADDR</B></I>=$ENV{'REMOTE_ADDR'}<BR>\n";
print "<I><B>REMOTE_HOST</B></I>=$ENV{'REMOTE_HOST'}<BR>\n";
print "<I><B>SCRIPT_NAME</B></I>=$ENV{'SCRIPT_NAME'}<BR>\n";
print "<I><B>SCRIPT_FILENAME</B></I>=$ENV{'SCRIPT_FILENAME'}<BR>\n";
print "<I><B>SERVER_NAME</B></I>=$ENV{'SERVER_NAME'}<BR>\n";
print "<I><B>SERVER_PORT</B></I>=$ENV{'SERVER_PORT'}<BR>\n";
print "<I><B>SERVER_PROTOCOL</B></I>=$ENV{'SERVER_PROTOCOL'}<BR>\n";
print "<I><B>SERVER_SOFTWARE</B></I>=$ENV{'SERVER_SOFTWARE'}<BR>\n";
print "<I><B>HTTP_ACCEPT</B></I>=$ENV{'HTTP_ACCEPT'}<BR>\n";
print "<I><B>HTTP_USER_AGENT</B></I>=$ENV{'HTTP_USER_AGENT'}<BR>\n";
print "<I><B>HTTP_HOST</B></I>=$ENV{'HTTP_HOST'}<BR>\n";
print "<HR>\n";
print "All enviroment:<BR>\n";
foreach $env_var (keys %ENV){
 print "<I>$env_var=$ENV{$env_var}</I><BR>\n";
 }
print "</BODY></HTML>\n";
Так как все ваши .cgi -файлы должны быть исполняемыми то чтоб облегчить себе жизнь заведите себе в директории cgi-bin командный файл mkcgi ,содержащий
#!/bin/sh
#mkcgi
chmod +x *.cgi
и сделайте его в свою очередь исполняемым chmod +x mkcgi -он сильно упростит вам жизнь.
Ну а теперь запускайте скрипт......
Изучив информацию,выдаваемую данным скриптом вы сможете лучше ориентироваться в переменных окружения CGI.

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


Вы наверное обратили свое внимание что CGI скрипты пишутся обычно на языке Perl (Practical Extraction and Report Language)- очень удобном языке,впитавшем из других все лучшие черты.Может у вас возникнуть сомнение :Ну вот!Изучать новый язык программирования!? Спешу вас успокоить,изучение Perl не будет в тягость (я сужу по своему опыту!). Вы даже сами не заметите как выучите его.Если вы хоть когда-нибудь программировали скажем на C и использовали утилиту grep для поиска регулярных выражений в тексте,то вам будет еще легче. Для Perl родной платформой является Unix поэтому пользователям PC он мало известен. Мое целенаправленое доведение Perl до широкой публики началось с того что я скачал Perl под Windows (фирмы ActiveWare) К нему прилагается отличная гипертекстовая HTML- документация, даже быстрого просмотра которой хватит , чтобы начать хорошо и широко использовать его. Хоть он значительно уступает и по скорости и по эффективности своему Unix'ному аналогу, все равно самый лучший способ изучить язык это программировать на нем. Если вы как я дома используете большую часть времени не Windows а Unix то с изучением Perl у вас вообще не должно быть особых сложностей. Я же от себя могу сказать, что даже после небольшого опыта изучения его, он стал моим любимым языком программирования....

Все в нем сделано для удобства программиста (в отличии например от 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=<STDIN>;
foreach(@Data){
 chomp;
 print if /^From:/;
 }
аналогичен такому:
@Data=<STDIN>;
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=<STDIN>;
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-hA-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 "<HTML><BODY></BODY></HTML>";
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(MAIL,"|mail paaa@uic.nnov.ru");#Пошлем информацию по почте
print MAIL "Hello\n";
print MAIL "...\n";
print MAIL "...\n";
close(MAIL);
когда вы открыли файл вы можете считать из него строку в скалярную переменную Вот так:$str=<FILE>
избавиться от символа новой строки на конце поможет функция chomp, ведь этот символ может помешаться например в имени файла или при выводе на экран
print "Введите имя файла:";
$fname=<STDIN>;
chomp($fname);
open(F,$fname)|| die "Cannot open $fname $!\n";
.....
Если также подставить списочную переменную,то получим список строк файла от текущей строки и до конца
print "Что искать:";
$search=<STDIN>;
chomp($search);
@L=<F>;
foreach(@L){
 print if /$search/;
 }
а можно и так:
print "Что искать:";
$search=<STDIN>;
chomp($search);
foreach(<F>){
 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; , &gt; и &amp; а как неплохо чтоб автоматически все http://www.... превратились в <A href="http://www...." >http://www....</A>)
#!/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(<IN>){
 s/&/&amp;/g;
 s/</&lt;/g;
 s/>/&gt;/g;
 s/(http:\/\/\S+)/<A href="$1">$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 -запрос.
Запрос состоит из Строки запроса(она обязательна) и остальных полей. Синтаксис строки :МЕТОД <SP> URI <SP> HTTP/версия <CRLF>
где <SP> -пробел ,<CRLF> -переход на новую строку
Методы 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

Поле Host: служит для того , чтобы указать, к какому хосту идет обращение. Данное поле не входит в число обязательных. Однако оно является необходимым в тех случаях, когда одному физическому серверу соответствует несколько виртуальных хостов. В этом поле тогда указывается какой из виртуальных хостов имеется в виду.
Host: www.nnov.city.ru

Примеры запросов:
Простейший запрос:
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=someorg&Name=&email=&comment= HTTP/1.0
User-Agent: Mozila/2.0
Accept: text/html
Accept: image/gif

Используя метод POST данные передаются в теле сообщения запроса:
POST /~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+%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 <SP> Status-Code <SP> 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

<HTML><HEAD><TITLE>Hello</TITLE></HEAD>
<BODY bgcolor="green" text="yellow">
......
</HTML>
А вот такое сервер выдаст в неудачном случае:
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

<HTML><HEAD>.......
Но иногда такое(когда он служит для перенаправления):
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 <PIC>;
      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 "<A href=\"$_\">$_</A>";
   print "&nbsp;readable" if -r;
   print "&nbsp;writable" if -w;
   print "&nbsp;executable" if -x;
   print "<BR>\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"

 <H1>Hello,Word!!</H1>
 <HR>
 <FONT size=+1 color=red>Hello people!</FONT>
 --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:
<SCRIPT language="JavaScript">
 if(navigator.AppName=="Netscape"){
  /*Сделать чо-нибудь специфичное для Netscape*/
  }
 else if(navigator.AppName=="Microsoft Internet Explorer"){
  /*Сделать чо-нибудь специфичное для Explorer*/
  }
 else{
  /*Не делаем специфичных вещей-хрен его знает с каким браузером мы имеем дело*/
  }
</SCRIPT>
или
<SCRIPT language="JavaScript">
if((navigator.AppName=="Netscape")&&(parseFloat(navigator.appVersion)<3.0)){
   document.writeln("Пользуетесь слишком старым браузером");
   }
</SCRIPT>

Ну не волнуйтесь вы так ,мы CGI-программисты не в самых худших условиях на этот счет. Вспомните о том что браузер сам при запросе посылает вам данные о себе и о своей версии. И делает он это для того,чтобы эту информацию можно было учесть.
В запросе он указывает User-Agent: которое и попадает на сервере в переменную среды HTTP_USER_AGENT ,которую и можно использовать.
Например если в ней содержится Mozilla/3.01Gold (Win95;I) то значит вы имеете дело с Netscape (Mozilla-кодовое название Netscape Navigator'а),версии 3.01Gold и далее после имени и версии может следовать необязательная информация ,например как в приведеном примере о платформе Win95 и о том является ли версия U -для США (USA) или I -международной(International). Напомню,что такая информация необязательна.(То есть если браузер послал информацию User-Agent: то гарантировано расчитывать вы можете только на Название/Версия).
Ну вот я слишком много развел демагогии,пора переходить к практическим примерам.
Допустим ваш скрипт генерирует какие-то тэги,которые слишком старые браузеры не поддерживают,причем без них не обойдешся,они составляют всю 'изюминку' сайта.
#!/usr/bin/perl
#oldbrowser.cgi
print "Content-Type: text/html\n\n";
if(defined ($ENV{'HTTP_USER_AGENT'})){
 $browser=$ENV{'HTTP_USER_AGENT'};
 ($vers)=($browser=~/\/(\d+\.\d+)/);
 if(($browser=~/mozilla/i)&&($vers<=2.0)){
   print "<HTML><HEAD><TITLE>Too old!</TITLE></HEAD>";
   print "<BODY bgcolor=\"red\" text=\"black\">";
   print "<CENTER><H1>Ваш Netscape Слишком старый для этого сайта";
   print "(старость не радость;))</H1></CENTER>";
   print "</BODY></HTML>";
   exit;
   }
 if(($browser=~/msie/i)&&($vers<=3.0)){
   print "<HTML><HEAD><TITLE>Too old!</TITLE></HEAD>";
   print "<BODY bgcolor=\"red\" text=\"black\">";
   print "<CENTER><H1>Ваш Explorer устарел";
   print "(а не пора ли сделать апгрейт хотя бы до 4.0 версии)</H1></CENTER>";
   print "</BODY></HTML>";
   exit;
   }
 }
print "<HTML><HEAD>.........";
Ну уже почувствовали,насколько это здорово.А вот еще примерчик.Это из разряда того, что тэги бывают разные.Например в Explorer есть тэг BGSOUND предназначеный для проигрывани музыки на страничке.(В Netscape этого тега нет,и поэтому для втыкания музыки приходится использовать подключаемые модули plugin).Мутится с этими Плугинами Вам в облом,а хочется побаловать человека хорошей музыкой,если браузер позволяет.
   ...
   ...
if($ENV{'HTTP_USER_AGENT'}=~/msie/i){
  print "<BGSOUND src=\"jmj00.mid\">";
  }
elsif($ENV{'HTTP_USER_AGENT'}=~/mozilla/i){
  #Оставлю сдесь коментарий,что воткну что-нибудь типа музыки,для Netscap'а ,
  #Когда мне не будет так в облом это делать.......
  }
Ну вот вы уже можете управлять этим процессом.Только не забывайте,что если вы не получили информацию о клиенте(так может быть,если например ваш скрипт вызвал какая-нибудь поисковая машина) то не в этом случае не надо делать никаких предположений,а просто пусть ваш скрипт продолжает делать то что должен был делать.

Как всегда Примерчик на последок.Этот примерчик позволит выбирать из списка файлов. и загружать что пользователь хочет.
#!/usr/bin/perl
#download.cgi

sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
@Filelist=qw(index.html readme.txt jmj00.mid gunshot.wav foto.gif);
@Sel_list=();
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
elsif($ENV{'REQUEST_METHOD'} eq 'POST'){sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});}
if($query eq ''){
 #Если никаких данных не подано на обработку,то сгенерируем форму,
 #которую и предложим заполнить пользователю.
 print "Content-Type: text/html\n\n";
 print "<HTML><HEAD><TITLE>File Downloading</TITLE></HEAD>";
 print "<BODY bgcolor=\"white\">";
 print "Выберите файлы которые вы хотите загрузить:<BR>";
 print "<FORM METHOD=\"POST\">";
 print "<SELECT NAME=\"file\" size=4 multiple>";
 foreach(@Filelist){
   print "<OPTION value=\"$_\">$_";
   }
 print "</SELECT><BR>";
 print "<INPUT TYPE=\"Submit\" value=\"Download!\">";
 print "</FORM>";
 print "</BODY></HTML>"
 }
else{
 @formfields=split(/&/,$query);
 foreach(@formfields){
   if(/^file=(.*)/){push(@Sel_list,urldecode($1));}
   }
 unless(@Sel_list){
   print "Content-Type: text/html\n\n";
   print "<HTML><BODY><CENTER><H1>Вы должны выбрать что-то из списка";
   print "</H1></CENTER></BODY></HTML>";
   }
 else{
   print "Content-Type: multipart/mixed;";
   print "boundary=\"bhy3e23r4t34tnehtpo7678nneu4232y213vdg\"\n\n";
   print "--bhy3e23r4t34tnehtpo7678nneu4232y213vdg\n";
   foreach(@Sel_list){
     print "Content-Type: application/x-qwerty; name=\"$_\"\n\n";
     open(F,"$_");
     print <F>;
     close(F);
     print "\n--bhy3e23r4t34tnehtpo7678nneu4232y213vdg\n";
     }
   print "Content-Type: text/html\n\n";
   print "<HTML><H1>Thats all folks!</H1></HTML>";
   print "\n--bhy3e23r4t34tnehtpo7678nneu4232y213vdg--\n";
   }
 }

Обработка Форм


Ну вот ,вы уже знаете достаточно,кое в чем уже успели приобрести опыт, пришло время перейти к очень важной теме - обработке форм. При всей простоте (кажушейся) это едва ли не самое главное предназначение всего стандарта CGI . Куда бы вы не зашли на любой уважающий себя сайт,везде вы встретите формы, которые вам предложат заполнить.В этом деле можно положится только на CGI, так как Java и JavaScript ,выполняющиеся на страничке у клиента не имеют доступа к серверу,на котором находится сайт.
Коротко вспомним о том что происходит при рассматриваемом процессе поближе,так сказать на трезвую голову ;). Итак браузер требует у сервера определенный URL (это может быть как простой документ,так и сгенерированый CGI) в этом документе может содержаться форма.Отображая такой документ браузер также выводит элементы формы (кнопки, поля ввода, поля ввода пароля, переключатели, радио-кнопки, списки, текстовые области,скрытые поля). И со всем этим добром пользователь может взаимодействовать.К форме естественно имеет доступ и встроеный язык программирования JavaScript -он может как использовать форму для своих нужд,не передавая CGI,так и помогать пользователю в заполнении формы.
После того,как пользователь заполнил форму он нажимат кнопку Submit которая говорит, что форму надо отправить на сервер. Браузер собирает все имена и значения элементов формы ,кодирует их методом urlencode и в зависимости от указаного в тэге FORM метода вызывает GET или POST с указаным URL,передавая ему данные. На сервере CGI-скрипту это попадает (в зависимости от метода) либо в переменную QUERY_STRING либо на STDIN.Скрипт может проверить данные ,занести их в какую нибудь базу данных,может как yahoo выполнить какой-нибудь поиск, может что-нибудь вычислить......да мало ли что он может,все зависит только от нашей фантазии..... В конце концов скрипт выдает браузеру ответ,который он и отображает.В этом ответе может содержаться все что вашей душе угодно от сообщения об удачном или неправильном запросе до таких ответов,по сравнению с которыми yahoo и altavista подвиснут от зависти, главное чтоб вам и тем кто посещает ваш сайт это нравилось.;)

Ну а теперь немного о синтаксисе элементов форм ,их описании и самое главное особенностях при обработке CGI-скриптом.
Итак немного экскурс в HTML:
FORM
<FORM action="http://......cgi" method="GET"|"POST" enctype="encodingType"
      name="formName" target="windowName" onSubmit="Handler">
</FORM>
Атрибуты:
action
как раз и задает тот URL,который будет и обрабатывать форму, если он опущен,то текущий URL документа(а он-то может быть сгенерирован нашим скриптом).
method
задает метод GET или POST
enctype
обычно не задается,для форм он application/x-www-form-urlencoded -по умолчанию, и поддерживается всеми CGI скриптами.Но если вы уж очень хотите чтобы браузер послал вам данные в другом формате (например text/plain) то можете указать этот тип кодировки,только потом не жалуйтесь,что ваш скрипт не может разделить поля,или вообще начинает глючить когда пользователь ввел какой-то спецсимвол.
name
Задается для JavaScript,чтоб обращатся к форме по имени,а не по номеру. Для CGI не играет ни какой роли,так как внутреннее для браузера.
target
Может Определять в какой фрейм отправить полученую информацию.Имеет значение во фреймосодержащих документах.Прозрачен для CGI обработки данных.
onSubmit
Определяет JavaScript -обработчик активизации формы.Применяется для проверки JavaScript'ом правильности заполнения.Опять таки прозрачен для CGI.
Пример типичной формы:
<FORM action="http://www.uic.nnov.ru/~paaa/cgi-bin/test.cgi" method="POST">
 .........Поля формы.........
</FORM>
Форма может содержать элементы.Элементы имеют имена,которые используются дл кодирования пар имя=значение.Некоторые Элементы не передаются CGI,а используются JavaScript для управления,например кнопки.Некоторые поля передаются только в тех случаях, когда в них что-то выбрано,например списки и переключатели.Остальные поля передаются всегда, даже когда они пустые.
Например:
<FORM action="http://www.doom/cgi-bin/test.cgi">
Your Name:<INPUT name="Name"><BR>
E-Mail:<INPUT name="Email"><BR>
Are you doomer:<INPUT type="checkbox" name="doomer" value="Yes">
<INPUT type="submit" value="Send Form!">
</FORM>
Допустим вы ввели имя lesha и адрес paaa@uic.nnov.ru,при этом выбрали переключатель После нажатия кнопки будет отправлен вот такой запрос:
http://www.doom/cgi-bin/test.cgi?Name=lesha&Email=paaa@uic.nnov.ru&doomer=Yes
Если же вы не выбрали переключатель,то запрос будет таким:
http://www.doom/cgi-bin/test.cgi?Name=lesha&Email=paaa@uic.nnov.ru
,как видите элемент doomer не вошел в строку запроса
Теперь попробуйте оставить поля редактирования пустыми:
http://www.doom/cgi-bin/test.cgi?Name=&Email=
Эти элементы (Name и Email) присутствуют и сообщают что они пустые.

Кнопка(button)
<INPUT type="button" name="buttname" value="Текст На Кнопке" onClick="Handler">
В форме изображается кнопка,при нажатии которой вызывается JavaScript-обработчик заданый атрибутом onClick ,атрибут name служит для JavaScript-именования кнопки а не дл передачи CGI.Так как значение кнопки не передается CGI, value задает Текст,изображаемый на кнопке.

<FORM onSubmit="return false;">
<INPUT type="button" value="Просто Кнопочка"
onClick="alert('Нажали на кнопку!');">
</FORM>
Submit
<INPUT type="submit" name="submitName" value="Отправить Форму" onClick="Handler">
Кнопка,предназначеная для передачи формы.Опять же,сама не передается,а служит только для управления. текст на ней задается атрибутом value.
  <FORM onSubmit="alert('Нечего Посылать!');return false;">
  <INPUT type="Submit" value="Послать!">
  </FORM>
  
Reset
<INPUT type="reset" name="resetName" value="Очистить" onClick="Handler">
Кнопка очистки формы.При ее нажатиивсем измененым элементам возвращается значение по умолчанию.
  <FORM onSubmit="return false;">
  <INPUT name="something"><BR>
  <INPUT type="reset" value="Очистить!">
  </FORM>
  

Поле ввода(text)
<INPUT [type="text"] name="textName" value="textValue" size=число [обработчики]>
Применяется очень часто,поэтому тип "text" служит для INPUT по умолчанию,его не надо каждый раз указывать.Имя поля,задаваемое name является обязательным для CGI (в отличии от JavaScript,где элементы формы можно индексировать по номерам,а имена для удобства и читабельности кода служат).Можно задать значение по умолчанию атрибутом value,которое будет после загрузки докумета.атрибут size позволяет задать размер поля.Также может содержать обработчики onBlur,onChange,onFocus,onSelect.
  <FORM onSubmit="return false;">
  <INPUT name="something" size=30
    value="Введите что-нибудь">
  </FORM>
  
Текстовая Область(textarea)

<TEXTAREA name="textareaName" rows="число" cols="число" wrap="hard"|"soft">
 TextToEdit
</TEXTAREA>
Область многострочного редактирования.Размеры в строках и столбцах задаютс атрибутами rows и cols.Значения атрибута wrap "hard" и "soft" -означают соответственно мягкую или жесткую разбивку на строки (в большинстве случаев ето не существенно). На что следует действительно обратить внимание так это на символ,используемый для указания перехода на новую строку. В Windows это '\r\n' а в Unix '\n',так что если это для вас существенно,то приводите преобразование,например так:
$my_text =~ s/\r\n/\n/g;
  <FORM onSubmit="return false;">
  <TEXTAREA name="MyText" rows=7 cols=30>
  Тут можно что-нибудь написать
  </TEXTAREA>
  </FORM>
  
Поле ввода пароля(password)
<INPUT type="password" name="passName" size=число value="passValue">
Очень похоже на поле ввода,отличается тем что вместо символов в нем отображаютс символы '*'.Служит для ввода пользователем пароля.
  <FORM onSubmit="return false;">
  Пароль:
  <INPUT type="password"
    name="yourpass" size=30>
  </FORM>
  
Пароль:
Скрытое поле(hidden)
<INPUT type="hidden" name="hiddName" value="hidValue">
Поле не отображаемое на экране.Но оно имеет имя и значение и следовательно передается в форму. Служит для того (и очень часто програмисты его применяют) чтоб передавать скрипту какую нибудь информацию.Например,если ваш скрипт обрабатывает несколько форм разных типов,то в скрытом поле каждой формы можно указать с какой формой конкретно вы имеете дело. Так как это ваша внутренняя кухня то нечего пользователю мозолить глаза этой информацией.
  <FORM onSubmit="return false;">
  Этого сдесь вам не видно,поле-скрытое.
  <INPUT type="hidden" name="formNum" value="3">
  </FORM>
  
Этого сдесь вам не видно,поле-скрытое.
Переключатель(checkbox)
<INPUT type="checkbox" name="checkboxname" value="checkboxValue" [checked] onClick="Handler">Text
В отличии от кнопки,атрибут value сдесь не задает на надпись на переключателе,а его значение(внутреннее).Поэтому если надо что-то подписать,пишите рядом в ним. Может быть сразу выбраным если указан атрибут checked .Если value не указано то значение по умолчанию "on" .Передается только в том случае,когда выбран.
  <FORM onSubmit="return false;">
  <INPUT type="checkbox" name="inet" value="Yes"
    checked>Доступ к Интернет
  </FORM>
  
Доступ к Интернет
Радио-кнопка(radio)
<INPUT type="radio" name="radioName" value="radioVal1" [checked] onClick="Handler">Text
В отличие от checkbox может быть несколько радиокнопок с одинаковым параметром name ,но с разными value,из них передается только та,что выбрана.Одна из них может быть изначально выбрана по умолчанию checked.Например:
  <FORM onSubmit="return false;">
  Вы уверены?<BR>
  <INPUT type="radio" name="Radbut" checked>Yes
  <INPUT type="radio" name="Radbut">No
  </FORM>
  
Вы уверены?
Yes No
Список(select)
<SELECT name="SelectName" size=число [multiple] [обработчики] >
<OPTION value="optionValue1" [selected]>Опция 1
<OPTION value="optionValue2" [selected]>Опция 2
<OPTION value="optionValue3" [selected]>Опция 3
.....
<OPTION value="optionValueN" [selected]>Опция N
</SELECT>
Задает список,позволяющий выбрать одну (или несколько) опций из списка. Если атрибут multiple не указан,то создается простой выпадающий список,в котором можно выбрать только одну из опций.Его значение всегда передается,т.к. всегда хоть одно выбрано. Если указан атрибут multiple,то во первых можно указать размер видимой части списка атрибутом size (Если опций больше появится скролинг).Во вторых передаются только выбраные опции ,т.е.Он может передатся несколько раз ?SelectName=opt1&SelectName=opt2&SelectName=opt9 если выбраны скажем несколько опций.А может и не разу,если ничего не выбрано из списка. Можно задавать обработчики onBlur,onChange,onFocus.
  <FORM onSubmit="return false;">
  Ваш цвет:<BR>
  <SELECT name="singleSel">
   <OPTION value="white">Белый
   <OPTION value="black">Черный
   <OPTION value="magenta">Фиолетовый
   <OPTION value="green">Зеленый
   <OPTION value="red">Красный
  </FORM>
  
Ваш цвет:
  <FORM onSubmit="return false;">
  Какие сорта пива вы пили:<BR>
  <SELECT name="miltiSel" multiple size=4>
   <OPTION value="Балтика">Балтика
   <OPTION value="Толстяк">Толстяк
   <OPTION value="Премьер">Премьер
   <OPTION value="Хольстен">Хольстен
   <OPTION value="Бавария">Бавари
   <OPTION value="Coca-Cola ;)">Coca-Cola ;)
  </SELECT>
  </FORM>
  
Какие сорта пива вы пили:
Небольшая Помощь JavaScript
Для CGI-програмиста конечно JavaScript -это иной мир,вы можете спокойно пропустить этот абзац,если вы не знаете JavaScript,так как написаное в нем к CGI не относится, а скорей к самим формам и дизайну сайта.Я скажу пару слов о том как JavaScript может оказать посильную помощь в проверке правильности заполнения форм.Все проверки конечно можно и нужно делать на сервере,но когда имеешь дело с рассеяным пользователем, то для него заполнение простой формы превратится в мучение.Поясню на примере,в форме есть какие-то обязательные поля,например имя.Если пользователь забыл его указать то скрипт скажет ему об этом в сообщении он исправит это, допустим что-нибудь еще не так ввел .... Только на передачу данных по сети может уходить масса времени.А на обработку на локальной машине-доли секуды.
Вот Например как это можно применить JavaScript для предварительного контроля правильности. Допустим простейшая форма содержит имя и возраст.Имя не должно быть пустым, а возраст должен состоять из цифр.
<HTML><HEAD>
<SCRIPT language="JavaScript">
<!--
function IsNumber(data){
 var NumStr="0123456789";
 var ch;var count;
 for(var i=0;i<data.length;i++){
   ch=data.substring(i,i+1);
   if(NumStr.indexOf(ch)!=-1)count++;
   }
 if(counter==data.length)return true;
 else return false;
 }
function IsEmpty(data){
 if(data.length==0)return true;
 else return false;
 }
function IsFormOk(f){
 if(IsEmpty(f.Name.value)){
  alert('Имя не должно быть пустой строкой');
  return false;
  }
 if(!IsNumber(f.Age.value)){
  alert('Возраст должен состоять из цифр');
  return false;
  }
 return true;
 }
//--></SCRIPT></HEAD>
<BODY>
<FORM action="http://www.test.ru/cgi-bin/test.cgi" onSubmit="IsFormOk(this.form)">
Your Name:<INPUT name="Name"><BR>
Your age:<INPUT name="Age"><BR>
<INPUT type="submit" value="Послать Данные">
</FORM>
</BODY></HTML>
Ну вот ,на этом можно закончить это краткое введение в 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-hA-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
Так мы проходим по всем полям,которые нам переданы.Это стандартный подход,он годится в качестве шаблона.У вас может возникнуть вопрос,а что делать если вам переданы данные от списка у которого задана возможность выбора нескольких элементов и данные поступают в таком виде: Sel=opt1&Sel=opt2&Sel=opt9. Тут тоже нет никаких проблем,просто запихиваем эти поступающие значения в массив.
 foreach(@formfields){
   .....
   if(/^Sel=(.*)/){push @Sel,urldecode($1);}
   .....
   }
И потом спокойно оперируем с Полученым Массивом @Sel.
На этом можно так сказать заканчивается шаблонная часть скрипта и начинается содержательная, которая зависит только от вашей фантазии.....
Вы можете сколько угодно анализировать полученые значения,обращатся при этом к различным файлам .Если вы к этому приложите фантазию,то кто знает что получится....
А Пока Ради примера я вам напишу скрипт,который ведет социологическое исследование насчет курения и отношения к нему.Может он слишком массивен для данного пособия, но зато он наглядно показывает как достаточно простыми средствами можно проводить социологические исследования.
<HTML><!-- HTML файл с формой,можете повесить его себе на сайт! ->
<HEAD><TITLE>Социологический опрос насчет курения</TITLE></HEAD>
<BODY>
<CENTER><H1>Социологический опрос насчет курения</H1></CENTER>
<FORM action="cgi-bin/smoketest.cgi">
<TABLE>
<TR><TD>Ваш возраст:</TD><TD><INPUT name="age"></TD></TR>
<TR><TD>Вы курите(Y/N):</TD>
    <TD><INPUT type="radio" name="smoke" value="Yes" checked>Да
        <INPUT type="radio" name="smoke" value="No">Нет</TD></TR>
<TR><TD>Как вы относитесь если рядом кто-то курит?</TD>
    <TD><SELECT name="sm_near">
        <OPTION value="0">Резко негативно
        <OPTION value="1">Негативно
        <OPTION value="2" selected>Мне все равно
        <OPTION value="3">Позитивно
        <OPTION value="4">Резко позитивно
        </SELECT>
    </TD></TR>
<TR><TD>Сколько вы выкуриваете в день?</TD>
    <TD><SELECT name="sm_day">
        <OPTION value="0">Ни сколько
        <OPTION value="1">1 сигарету
        <OPTION value="2">2 сигареты
        <OPTION value="5">около 5
        <OPTION value="0.5pac">полпачки
        <OPTION value="pac">пачку
        <OPTION value="2pac">2 пачки
        <OPTION value="more">больше
        </SELECT>
    </TD></TR>
<TR><TD>Как давно вы начали курить?</TD>
    <TD><SELECT name="sm_stage">
        <OPTION value="noatall">Не начинал
        <OPTION value="onetime">Бросил
        <OPTION value="0.5year">Полгода
        <OPTION value="1year">Год
        <OPTION value="2year">2 Года
        <OPTION value="5year">5 Лет
        <OPTION value="more">Больше
        </SELECT>
    </TD></TR>
<TR><TD>Считаете ли вы это опасным для своего здоровья?</TD>
    <TD><SELECT name="sm_danger">
        <OPTION value="0">Очень Опасно
        <OPTION value="1">Думаю,что да
        <OPTION value="2" selected>Не знаю
        <OPTION value="3">Может самую малость
        <OPTION value="4">Нет,Безопасно.
        </SELECT>
    </TD></TR>
<TR><TD>Хотите ли вы бросить?</TD>
    <TD><SELECT name="sm_nosmoke">
        <OPTION value="0">Уже бросаю
        <OPTION value="1">Думаю бросить
        <OPTION value="2" selected>Иногда
        <OPTION value="3">Очень Редко
        <OPTION value="4">Никогда.
        </SELECT>
    </TD></TR>
<TR><TD><INPUT type="submit" value="Послать Данные"></TD>
    <TD><INPUT type="reset" value="Очистить Форму"></TD></TR>
</TABLE>
</FORM>
</BODY></HTML>
А вот скрипт для его обработки:
#!/usr/bin/perl
#smoketest.cgi
$datafile="smoke.dat";
sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
sub print_err{
 print "Content-Type: text/html\n\n";
 print "<HTML><HEAD><TITLE>Error!!</TITLE></HEAD>";
 print "<BODY><CENTER><H1>@_</H1>";
 print "</BODY></HTML>";
 exit;
 }
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
elsif($ENV{'REQUEST_METHOD'} eq 'POST')
  {sysread(STDIN,$query,$ENV{'CONTENT_LENGTH'});}
if($query ne ''){
  @formfields=split(/&/,$query);
  foreach(@formfields){
   if(/^age=(.*)/){$age=urldecode($1);}
   if(/^smoke=(.*)/){$smoke=urldecode($1);}
   if(/^sm_near=(.*)/){$sm_near=urldecode($1);}
   if(/^sm_day=(.*)/){$sm_day=urldecode($1);}
   if(/^sm_stage=(.*)/){$sm_stage=urldecode($1);}
   if(/^sm_danger=(.*)/){$sm_danger=urldecode($1);}
   if(/^sm_nosmoke=(.*)/){$sm_nosmoke=urldecode($1);}
   }
  if((!$age)||($age=~/\D/)){
   print "Content-Type: text/html\n\n";
   print "<HTML><BODY><H2>Возраст введен неправильно,должен состоять из цифр.</H2>";
   print "<FORM><INPUT type=\"button\" value=\"Вернуться назад к Анкете\"";
   print "onClick=\"history.back();\"></FORM>";
   print "</BODY></HTML>";
   }
  $anket_str=join('\t',($age,$smoke,$sm_near,$sm_day,$sm_stage,$sm_danger,$sm_nosmoke));
  open(DATA,">>$datafile") || print_err("Cannot open $datafile $!");
  print DATA "$anket_str\n";
  close(DATA);
  }
open(DATA,"$datafile") || print_err("Cannot open $datafile $!");
@AllData=<DATA>;
close(DATA);
$total=$#AllData;
foreach(@AllData){
 ($age,$smoke,$sm_near,$sm_day,$sm_stage,$sm_danger,$sm_nosmoke)=split(/\t/,$_);
 $smok_total++ if ($smoke eq 'Yes');
 $nosmok_total++ if ($smoke eq 'No');
 if($age<16){$age16_total++;
   if($smoke eq 'Yes'){$age16_sm++;}else{$age16_nosm++;}
   }
 if(($age>16)&&($age<=18)){$age16_18_total++;
   if($smoke eq 'Yes'){$age16_18_sm++;}else{$age16_18_nosm++;}
   }
 if(($age>18)&&($age<=20)){$age18_20_total++;
   if($smoke eq 'Yes'){$age18_20_sm++;}else{$age18_20_nosm++;}
   }
 if($age>20){$age20_total++;
   if($smoke eq 'Yes'){$age20_sm++;}else{$age20_nosm++;}
   }
 if($sm_near eq '0'){$near0++;}
 if($sm_near eq '1'){$near1++;}
 if($sm_near eq '2'){$near2++;}
 if($sm_near eq '3'){$near3++;}
 if($sm_near eq '4'){$near4++;}
 if($sm_day eq '0'){$day0++;}
 if($sm_day eq '1'){$day1++;}
 if($sm_day eq '2'){$day2++;}
 if($sm_day eq '5'){$day5++;}
 if($sm_day eq '0.5pac'){$dayhalfpac++;}
 if($sm_day eq 'pac'){$daypac++;}
 if($sm_day eq '2pac'){$day2pac++;}
 if($sm_day eq 'more'){$daymore++;}
 if($sm_stage eq 'noatall'){$stagenoatall++;}
 if($sm_stage eq 'onetime'){$statgeonetime++;}
 if($sm_stage eq '0.5year'){$stagehalfyear++;}
 if($sm_stage eq '1year'){$stage1year++;}
 if($sm_stage eq '2year'){$stage2year++;}
 if($sm_stage eq '5year'){$stage5year++;}
 if($sm_stage eq 'more'){$stagemore++;}
 if($sm_danger eq '0'){$danger0++;}
 if($sm_danger eq '1'){$danger1++;}
 if($sm_danger eq '2'){$danger2++;}
 if($sm_danger eq '3'){$danger3++;}
 if($sm_danger eq '4'){$danger4++;}
 if($sm_nosmoke eq '0'){$stopsmoke0++;}
 if($sm_nosmoke eq '1'){$stopsmoke1++;}
 if($sm_nosmoke eq '2'){$stopsmoke2++;}
 if($sm_nosmoke eq '3'){$stopsmoke3++;}
 if($sm_nosmoke eq '4'){$stopsmoke4++;}
 }
#########
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>Результаты обработки данных</TITLE></HEAD>";
print "<BODY bgcolor=\"yellow\">";
unless($total){print "<H1>Еще нет данных</H1></BODY></HTML>";exit;}
print "<CENTER><H1>Результаты обработки данных</H1></CENTER>";
print "<BR>\n";
print "Обработано анкет: $total<BR>\n";
print "Общие данные Всего:<BR>\n";
print "Курящие:$smok_total (".(($smok_total/$total)*100) ."%)<BR>\n";
print "Некурящие:$nosmok_total (".(($nosmok_total/$total)*100)."%)<BR>\n";
print "<TABLE>\n";
print "<TR><TD colspan=4>Возрастные группы:(<16,16..18,18..20,>20)</TD></TR>\n";
print "<TR><TD>Возраст</TD><TD>Курящие</TD><TD>Некурящие</TD><TD>Всего</TD></TR>\n";
print "<TR><TD>&lt;16:</TD><TD>$age16_sm</TD>";
print "<TD>$age16_nosm</TD><TD>$age16_total</TD></TR>\n";
print "<TR><TD>16..18:</TD><TD>$age16_18_sm</TD>";
print "<TD>$age16_18_nosm</TD><TD>$age16_18_total</TD></TR>\n";
print "<TR><TD>18..20:</TD><TD>$age18_20_sm</TD>";
print "<TD>$age18_20_nosm</TD><TD>$age18_20_total</TD></TR>\n";
print "<TR><TD>&gt;20:</TD><TD>$age20_sm</TD>";
print "<TD>$age20_nosm</TD><TD>$age20_total</TD></TR>";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Отношение когда кто-то курит рядом:(%)</TD></TR>\n";
print "<TR><TD>Резко негативно</TD><TD>".(($near0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Негативно      </TD><TD>".(($near1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Мне все равно  </TD><TD>".(($near2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Позитивно      </TD><TD>".(($near3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Резко позитивно</TD><TD>".(($near4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>В среднем выкуривают:(%)</TD></TR>\n";
print "<TR><TD>не курят:  </TD><TD>".(($day0/$total)*100)."</TD></TR>\n";
print "<TR><TD>1 сигарету:</TD><TD>".(($day1/$total)*100)."</TD></TR>\n";
print "<TR><TD>2 сигареты:</TD><TD>".(($day2/$total)*100)."</TD></TR>\n";
print "<TR><TD>5 сигарет: </TD><TD>".(($day5/$total)*100)."</TD></TR>\n";
print "<TR><TD>полпачки:  </TD><TD>".(($dayhalfpac/$total)*100)."</TD></TR>\n";
print "<TR><TD>пачку:     </TD><TD>".(($daypac/$total)*100)."</TD></TR>\n";
print "<TR><TD>2 пачки:   </TD><TD>".(($day2pac/$total)*100)."</TD></TR>\n";
print "<TR><TD>больше:    </TD><TD>".(($daymore/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Стаж курения:(%)</TD></TR>\n";
print "<TR><TD>Не начинал</TD><TD>".(($stagenoatall /$total)*100)."</TD></TR>\n";
print "<TR><TD>Бросил    </TD><TD>".(($statgeonetime/$total)*100)."</TD></TR>\n";
print "<TR><TD>Полгода   </TD><TD>".(($stagehalfyear/$total)*100)."</TD></TR>\n";
print "<TR><TD>Год       </TD><TD>".(($stage1year   /$total)*100)."</TD></TR>\n";
print "<TR><TD>2 Года    </TD><TD>".(($stage2year   /$total)*100)."</TD></TR>\n";
print "<TR><TD>5 Лет     </TD><TD>".(($stage5year   /$total)*100)."</TD></TR>\n";
print "<TR><TD>Больше    </TD><TD>".(($stagemore    /$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Курение опасно:(%)</TD></TR>\n";
print "<TR><TD>Очень Опасно       </TD><TD>".(($danger0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Думаю,что да       </TD><TD>".(($danger1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Не знаю            </TD><TD>".(($danger2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Может самую малость</TD><TD>".(($danger3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Нет,Безопасно.     </TD><TD>".(($danger4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "<TABLE>\n";
print "<TR><TD colspan=2>Хотели ли вы бросить:(%)</TD></TR>\n";
print "<TR><TD>Уже бросаю   </TD><TD>".(($stopsmoke0/$total)*100)."</TD></TR>\n";
print "<TR><TD>Думаю бросить</TD><TD>".(($stopsmoke1/$total)*100)."</TD></TR>\n";
print "<TR><TD>Иногда       </TD><TD>".(($stopsmoke2/$total)*100)."</TD></TR>\n";
print "<TR><TD>Очень Редко  </TD><TD>".(($stopsmoke3/$total)*100)."</TD></TR>\n";
print "<TR><TD>Никогда.     </TD><TD>".(($stopsmoke4/$total)*100)."</TD></TR>\n";
print "</TABLE>\n";
print "</BODY></HTML>";



Изображения ismap


После такой серьезной темы как обработка форм,я перейду к чему-нибудь веселенькому. Я познакомлю вас с изображениями ismap потому что это просто есть такой способ. Он поддерживается браузерами и естественно имеет право на жизнь.Хотя с приходом новых веяний в HTML (особенно Java-аплетов) он стал настоящей редкостью.И хот можно в 80% случаев найти ему более быструю замену,все-же вы можете в некоторых случаях найти именно ismap предпочтительней всего.
Синтаксис очень простой,почти не отличается от того,если бы вы решили оформить рисунок для якорь гиперссылки:

<A href="cgi-bin/somescript.cgi"><IMG src="somepic.gif" border=0 ismap></A>
Заметьте что все отличие заключается в том,что в тэге IMG добавлен атрибут ismap. Он говорит браузеру,что когда пользователь щелкнет на картинке то нужно перейти не просто к URL указаному в <A href="URL"> а что нужно к этому URL добавить координаты той точки по которой пользователь щелкнул мышью .
В нашем примере если пользователь щелкнул по точке x=10 ,y=15 то браузер перейдет на URL:
http://www.somehost.ru/cgi-bin/somescript.cgi?10,15
Т.е. координаты идут на скрипте в переменную QUERY_STRING ,их оттуда извлечь? Нет ничего проще:
($x,$y)=split(/,/,$ENV{'QUERY_STRING'});
Вот скрипт,который просто показывает координаты точки щелчка:
#!/usr/bin/perl
#ismap_xy.cgi
($x,$y)=split(/,/,$ENV{'QUERY_STRING'});
print "Content-Type: text/html\n\n";
print "<HTML><HEAD><TITLE>Ismap X Y</TITLE></HEAD>";
print "<BODY><H1>Вы щелкнули в точке: x=$x ,y=$y</H1></BODY></HTML>";
А что с ними делать дальше это уже чисто зависит только от вашей фантазии.Дайте ей ход и все у вас получится!.Очень часто ismap применяют для графического оглавления сайта. Когда щелкают на разные части рисунка,то переходят к разным страничкам сайта. Это легко реализуется,если скрипт выдаст нужный URL в Location: (Вспомните заголовок ответа CGI).
Вот пример и покажет это.Заготовьте файл urlmap.txt в котором будет информаци из строк в таком формате:
minx miny maxx maxy URL
где minx miny maxx maxy задают участок рисунка,а следующее за ними поле задает URL, которому этот участок соответствует.Пример:
1 1 20 50 http://www.uic.nnov.ru/~paaa/index_p.html
1 50 20 100 http://www.uic.nnov.ru/~paaa/projects.html
20 1 100 100 http://www.uic.nnov.ru/~paaa/cgi-bin/guestbook.cgi
Где нибудь на своей страничке воткните что-то вроде:
<A href="cgi-bin/testismap.cgi"><IMG src="gifs/doom2.jpg" border=0 ismap></A>
А сам скрипт testismap.cgi будет иметь вот такой простенький вид:
#!/usr/bin/perl
#testismap.cgi
$default_url="http://www.uic.nnov.ru/~paaa/";
		#URL по умолчанию,переходим к нему когда щелкнули
                #в участок,которому не сопоставлен URL
$url_map_file="urlmap.txt"; #файл с информацией об URL

($x,$y)=split(/,/,$ENV{'QUERY_STRING'});
open(F,"$url_map_file")|| print "Location: $default_url\n\n";
$url=$default_url;
foreach(<F>){
 chomp;
 ($minx,$miny,$maxx,$maxy,$URL)=split(/\s+/);
 if(($x>=$minx)&&($x<$maxx)&&
    ($y>=$miny)&&($x<$maxy)){$url=$URL;}
 }
close(F);
print "Location: $url\n\n";

Анимация


Когда говорят о каком-то популярном сайте,то частенько к преимуществам относят и анимацию. Действительно,когда изображение изменяется (и особенно к месту ;)),то это смотритс и пользователю нравится.
Говоря об анимации нужно сразу отметить что нет лучшего способа. Анимацию можно сделать ДЕСЯТКАМИ Способов,каждый хорош в своей области применения. Я перечислю только некоторые из них,которые чаще всего применяются:
Самый простой,но наименее функциональный способ это GIF с анимацией.
Потом можно воткнуть анимационный файл MPEG или AVI они более отражают суть анимации, но имеют недостаток,что для проигрывания их на некоторых браузерах нужны специальные подключаемые модули.К тому же они не интерактивны.
Можно реализовать анимацию в рамках Java-апплета,когда апплет находясь на страничке сам перерисовывается со временем.
Таким же интерактивным средством служит обращение к массиву document.images[] из JavaScript.Достоинство-помимо интерактивности,полная интегрированость с HTML -станичкой.Но может как и предыдущий использоваться только с относительно новыми браузерами,которые поддерживают Java и JavaScript.

В общем в каждом случае выбор остается за вами.Вам решать насколько тот или иной способ хорош в вашей ситуации.Я же познакомлю вас с еще одним.
Вы даже уже были знакомы с этим способом,когда я вам рассказывал о nph- скриптах Теперь когда вы уже так много знаете,можно модифицировать тот пример, добавив в него вызов картинки по случайному принципу:
#!/usr/bin/perl
#nph-animate2.cgi
$delay=3;
@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";
srand;
print "--myboundary\n";
while(1){
  $file=$files[int(rand($#files))];   #random file
  print "Content-Type: image/gif\n\n";
  open(PIC,"$file");
  print <PIC>;
  close(PIC);
  print "\n--myboundary\n";
  sleep($delay);
  }
Конечно одно из самых примитивных применений такой системы.Более мощным примером могло бы послужить отслеживание на сервере какого-нибудь периодически изменяющегося файла и пересылка пользователю обновленной версии.
Такая Система применяется например в Чате,при появлении новых сообщений. Чатовая система достаточно сложна для этого пособия и я не стал сюда ее включать.Однако,если вам очень интересно,то я с удовольствием пришлю ее вам.

Несколько советов по отладке


CGI-программы -не самые простые в отладке,по сложности отладки они способны сравнится лишь с отладкой драйверов. Вся сложность заключается в том,что скрипт выполняется не как обычная программа. Он выполняется в специальной среде сервера,которая создается при клиентском запросе, к тому же он исполняется не из под вашего аккаунта,а на непривилегированом уровне.
Если скрипт не исполняется потому,что вы допустили синтаксические ошибки,то самих этих ошибок вы не увидите,на экране будет только 'Internal Server Error' из-за чего она произошла вы можете только гадать. Также если вы забыли задать к какому-то файлу нужные права доступа ,то тоже будет трудно выяснить что же произошло и в чем причина ошибки (если конечно к этому вы не готовы).
Ну вот ,хватит вас пугать,тем более что нас не запугаешь ;) !
Приступим к отладке.Я вам опишу достаточно примитивные меры,которыми я сам пользуюсь.
Начнем с того что у нас есть скрипт test.cgi мы уже сделали его исполняемым chmod +x test.cgi Простейший способ проверить его на ошибки это команда perl -c test.cgi Ключ -c говорит Perl что надо только проверить синтаксис.Все сообщения об ошибках вы можете видеть и подправить.Более тяжелый случай состоит в том когда Perl встроен в Web -Сервер, причем версии разные.Как у до недавнего времени было на uic;(( ! Тот Perl с которым работаем в командной строке 4й версии ,а на сервере стоит 5й версии.Если ваша CGI-программа использует при этом какие-нибудь преимущества 5-й версии (например обьектно-ориентированые модули),то вы думаете отладить ее низ -ошибаетесь!.Только приготовтесь к тому, что я сейчас скажу,вы сядте,а то упадете ;)) :
Закоментируйте всю вашу программу ,т.е. перед каждой строчкой поставьте символ '#'. После чего,добавьте вот такие строчки: print "Content-Type: text/html\n\n"; print "<HTML>Test</HTML>"; exit; ,Должно получится так:
#!/usr/bin/perl
#test.cgi
print "Content-Type: text/html\n\n";
print "<HTML>Test</HTML>";
exit; #Программа как вы понимаете выполняется только до етого места
#
#if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'}}
#else{sysread STDIN,$query,$ENV{'CONTENT_LENGTH'};}
#if($query eq ''){
# @formfields=split /&/,$query;
# .......
# ........
А теперь запускайте скрипт.Естественно он выдаст Одно только слово 'Test'. Разкоментируйте несколько строчек.Еще раз запустите скрипт.Он опять выдаст 'Test'. Значит синтаксически эти только что разкоментированые строчки были правильные. И так далее....
Если очередной раз после раскоментирования вы запустили скрипт и получили 'Internal Server Error' - значит в этих строках содержалась какая-та синтаксическая ошибка. Это способ отловки синтаксических ошибок трудоемок,но к нему придется прибегнуть если ваш скрипт писан под ту версию Perl,что на сервере,а не под ту что у вас.
Узнать версию Perl можно perl -v
Ну вот мы отловили в нашем скрипте все синтаксические ошибки,он заработал, но это не значит,что он работает правильно.
Что еще можно посоветовать при отладке CGI-скриптов от ошибок возникающих во время выполнения программы. Допустим какой-то файл не открылся.Конечно показывать перепуганому пользователю эти технические подробности никчему,поэтому заведите себе специальный файл debug.txt и пусть ваши скрипты пишут в этот файл причины своих ошибок и сбоев, да и вообще о всех непредвиденых событиях.
Это можно реализовать так:
sub debug_err{
 open(DEBUGFILE,">>debug.txt");
 print DEBUGFILE $ENV{'SCRIPT_NAME'}.' '.scalar localtime.' '.@_."\n";
 close(DEBUGFILE);
 }
Примеры использования (Напомню,что встроеная переменная Perl $! содержит сообщение о причине последней ошибки,поэтому включайте ее всегда в свои сообщения):
open(F,"+<$myfile") || debug_err("Cannot open $myfile $!");
seek(F,0,0) || debug_err("Cannot seek $myfile $!");
connect(SOCKET,$paddr)|| debug_err("Cannot connect to $remote $!");
......
Потом можно периодически заглядывать в этот файл debug.txt и смотреть,какие ошибки встречались при работе ваших скриптов.Таким образом ваши скрипты сами помогать будут в своей отладке ;).

Также очень может оказать помощь (может и не оказать) просмотр http'шных логов. Все обращения к URL на сервере и все возникающие при этом ошибки идут в логи сервера httpd. Сразу хочу предупредить - размеры этих логов даже на средних размеров сервере достигает десятков мегабайт. Поэтому даже не пытайтесь их вот так просто посмотреть.- Лучше если вы уж за данное дело взялись - воспользуйтесь такими утилитами как grep,head,tail,more,less. .

Кстати я хочу сказать о причине еще одной (совсем не очевидной) ошибки.Если вы набрали скрипт у себя дома на компутере,то полученый скрипт состоит из текста в DOS'ом формате, а не в Unix'ом так что имейте это ввиду. Запускать вам его придется в системе Unix , так что следует перевести програмный текст в нужный формат.
Дело в том что в системах DOS и Windows (это уж очередной раз скажите все что вы думаете о Билле Гейтсе и Ко) для разделения строк используетс не один "символ новой строки" ("\n"),а пара символов "возврат каретки" ("\r") и "символ новой строки" ("\n"). Для простых HTML-файлов это не критично - браузеры игнорируют такие символы при выводе. Но скрипт является ПРОГРАММОЙ. А в программе никаких символов возврата каретки быть не должно!! Особенно в первой строке. Потому что когда операционная система запускает скрипт, она определяет какое приложение запустить для обработки данного скрипта именно по первой строке. В первой строке как вы знаете содержится #!/usr/bin/perl или #!/usr/local/bin/perl .Поэтому при запуске скрипта mysrcipt (это кстати вы можете сами увидеть коммандами top и ps) Система запускает комманду /usr/bin/perl mysript .А теперь представьте что будет если не убрать символ возврата каретки из Windows-файла. Получиться #!/usr/bin/perl<возврат каретки> и естественно что такого приложения нет, и следовательно попытка выполнить коммаду /usr/bin/perl<возврат каретки> mysript ни к чему не приведет!!!
Как с таким злом бороться я раскажу в моей следующей главе.

Trics and traps


Я так решил назвать эту часть,потому что это название больше всего соответствует.
Я вам в этой части расскажу о всяких тонких и неочевидных местах и о том как с этим боротся.
Продолжая тему отладки, я обнаружил что многие столкнулись с одной неочевидной проблеммой когда скрипты создают дома, помимо того, что надо перевести раскладку Windows (или DOS) в koi8-r скрипты все равно отказывались работать. Признаюсь ,хоть и разобрался в чем дело я быстро, причина весьма неочевидна: Дело в том,что текстовый файл Windows содержит перед символом перехода на новую строку еще символ возврата каретки. А вот из-за этого скрипт и отказывался выполнятся, что содержал этот символ!
Методом решения (этот процесс как и все я максимально автоматизирую) стал коротенький скрипт delcr .Просто "натравливайте" его на ваши скрипты:delcr *.cgi
#!/usr/bin/perl
#delcr
unless(@ARGV){die "Usage: delcr file ....\n";}
foreach $file(@ARGV){
 if(! -r $file || ! -w $file){print "$file: access denied\n";}
 else{
  open(F,"+<$file")|| die "Cannot open $file $!\n";
  binmode(F) || die "Cannot binmode $file $!\n";
  @D=<F>;
  seek(F,0,0);
  foreach(@D){
    s/\r//g;
    print F;
    }
  truncate(F,tell(F));
  close(F);
  }
 }
Саму же взаимную перекодировку Dos<->koi-8<->Win поможет выполнить вот такая прога. Написал я ее очень давно, даже раньше чем занялся CGI программированием и с тех давних пор она меня все время выручала, став моим по-настоящему незаменимым и верным другом. Итак, знакомьтесь: txtconv -Утилита перевода раскладок символов.
#include<stdio.h>
#include<string.h>
/*****************/
char tbldw[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
 0x5f,0x5f,0x5f,0xa6,0xa6,0xa6,0xa6,0x2b,0x2b,0xa6,0xa6,0x2b,0x2b,0x2b,0x2b,0x2b,
 0x2b,0x2d,0x2d,0x2b,0x2d,0x2b,0xa6,0xa6,0x2b,0x2b,0x2d,0x2d,0xa6,0x2d,0x2b,0x2d,
 0x2d,0x2d,0x2d,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x2b,0x5f,0x5f,0x5f,0x5f,0x5f,
 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff,
 0xa8,0xb8,0xaa,0xba,0xaf,0xbf,0xa1,0xa2,0xb0,0x95,0xb7,0x5f,0xb9,0xa4,0x5f,0x5f
};
char tblwd[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,
 0x5f,0x5f,0x5f,0x5f,0x5f,0xf9,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,0x5f,
 0x5f,0xf6,0xf7,0x5f,0xfd,0x5f,0x7c,0x15,0xf0,0x63,0xf2,0x11,0x2d,0x2d,0x72,0xf4,
 0xf8,0x5f,0x49,0x69,0x5f,0x5f,0x14,0xfa,0xf1,0xfc,0xf3,0x10,0x5f,0x5f,0x5f,0xf5,
 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef
};
char tbl_asc[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f
};
char tbldu[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0xE1,0xE2,0xF7,0xE7,0xE4,0xE5,0xF6,0xFA,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,
 0xF2,0xF3,0xF4,0xF5,0xE6,0xE8,0xE3,0xFE,0xFB,0xFD,0xFF,0xF9,0xF8,0xFC,0xE0,0xF1,
 0xC1,0xC2,0xD7,0xC7,0xC4,0xC5,0xD6,0xDA,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,
 0x90,0x91,0x92,0x81,0x87,0xB2,0xB4,0xA7,0xA6,0xB5,0xA1,0xA8,0xAE,0xAD,0xAC,0x83,
 0x84,0x89,0x88,0x86,0x80,0x8A,0xAF,0xB0,0xAB,0xA5,0xBB,0xB8,0xB1,0xA0,0xBE,0xB9,
 0xBA,0xB6,0xB7,0xAA,0xA9,0xA2,0xA4,0xBD,0xBC,0x85,0x82,0x8D,0x8C,0x8E,0x8F,0x8B,
 0xD2,0xD3,0xD4,0xD5,0xC6,0xC8,0xC3,0xDE,0xDB,0xDD,0xDF,0xD9,0xD8,0xDC,0xC0,0xD1,
 0xB3,0xA3,0x99,0x98,0x93,0x9B,0x9F,0x97,0x9C,0x95,0x9E,0x96,0xBF,0x9D,0x94,0x9A
};
char tblud[256]={
 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,

 0xC4,0xB3,0xDA,0xBF,0xC0,0xD9,0xC3,0xB4,0xC2,0xC1,0xC5,0xDF,0xDC,0xDB,0xDD,0xDE,
 0xB0,0xB1,0xB2,0xF4,0xFE,0xF9,0xFB,0xF7,0xF3,0xF2,0xFF,0xF5,0xF8,0xFD,0xFA,0xF6,
 0xCD,0xBA,0xD5,0xF1,0xD6,0xC9,0xB8,0xB7,0xBB,0xD4,0xD3,0xC8,0xBE,0xBD,0xBC,0xC6,
 0xC7,0xCC,0xB5,0xF0,0xB6,0xB9,0xD1,0xD2,0xCB,0xCF,0xD0,0xCA,0xD8,0xD7,0xCE,0xFC,
 0xEE,0xA0,0xA1,0xE6,0xA4,0xA5,0xE4,0xA3,0xE5,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,
 0xAF,0xEF,0xE0,0xE1,0xE2,0xE3,0xA6,0xA2,0xEC,0xEB,0xA7,0xE8,0xED,0xE9,0xE7,0xEA,
 0x9E,0x80,0x81,0x96,0x84,0x85,0x94,0x83,0x95,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,
 0x8F,0x9F,0x90,0x91,0x92,0x93,0x86,0x82,0x9C,0x9B,0x87,0x98,0x9D,0x99,0x97,0x9A
};
char tbluw[256]={
 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,

 0x2D,0xA6,0x2B,0x2B,0x2B,0x2B,0x2B,0xA6,0x2D,0x2D,0x2B,0x5F,0x5F,0x5F,0x5F,0x5F,
 0x5F,0x5F,0x5F,0xAF,0x5F,0x95,0x5F,0xA2,0xBA,0xAA,0x5F,0xBF,0xB0,0xA4,0xB7,0xA1,
 0x2D,0xA6,0x2B,0xB8,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0x2B,0xA6,
 0xA6,0xA6,0xA6,0xA8,0xA6,0xA6,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2B,0x2B,0x2B,0xB9,
 0xFE,0xE0,0xE1,0xF6,0xE4,0xE5,0xF4,0xE3,0xF5,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,
 0xEF,0xFF,0xF0,0xF1,0xF2,0xF3,0xE6,0xE2,0xFC,0xFB,0xE7,0xF8,0xFD,0xF9,0xF7,0xFA,
 0xDE,0xC0,0xC1,0xD6,0xC4,0xC5,0xD4,0xC3,0xD5,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,
 0xCF,0xDF,0xD0,0xD1,0xD2,0xD3,0xC6,0xC2,0xDC,0xDB,0xC7,0xD8,0xDD,0xD9,0xD7,0xDA
};
char tblwu[256]={
 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,
 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,
 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,

 0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,
 0x5F,0x5F,0x5F,0x5F,0x5F,0x95,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,0x5F,
 0x5F,0x9F,0x97,0x5F,0x9D,0x5F,0x7C,0x15,0xB3,0x63,0x99,0x11,0x2D,0x2D,0x72,0x93,
 0x9C,0x5F,0x49,0x69,0x5F,0x5F,0x14,0x9E,0xA3,0xBF,0x98,0x10,0x5F,0x5F,0x5F,0x9B,
 0xE1,0xE2,0xF7,0xE7,0xE4,0xE5,0xF6,0xFA,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,
 0xF2,0xF3,0xF4,0xF5,0xE6,0xE8,0xE3,0xFE,0xFB,0xFD,0xFF,0xF9,0xF8,0xFC,0xE0,0xF1,
 0xC1,0xC2,0xD7,0xC7,0xC4,0xC5,0xD6,0xDA,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,
 0xD2,0xD3,0xD4,0xD5,0xC6,0xC8,0xC3,0xDE,0xDB,0xDD,0xDF,0xD9,0xD8,0xDC,0xC0,0xD1
};
/*****************/
int convert(unsigned char *buff,unsigned char *Tbl,int count)
 {
 int i;
 for(i=0;i<count;i++)buff[i]=Tbl[(unsigned)buff[i]];
 return 0;
 }
/*****************/
char szHelp[]=
"Text file converter (c)lesha 1998\n"
"Usage:txtconv <options> <srcfile> <dstfile>\n"
"  options: -ud koi8->dos\n"
"           -du dos->koi8\n"
"           -uw koi8->win\n"
"           -wu win->koi8\n"
"           -dw dos->win\n"
"           -wd win->dos\n"
"           -? -This help\n";
int main(int argc,char *argv[])
 {
 FILE *f1;
 FILE *f2;
 char tmpbuff[1024];
 char *xtbl=NULL;
 int nr;
 if((argc>1)&&(strcmp(argv[1],"-?")==0)){printf(szHelp);return 0;}
 if(argc<4){printf(szHelp);return 0;}
 if     (strcmp(argv[1],"-ud")==0)xtbl=tblud;
 else if(strcmp(argv[1],"-du")==0)xtbl=tbldu;
 else if(strcmp(argv[1],"-uw")==0)xtbl=tbluw;
 else if(strcmp(argv[1],"-wu")==0)xtbl=tblwu;
 else if(strcmp(argv[1],"-dw")==0)xtbl=tbldw;
 else if(strcmp(argv[1],"-wd")==0)xtbl=tblwd;
 if(xtbl==NULL){printf("unknown option:%s",argv[1]);return 1;}
 if((f1=fopen(argv[2],"rb"))==NULL){perror(argv[2]);return 1;}
 if((f2=fopen(argv[3],"wb"))==NULL){fclose(f1);perror(argv[3]);return 1;}
 while((nr=fread(tmpbuff,1,sizeof(tmpbuff),f1))>0)
   {
   convert(tmpbuff,xtbl,nr);
   fwrite(tmpbuff,1,nr,f2);
   }
 fclose(f1);
 fclose(f2);
 return 0;
 }
Это еще одна утилита,соторая поможет вам в тяжелой реальности Интернета ;). Скомпилить ее можно под все три системы и используется она после этого очень легко, особенно когда всегда под рукой.
В общем что я могу сказать ,у вас на пути будет немало трудностей, но их можно всех преодолеть используя нехитрые приспособления.
Желаю вам удачи. А пока посмотрите на некоторые примеры приложений, возможно они вам будут полезны.

Примеры приложений:


Кто посещает мою страничку?


Вам иногда хотелось наверное узнать,кто же смотрит на вашу страничку,откуда и когда ваша страничка посещалась.
Бывают такие вопросы? Кто-то считает,что ответить на них нельзя. Но вы не верьте этому расхожему мнению.
Один раз с подобными вопросами ко мне подошел мой одногрупник, Диман. У него неплохой сайт. и туда к нему всегда валит целая куча народа. Вот как раз разговор и зашел об этой куче народа. Результатом моего непродолжительного труда стал небольшой скрипт.В страничку он втакаетс через тэг <IMG src="cgi-bin/get_ip.cgi"> он покажет вам изображение что не будет бросаться в глаза. Зато при своей работе он все запишет в файл ipdata.txt : В нем будет время и IP-адрес того,кто смотрел на вашу страничку!
#!/usr/bin/perl
#get_ip.cgi
$gif="../gifs/player.gif";
$data="ipdata.txt";
print "Content-Type: image/gif\n\n";
open(G,$gif);
print <G>;
close(G);
open(D,">>$data");
print D scalar localtime,' '.$ENV{'REMOTE_ADDR'}."\n";
close(D);

Гостевая книга


А вот еще пример того,как можно с умом использовать нехитрые знания. Гостевая книга ,в которую каждый может записать свое вам пожелание.
Запись происходит в базу данных guestbook.dat и при каждой новой записи в гостевую книгу скрипт извещает по почте хозяина гостевой книги, а тому кто в нее вошел по почте посылается сообщение об этом.
#!/usr/bin/perl
#guestbook.cgi
$myemail="paaa\@uic.nnov.ru";
$myname="lesha";
$mail="mail";
($sd,$sn)=($ENV{'SCRIPT_FILENAME'}=~/(.*)\/([^\/]*)/);
$datafile=$sd."\/guestbook.dat";
@Mailgifs=qw(../gifs/mood0.gif ../gifs/mood1.gif ../gifs/mood2.gif);
$Facetxt{$Mailgifs[0]}= ":)";
$Facetxt{$Mailgifs[1]}= ":|";
$Facetxt{$Mailgifs[2]}= ":(";

sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9a-hA-H]{2})/pack('C',hex($1))/eg;
 return $val;
 }
sub strhtm{
 local($val)=@_;
 $val=~s/&/&/g;
 $val=~s/</</g;
 $val=~s/>/>/g;
 $val=~s/(http:\/\/\S+)/<A href="$1">$1<\/A>/g;
 return $val;
 }
$cont_len=$ENV{'CONTENT_LENGTH'};
if($ENV{'REQUEST_METHOD'} eq 'GET'){$query=$ENV{'QUERY_STRING'};}
else {sysread(STDIN,$query,$cont_len);}
if($query eq ''){
  print "Content-type: text/html\n\n";
  print <<HTML_generating;
<HTML><HEAD><TITLE>Wellcome to my guestbook</TITLE></HEAD>
<BODY bgcolor="cyan">
<CENTER><H1>Wellcome to my guestbook</H1></CENTER>
<HR><FORM action="guestbook.cgi" METHOD="POST">
<TABLE border=0>
<TR><TD>Name:</TD><TD colspan=3><INPUT NAME="Name"></TD></TR>
<TR><TD>E-mail:</TD><TD colspan=3><INPUT NAME="Email"></TD></TR>
<TR><TD>URL:</TD><TD colspan=3><INPUT NAME="URL"></TD></TR>
<TR><TD>Message:</TD><TD colspan=3>
<TEXTAREA NAME="Message" rows=6 cols=64></TEXTAREA></TD></TR>
<TR><TD>Mood:</TD><TD><IMG src="$Mailgifs[0]"></TD><TD>
<IMG src="$Mailgifs[1]"></TD><TD><IMG src="$Mailgifs[2]"></TD></TR>
<TR><TD> </TD><TD><INPUT TYPE="radio" NAME="Mood" VALUE="$Mailgifs[0]"></TD>
                   <TD><INPUT TYPE="radio" NAME="Mood" VALUE="$Mailgifs[1]"></TD>
                   <TD><INPUT TYPE="radio" NAME="Mood" VALUE="$Mailgifs[2]"></TD></TR>
<TR><TD colspan=2><INPUT TYPE="submit" VALUE="Send"></TD>
    <TD colspan=2><INPUT TYPE="reset" VALUE="Clean"></TD></TR>
</TABLE></FORM>
<HR><BR>
HTML_generating
  open(DATAFILE,"$datafile")|| die "Cannot open $datafile $!\n";
  @GUESTDATA=<DATAFILE>;
  print @GUESTDATA;
  close(DATAFILE);
  print "</BODY></HTML>";
  }
else{
  foreach(@fields=split(/&/,$query)){
    if(/^Name=(.*)/){$Name=&urldecode($1);}
    if(/^Email=(.*)/){$Email=&urldecode($1);}
    if(/^URL=(.*)/){$URL=&urldecode($1);}
    if(/^Message=(.*)/){$Message=&urldecode($1);}
    if(/^Mood=(.*)/){$Mood=&urldecode($1);}
    }
  $MESSAGE=&strhtm($Message);
  if(-e $datafile){unless (-r $datafile && -w $datafile){die "Cannot access $datafile\n";}}
  $Newmsg="<IMG src=\"$Mood\"><BR><A href =\"mailto:$Email\">$Name</A>".
          "(<A href=\"$URL\">$URL</A>):<BR>\n$MESSAGE<HR>\n";
  open(DATAFILE,"+<$datafile") || die "Cannot open $datafile $!\n";
  @GUESTDATA=<DATAFILE>;
  @GUESTDATA=($Newmsg,@GUESTDATA);
  seek(DATAFILE,0,0);
  print DATAFILE @GUESTDATA;
  close(DATAFILE);
  print "Content-type: text/html\n\n";
  print "<HTML><HEAD><TITLE>Congratulations</TITLE></HEAD>\n";
  print "<BODY bgcolor=\"cyan\">\n<CENTER>";
  print "<H1>Congratulations:you have successfully entered to $myname\'s";
  print "guestbook.Thank you!</H1></CENTER><HR>$Newmsg</BODY></HTML>";
  open(MAIL,"|$mail $Email");
  print MAIL "Guestbook\n";
  print MAIL "You have entered to $myname\'s guestbook\n";
  print MAIL "Thank you.\n\t\t\t\t$myname";
  close(MAIL);
  format NOTIFYMAIL=
Guestbook
========================== Guestbook Entry =======================
| Time:                    |Name:                                |
| @<<<<<<<<<<<<<<<<<<<<<<<<|@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
scalar localtime,$Name
+--------------------------+-------------------------------------+
| Email:                   |URL:                                 |
| @<<<<<<<<<<<<<<<<<<<<<<<<|@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Email,$URL
+--------------------------+-------------------------------------+
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Message
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Message
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
$Message
| ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<    @<<<<< |
$Message,$Facetxt{$Mood}
==================================================================
.
  open(NOTIFYMAIL,"|$mail $myemail");
  write NOTIFYMAIL;
  close(NOTIFYMAIL);
  }


Счетчик посещений


Наверное тоже одним из часто встречающихся приложений CGI являются счетчики посещений. Они стоят практически на каждой страничке, возможно даже и у вас. Но иногда вас не устраивает тот факт, что счетчик лежит где-то в другом месте.Из-за этого скажем невозможно начать счет с произвольного числа.Или еще некоторые счетчики по разному фильтруют 'Reload'. Да и мало ли? Ну а иногда вам хочется просто сделать другой дизайн цифр. То если вы CGI-програмист то возможно имеет смысл написать свой счетчик. И делать с ним что захочется. Вот я так-же написал.....
Скрипт данного счетчика обслужевает несколько счетчиков ,им вы присваиваете идентификаторы. Поэтому вы спокойно можете втыкать независимые счетчики в разные страницы сайта и даже давать это делать друзьям. В общем он прост в использовании:<IMG src="cgi-bin/counter.cgi?id=name">, Где name -любое уникальное имя идентифицирующее счетчик.Вытакже можете задать необязательный параметр dig который задает количество цифр в счетчике ,Например:
<IMG src="cgi-bin/counter.cgi?id=doom2&dig=9">.
.gif'ы в счетчике с прозрачными областями.Что дает дополнительную гибкость к примеру для улучшения внешнего вида с помощью другого фона его иногда имеет смысл запихнуть в "таблицу":
<TABLE><TR><TD bgcolor="white"><IMG src="counter.gif"></TD></TR></TABLE>


Свои данные он пишет примерно в такой файл counter.dat:

doom2
4 127.0.0.1 906992351
quake2
1 127.0.0.1 906992700
quake
3 127.0.0.1 906992668
doom
1 127.0.0.1 906991960
Вы спросите,зачем столько информации? Чтобы отфильтровывать нажатия Reload. Если с одного IP-адреса между заходами промежуток меньше чем 30 секунд,то счетчик не инкрементируется (Так например поступает счетчик в Rambler'е).
Теперь об исходнике. Скрипт получился великоват,потому,что сдесь большую часть занимает генерация .gif - файлов.. Выглядит громоздко , зато пашет как трактор ;))!!
#!/usr/bin/perl
#newcount.cgi
###############
$LOCK_EX=2;
$LOCK_UN=8;
$datafile="counter.dat";
###############
$Dig[0]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[1]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x02\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[2]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x01\x01\x01\x02\x02\x02\x01".
"\x01\x01\x02\x02\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[3]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x01\x01\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[4]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x01\x02\x02\x01\x01\x01".
"\x01\x01\x02\x01\x02\x01\x01\x01".
"\x01\x02\x01\x01\x02\x01\x01\x01".
"\x01\x02\x02\x02\x02\x01\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[5]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[6]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[7]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x01\x01".
"\x01\x01\x01\x01\x01\x02\x01\x01".
"\x01\x01\x01\x01\x02\x01\x01\x01".
"\x01\x01\x01\x02\x01\x01\x01\x01".
"\x01\x01\x01\x02\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x01\x01\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[8]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x01\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
$Dig[9]=(
"\x01\x01\x01\x01\x01\x01\x01\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x02\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x02\x01".
"\x01\x02\x01\x01\x01\x01\x02\x01".
"\x01\x01\x02\x02\x02\x02\x02\x01".
"\x01\x01\x01\x01\x01\x01\x01\x01"
);
###############
sub urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;
 return $val;
 }
sub gifcompress{
 local($bmp)=@_;
 local(@Tbl);
 local($rootsize)=(8); #bits per pixel
 local($i,$bmp_i,$c,$k,$ck,$code,$tbl_i,$comp_size);
 local($cc,$eoi);
 local($bits)=('');
 local($RV)=('');
 $bmp_i=0;
 foreach $i(0..2**$rootsize-1){$Tbl[$i]=pack('C',$i);}
 $tbl_i=2**$rootsize+2;
 $cc=2**$rootsize;
 $eoi=2**$rootsize+1;
 $comp_size=$rootsize+1;
 $c='';
 $bits.=substr(unpack('b16',pack('S',$cc)),0,$comp_size);
 if($cc==(2**$compsize -1)){$comp_size++;}
 while($bmp_i<length($bmp)){
  $k=substr($bmp,$bmp_i,1);
  $ck=$c.$k;
  $code=-1;
  for($i=0;$i<$tbl_i;$i++){if($Tbl[$i] eq $ck){$code=$i;}}
  if($code!=-1){
    $c=$ck;
    }
  else{
    $Tbl[$tbl_i]=$ck;$tbl_i++;#add
    $code=-1;for($i=0;$i<$tbl_i;$i++){if($i!=$eoi&&$i!=$cc){if($Tbl[$i] eq $c){$code=$i;}}}
    $bits.=substr(unpack('b16',pack('S',$code)),0,$comp_size);
    if($code==(2**$compsize -1)){$comp_size++;}
    if($code==4095){$bits.=substr(unpack('b16',pack('S',$cc)),0,$comp_size);
    	foreach $i(0..2**$rootsize-1){
    		$Tbl[$i]=pack('C',$i);
    	};
    	$tbl_i=2**$rootsize+2;
    	$comp_size=$rootsize+1;$c='';
    }
    $c=$k;
    }
  $bmp_i++;
  }
 $code=-1;for($i=0;$i<$tbl_i;$i++){if($i!=$eoi&&$i!=$cc){if($Tbl[$i] eq $c){$code=$i;}}}
 $bits.=substr(unpack('b16',pack('S',$code)),0,$comp_size);
 if($code==(2**$compsize -1)){$comp_size++;}
 if($code==4095){$bits.=substr(unpack('b16',pack('S',$cc)),0,$comp_size);
 	foreach $i(0..2**$rootsize-1){
 		$Tbl[$i]=pack('C',$i);
 	};
 	$tbl_i=2**$rootsize+2;
 	$comp_size=$rootsize+1;$c='';
 }
 $bits.=substr(unpack('b16',pack('S',$eoi)),0,$comp_size);
 local($bytes)=('');
 for($i=0;$i<length($bits)/8;$i++){
  $bytes.=pack('b8',substr($bits,$i*8,8));
  }
 $RV=pack('C',$rootsize);
 for($i=0;$i<length($bytes)/255;$i++){
  $block=substr($bytes,$i*255,255);
  $RV.=pack('C',length($block));
  $RV.=$block;
  }
 $RV.=pack('C',0);
 return $RV;
 }

sub gengif2{
 local($Number,$digits,$c_r,$c_g,$c_b)=@_;
 local($Ascii_Num,$Zeropad);
 $Ascii_Num=''.$Number;
 $digits=($digits>length($Ascii_Num)?$digits:length($Ascii_Num));
 $Zeropad='0' x $digits;
 substr($Zeropad,- length($Ascii_Num),length($Ascii_Num))=$Ascii_Num;
 $Ascii_Num=$Zeropad;
 local($sym,$pos,$i);
 local($bmp)="\x00" x ($digits * 8 * 8);
 foreach $pos(0..length($Ascii_Num)-1){
  $sym=substr($Ascii_Num,$pos,1);
  foreach $i(0..7){
   substr($bmp,$i*$digits*8 + $pos*8,8)=substr($Dig[$sym],$i*8,8);
   }
  }
 local($g_x,$g_y);
 $g_x=$digits*8;
 $g_y=8;
 local($transp_index)=(1);
 local($RV)=('GIF89a');
 local($lscr)=(pack('SS',$g_x,$g_y).pack('B8','11110111').pack('C',0).pack('C',0));
 local($pal)=(pack('CCC',0x0,0x0,0x0).pack('CCC',0x7f,0x7f,0x7f).pack('CCC',$c_r,$c_g,$c_b).
         pack('CCC',0x7f,0x0,0x0).pack('CCC',0x0,0x7f,0x0).pack('CCC',0x0,0x0,0x7f));
 local($tmp)=(pack('C',0) x 768);
   substr($tmp,0,length($pal))=$pal;
   $pal=substr($tmp,0,768);

 local($gr_ext)=(pack('C',0x21).pack('C',0xf9).pack('C',4).pack('B8',
 '00001001').pack('S',0).pack('C',$transp_index).pack('C',0));

 local($imgdescr)=(pack('C',0x2c).pack('SSSS',0,0,$g_x,$g_y).pack('B8','00000000'));

 local($gifdata)=(&gifcompress($bmp));
 local($gifend)=(pack('C',0x3b));
 $RV=$RV.$lscr.$pal.$gr_ext.$imgdescr.$gifdata.$gifend;
 return $RV;
 }
######################
binmode(STDOUT);
$|=1;
#print "Content-Type: image/gif\n\n";
#print &gengif2($Number,$digits,$c_r,$c_g,$c_b);
#print &gengif2(1234567890,9,100,0,0);

$query=$ENV{'QUERY_STRING'};
if($query eq ''){print "Content-Type: image/gif\n\n";print &gengif2(1234567890,10,100,0,0);}
else{
 @fields=split(/&/,$query);
 foreach(@fields){
   if(/^id=(.*)/){$id=&urldecode($1);}
   if(/^dig=(.*)/){$dig=&urldecode($1);}
   }
 $digits=$dig;
 $digits=9 unless($dig);
 $cur_ip=$ENV{'REMOTE_ADDR'};
 $cur_time=time;
 open(DATA,"+<$datafile");
 flock(DATA,$LOCK_EX);
 @Dat=<DATA>;
 chop(@Dat);
 %Counters=@Dat;
 ($count,$ip,$t)=split(/\s+/,$Counters{$id});
 $count++ if(($ip!=$cur_ip)||($cur_time-$t>30));
 $ip=$cur_ip;
 $t=$cur_time;
 $Counters{$id}=join(' ',$count,$ip,$t);
 seek(DATA,0,0);
 foreach(keys %Counters){
  print DATA "$_\n";
  print DATA "$Counters{$_}\n";
  }
 truncate(DATA,tell(DATA));
 flock(DATA,$LOCK_UN);
 close(DATA);
 print "Content-Type: image/gif\n\n";
 print &gengif2($count,$dig,100,0,0);
 }
Если вам циферки не понравились вы их легко сможете заменить.



Вместо заключения


Вот и подошла к концу моя книга. Надеюсь что она найдет поддержку в широких слоях интернет-сообщества.В ней я постарался изложить простым и понятным языком немного из того, что я знаю. Очень надеюсь, что я достиг своей цели (если вы дочитали мое учебное пособие до конца).И я также я очень надеюсь, что те цели которые вы перед собой ставили при прочтении данной книги станут осуществимыми. Если вы прочитали эту книгу, и вам понравилось, порекомендуйте ее своим знакомым web-дизайнерам и начинающим интернет-программистам. Для них она будет очень полезна. Также жду ваших отзывов и предложений. Я постараюсь ответить на интересующие вас вопросы. Если вам данная тема понравится, то я обязательно подумаю над тем, чтобы написать продолжение, в котором я расскажу о тех вопросах, с которыми сталкивается профессиональный интернет-программист в своей работе.
И еще раз хочу дать совет и пожелание: если что-нибудь у вас не получается, не огорчайтесь и не опускайте руки, всегда есть способ сделать любое, пусть даже с первого взгляда сложное дело. Тем более в наших руках такая мощная вещь как интернет, и от нас зависит, каким он будет через месяц, через год, через два, через десять лет. И если вы действительно приложите усилия , то ваш сайт станет достойным всеобщего восхищения.


Литература по CGI