Дмитро Ковальов
Більшість того, що можна написати про Перл, буде справедливим для будь-якої (або хоча б для більшості) систем, в яких Перл працює. Але те, як виконувати програми написані на Перлі, у більшості випадків буде специфічним для тієї, або іншої системи.
Я маю досвід роботи з двома версіями (напрямками) Перлу. а саме - Перл для Юнікса (власне, ориґінальний Перл) і Перл для Макінтоша (MacPerl). MacPerl є дещо специфічним і взагалі заслуговує на окремий розгляд, а інші версії Перла (такі, як MS-DOS, Windows тощо) мене особисто не цікавлять.
Тому, вважаючи на написане в попередньому абзаці, надалі мова йтиме тільки про використання Перлу в тій або іншій версії Юніксу, або його похідних (таких, як наприклад Лінакс).
Тож, перш, ніж намагатися виконувати програму в Перлі, було б непагано для початку впевнитися, що він встановлений в даній системі. В цьому допоможе команда 'which'. Якщо з вашою системою (а більше навіть не з системою, а з середовищем користувача) все в порядку, то ви побачите на екрані щось подібне до наступного:
dk@sophy $ which perl /usr/bin/perl dk@sophy $
В трьох приведених рядках частина написана ліворуч від знаку долара: 'dk@sophy $' є системним запрошенням, або як його інколи називають системою підказкою і може бути (напевне буде) відмінним, від того, що ви побачите на своєму екрані. Другий рядок є результатом роботи команди 'which', і є маршрутом (доріжкою) до програми 'perl'.
Якщо Перл не встановленій у вашій системі, або якщо програма 'which' не може його знайти, то в залежності від тих або інших факторів ви можете побачити щось подібне до одного з наступних варіантів:
(1)
dk@sophy $ which perl dk@sophy $
(2)
dk@sophy $ which perl which: no perl in (/usr/local/qt/bin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:) dk@sophy $
(3)
dk@sophy $ which perl perl: Command not found. dk@sophy $
В першому варіанті програма 'which' не знайшла прграму 'perl' і вирішила не розстроювати користувача цим фактом і тому, просто зробила вигляд, що не чула запитання. У варіантах 2 і 3 'which' подала звіти про результати своїх пошуків дещо різні за своєю змістовністю. Але у всіх трьох варіантах нас більше всього цікавить саме той сумний факт, що програма Перл не може бути знайденою в цій системі і наше навчання відкладається на деякий час.
Прошу читача звернути увагу на деякі нюанси: я написав "не може бути знайденою" на відміну від набагато більш категоричного "не встановлена". Може бути так, що програма встановлена в системі, але не дивлячись на це 'which' не може її відшукати. Це може бути наслідком дуже багатьох причин, основною серед яких є різні нестандартні маршрути до програми. З варіанту 2 видно, що 'which' шукає протрібну користувачеві програму в певних директоріях. Якщо Перл встановлено в директорії, яка не входить до "поля зору" 'which', то зрозуміло, що Перл не буде знайдено.
Допомогти відшукати Перл в системі можуть інші системні засоби, такі як, наприклад, 'find', 'locate', 'rpm' (дві останні програми характерні для Лінакса більше, ніж для інших Юніксів, причому остання з них ++ тільки для Лінаксів, що базуються на RedHat подібній системі - такій як, сам RedHat, Caldera, Mandrake, TurboLinux, тощо).
Подальші пошуки перлів в файлових системах залишимо у вигляді вправи для допитливого користувача. Якщо Перл в системі не встановлено, то вихід з цієї ситуації дуже простий - його треба встановити. І я дуже радий заявити, що я знімаю з себе відповідальність за це, бо це виходить з поля зору даного твору і за допомогою в цьому випадку треба звертатись до системних адміністраторів.
Тож вважаємо, що невеличка вправа з програмкою 'which' закінчилася успішно і перейдем далі. А саме до того, як отримати трошки більше інформації про свій Перл.
Важливою інформацією про Перл є його номер версії. В сучасному світі існують дві версії Перлів - версія 4 та версія 5. Кожна з них поділяється на більш дрібні підверсії (тобто 4.036, 5.003, 5.005_05 тощо), але на ці дрібниці ми не будемо звертати уваги ++ оскільки основні відмінності лежать в перших розрядах версії.
Для більшості вправ навіть відмінність між версією 4 та 5 буде несуттєвою, але все-таки краще знати, що такі відмінності існують.
Щоб дізнатися, яка версія Перлу встановлена, надрукуйте таку команду в командному рядку: "perl -v". І ось що при цьому трапляється:
dk@sophy$ perl -v This is perl, version 5.005_02 built for i586-linux Copyright 1987-1998, Larry Wall Perl may be copied only under the terms of either the Artistic License or the GNU General Public License, which may be found in the Perl 5.0 source kit. Complete documentation for Perl, including FAQ lists, should be found on this system using `man perl' or `perldoc perl'. If you have access to the Internet, point your browser at http://www.perl.com/, the Perl Home Page. dk@sophy$
Зрозуміло, що в першому рядку цього повідомлення саме і вказується версія Перлу. Зрозуміло також, що отримавши від користувача параметр "-v", Перл не намагається виконувати ніякої програми, а просто друкує на екрані номер версії і закінчує роботу.
Для найпростішої програми в Перлі вам не потрібно буде редагувати ніяких файлів, не потрібно компілювати, відладжувати, тощо. Ми створимо і виконаємо вашу першу програму прямо зараз: в той час, як ви це читаєте.
По конях! До командного рядка!
META різниця між шел і редактором
Надрукуйте цю дуже просту штуку в командному рядку:
dk@sophy$ perl -e 'print "Hello! I have written my first program! \n"' Hello! I have written my first program! dk@sophy$
Зверніть увагу на різні лапки (одинарні та подвійні) в цьому прикладі. Вони важливі, як і їх порядок.
Написання програм в Перлі просто в командному рядку без створення файлів не є якимось рідкісним трюком чи звихненням або ж педагогічним прийомом. Є багато випадків, коли написати одну-дві команди в Перлі, щоб "виловити" якусь корисну інформацію з файла є набагато простішим, ніж відкривати текстовий редактор і записувати файл (навіть якщо текстовий редактор є vi або emacs), а потім виконувати цей файл.
Давайте подивимось трохи уважніше на щойно написану та виконану програму.
Параметр "-e" вказує Перлу, що після цього йде сама власне програма, а не назва файлу, в якій програма записана. Далі, записана в одинарних лапках іде текст програми. В нашому випадку це:
print "Hello! I have written my first program! \n"
Одинарні лапки потрібні для того щоб ізолювати внутрішній текст від програмної оболонки (shell), в якій ви працюєте. Інакше програмна оболонка буде намагатися виконати команди власноручно ті команди, що призначені для Перла. Не у всіх випадках використовуються одинарні лапки. Використання лапок відноситься скоріше до відомства shell'а, ніж до Перла. Тому, щоб зрозуміти краще, що відбувається з цими лапками, зверніться до документації по тій оболонці, в якій ви працюєте (тобто sh, csh, tcsh, bash, ksh чи ще що-небудь). Як бачите Юнікс забезпечує можливість вибору в цьому відношенні.
Внутрішні лапки (подвійні в нашому випадку) відносяться вже до самого Перла. В них записується текст, який повинна вивести на екран команда 'print'. (Зрозуміло, що команда 'print' щось повинна друкувати - інакше звідки би взялася ця назва? )
В кінці рядка ще записана якась дивина: "\n". Ця штука завжди використовується, коли треба розбити рядок на кілька рядків, або як кажуть серйозні люди, тобто програмісти: "Вставити символ кінця рядка". Кінець рядка не повинен стояти в кінці того рядка, що ви друкуєте. Ви його вставляєте там де вам потрібно. Якщо ви трохи пограєтеся з вашою улюбленою програмою на Перлі, то може бути таке:
dk@sophy$ perl -e 'print "Hello! \n I\n have wri\ntt\nen my first program! \n"' Hello! I have wri tt en my first program! dk@sophy$
Для того, щоб створити найпростіший файл з програмою в Перлі теж не дуже багато потрібно. Що нам треба - це всього-навсього текстовий редактор. (Ось зовсім забув, чи я казав, що для всих цих вправ потрібен також комп'ютер? Якщо ні, то потурбуйтеся про це також, поки ще не пізно.)
Тож в вашому улюбленому редакторі... Немає такого? Тоді беріть emacs,
і не помилитесь. Деякі більше люблять, звичайно vi , але я не буду
займатися тим, щоб відлякувати читачів з самого початку. Любов до vi
приходить не зразу. Люди, які користуются ним для програмування,
належать до особливого класу людей. Вони ніколи в житті не клацнуть
двічі мишкою на піктограмі Microsoft Word'а. Навіть до користувачів
emacs'у вони ставляться з деякою поблажливістю, як доросла людина
робить сердитий вигляд, при виді капризуючої дитини, але при цьому
тихенько посміюється собі у вуса. Користування vi навіть цілком
врівноважену людину може довести
до сліз. Тому наш вибір падає на emacs .
Якщо ви як і ми вирішили спинитися на emacs'і, то наступний ваш командний рядок буде таким:
dk@sophy$ emacs myperl.pl
Я перейняв на себе відповідальність визначити назву файлу вашої Перл-програми. Але замість "myperl.pl" ви можете вказати, що вашій
душі ближче та рідніше. Вибираючи назву файлу, що закінчується на "pl" (або як кажуть маститі - має розширення "pl" (*) ) ви маєте шанс ввімкнути в emacs'і режим Перлу.
emacs має різноманітні режими для підтримки (**) багатьох мов програмування, в тому числі і для підтримки Перл. Але я не можу гарантувати, що цей режим ввімкнеться в вашому редакторі автоматично. emacs'и можуть відрізняти файли один від одного або за розширеннями, або ж за першим рядком файлу. Пролог (теж мова програмування) теж користується розширенням "pl", тому деякі версії emacs'ів можуть розпізнати цей файл як пролог-програму і вмикнути режим Пролог замість Перл (або ж emacs може бути не настроєним зовсім і не вмикнути зовсім ніякого режиму).
Вмикнути режим Перл у вашому emacs'і можна так: натисніть та відпустіть Esc і після цього натисніть `x' (латинська літера x). Після цього в самому нижньому рядку emacs'а ліворуч з'явиться напис `M-x'. Це запрошення emacs'а до вводу команди. Надрукуйте в цьому рядку `perl-mode' і натисніть Enter .
Далі, в залежності знову ж таки від конфігурації вашої системи, а точніше вашого emacs'у, ви маєте два варіанти - або Перл-режим вмикнеться або ж ні. Ознакою того, що він вмикнувся, буде слово `(Perl)' , написане в рядку статусу emacs'у (другий рядок знизу - найчастіше він позаний на екрані в інверсному кольорі). Дізнатися що режим Перлу знайдено можна по повідомленню No match надрукованому в командному рядку emacs'а.
Якщо ви належите до тієї категорії людей, яким не везе з встановленими пакетами і програмами, і режим Перл все-таки не встановлений у вашій системі, зверніться за допомогою до системного адміністратора. Але не зважаючи на невезучість ви все-одно можете продовжувати редагувати вашу програму.
Файл програми буде складатися з одного єдиного рядка. Тож напишіть вже знайомий вам рядок в emacs'і:
print "Hello! I have written my first program! \n"
і запишіть файл. Цей рядок не має вже подвійних лапок, як не має і самого 'perl -e'. Виконання програми трохи відрізняється від попереднього варіанту. В цьому випадку щоб виконати програму поверніться знову в програмну оболонку і надрукуйте в командному рядку команду:
dk@sophy$ perl mytest.pl Hello! I have written my first program! dk@sophy$
Як бачите з командного рядка зник параметр '-e' і по цьому Перл визначає, що те, що йде після слова perl є назвою файлу, який треба виконати, використовуючи інтерпретатор Перл. Результат роботи програми, як бачите, не змінився.
Все, що писалося до цього моменту, відносилося як до Юнікса, так і до інших систем, що мають командний рядок (MSDOS або MPW у Макінтоші для прикладу). Але на цьому схожість закінчується і ми входимо в джунглі чистісінького Юніксу. Те, про що йдеться далі, можливо в Юніксі і тільки в Юніксі. Написані цілі керівництва щодо того, як зробити так, щоб в Windows NT програму, написану на Перлі, можна було-би запускати так, як це робиться в Юніксі.
Ми ж, не гаючи часу на дурниц,і переходимо зразу до справи. Чому не можна виконувати програму так, як ми це вже робили в попередній вправі? Можна, але не завжди зручно. Можливо ви захочете наприклад переписати свою стару програму (або як кажуть "скрипт") в мові shell або awk, і зробити її програмою в Перлі. Якби не було можливо вказати, яка програма використовується для виконання того, чи іншого скрипту, то кожного разу довелося б вказувати конкретно це в командному рядку. Тобто програма в Перлі завжди повинна запускатися "perl program", скрипт в мові sh завжди б запускався як "sh program". А що, якщо я хочу написати скрипт, який би запускався просто як "program"? Що мені робити?
Спробуємо запустити таким чином наш скрипт. Друкуємо:
dk@sophy$ ./mytest.pl ./mytest.pl: Permission denied. dk@sophy$
Допитливий Читач:
Гм-м-м, по-перше, нащо тут крапка з косою? А по-друге що таке "Permission", і чому воно "denied"?
Автор:
Д.Ч.:
А.:
Треба зробити файл вашої програми здібним до виконання (executable). Той, хто має досвід роботи з MSDOS та його похідними Windows'ами, знає, що в потойбічному світі розширення файлу (те, що йде в імені файлу після крапки) визначає, чи може виконуватися цей файл чи ні. Виконуються тільки файли з розширеннями com, exe та bat. В світі Макінтошів належність файлу до програм визначається типом файлу (який записується в ресурсній гілці файлу. Той, хто не знає і не здогадується, що таке "ресурсна гілка" нехай сприйме це як ще один факт від якого немає ніякої користі і якими так переповнене наше життя). Щоб система виконувала файл, як програму (могла передати на нього управління - говорячи по-розумному), файл повинен мати тип APPL.
В Юніксі ж все визначається дозволами (eng. permissions) на файл. Щоб система виконала файл, як програму, він повинен мати дозвіл на виконання. І не просто дозвіл, а дозвіл для певного користувача. Не дуже вдаючися у подробиці дозволів та різниці в правах користувачів, зазначу зразу, що команда для встановлення потрібних для виконання дозволів буде така:
chmod +x mytest.pl
Деяка розшифровка: остання частина команди означає файл, якому встановлюються дозволи, chmod походить від "change mode", а +x означає, що треба додати дозволи на виконання до цього файлу. Після виконання цієї команди, виконувати наш скрипт буде дозволено будь-кому. Можливо також селективне встановлення дозволів в залежності від приналежності користувача до тієї або іншої групи. За подальшою інформацією дивіться "man chmod".
Нетерплячий Читач:
"Ну що, встановили дозволи, то-ж давай виконувати!.."
dk@sophy$ chmod +x mytest.pl dk@sophy$ ./mytest.pl ./mytest.pl: print: command not found dk@sophy$
Н.Ч.: "Га? Що далі?"
А.: Далі -- Правило друге:
Мало того, щоб Юнікс знав, що файл можна виконата, ще йому треба вказати, як саме виконувати вашу програму. В Юніксі є для цього засіб, який найбільш відомий як "magic number", або чарівне число. (Майже те-ж саме, що і чарівне слово - коли ви хочете запалити, то мало дозволити перехожому пригостити вас цигаркою, ще треба і сказати "Будь-ласка"). Це чарівне число - два перших байти (дві перших літери) файлу. Прочитавши два цих перших байти Юнікс завжди може сказати до якого типу належить цей файл. Два перших байти будь-якого скрипту (будь-то скрипт у Перлі, sh, csh чи ще чому завгодно -- наприклад навіть такому екзотичному скрипті як gnuplot -- системі для графічного зображення числових даних) повинні бути "#!" (без лапок, звичайно).
Побачивши цих два магічних байти, система почне придивлятися до файлу уважніше, щоб визначити, який інтерпретатор їй використовувати.
До речі: Всі командні оболонки, всі ці sh, csh, ..sh є інтерпретаторами, Перл також є інтерпретатором, правда трохи іншого типу. Його можна назвати компілюючим інтерпретатором (чи може інтерпретуючим компілятором? А взагалі, яка різниця?!). Тобто, замість того, щоб читати по одному рядку і виконувати цей рядок, як роблять більшість командних оболонок, Перл читає зразу весь файл, компілює його весь, а вже потім виконує. Це значно збільшує швидкість роботи скриптів у Перлі.
Тож в першому рядку файлу повинно бути записано, яким інтерпретатором повинна користуватися система.
Але звідки ж буде знати користувач, що треба записати в цьому першому рядку? Тут саме і час знову згадати про команду which. Пам'ятаєте, як там було (в успішному варіанті):
Я: "Which perl?"
Вона: "/usr/bin/perl"
Саме оцей рядок, разом із чарівним словом треба написати в першому рядку програми. Після цього наша програма виростає аж вдвічі, замість одного рядка в ній стає два:
#!/usr/bin/perl print "Hello! I have written my first program! \n"
Що станеться, якщо програміст забуде написати магічне слово, або ж допустить помилку в написанні повного рядка до Перл-інтерпретатора?
В Юніксі є домовленість, що якщо не вказана повна адреса інтерпретатора, то Юнікс вважає, що цей скрипт написаний для найпростішої командної оболонки - для /bin/sh В нашому випадку це означає, що /bin/sh намагатиметься виконати команду print , якої немає в мові sh , і звичайно видає помилку про це.
Якщо ж в маршруті до інтерпретатора допущена помилка (або, наприклад, якщо ви на Інтернеті знаходите програма, яку так довго шукали. А ту програму написав хтось, у кого Перл був встановлений в зовсім іншому місці), то повідомлення про помилку буде зовсім іншим. Щоб не бути голослівним, давайте проведем дослід.
Для початку давайте впевнимося, що наша новенька програмка працює так, як нам цього хочеться:
dk@sophy$ ./mytest.pl Hello! I have written my first program! dk@sophy$
Так... добре! Тепер змініть перший рядок, так щоб в ньому було написане щось зовсім інше. Наприклад таке:
#!/USR/BIN/PERL
До речі:
Для тих, хто ще не звик до Юнікса, нагадаю, що всі назви файлів в Юніксі відрізняють великі і малі літери. І тому #!/USR/BIN/PERL це зовсім не те, що #!/usr/bin/perl, і навіть не те, що #!/Usr/bin/perl.)
Виконаємо ту-ж саму команду, що і перед цим. І...
В залежності від того, в якій оболонці ви в даний момент знаходитесь ви можете отримати різні відповіді:
csh:
dk@sophy$ ./mytest.pl ./mytest.pl: Command not found. dk@sophy$
bash:
dk@sophy $ ./mytest.pl bash: ./mytest.pl: No such file or directory dk@sophy $
sh:
sh$ ./mytest.pl sh: ./mytest.pl: No such file or directory sh$
Чому системні команди такі, наприклад, як ls, echo, startx та інші можна виконувати без всяких крапок та косих, що йдуть перед назвою файлу? І чому, коли я працював в ДОСі, я міг створити BATCH-файл "MYFILE.BAT" і просто надрукувати в командному рядку "MYFILE" і воно працювало? Чому всі ці складнощі з Юніксом? Чому, якщо файл знаходиться осьо тут, прямо в мене перед очима, в цій директорії, в якій я зараз є, чому я повинен перед іменем файлу писати ще якісь дурниці? Ось мій файл "mytest.pl", чому я не можу написати "mytest"? Чому я не можу написати "mytest.pl"? Чому я обов'язково маю писати "./mytest.pl"?
Як виявляється всі ці дурниці, як здається на перший погляд, не є зовсім дурницями, а мають свій великий потаємний зміст. Обидві системи (ДОС та Юнікс) мають поняття про таку річ, як змінна середовища PATH , яка є просто переліком директорій, в яких система веде пошук команд для виконання. Але використання цієї змінної суттєво відрізняється в двох системах.
По-перше , про те в чому відрізняються ДОС і Юнікс?
Коли в ДОСі користувач друкує команду в командному рядку, система починає шукати цю команду. Послідовність пошуку така:
До цього додаються ще деякі варіації щодо розширень файлу. Тобто, коли користувач друкує назву команди без розширення, то файли "*.COM" будуть пріоритет перед файлами "*.EXE", тощо.
Оскільки Юнікс не обмежується трьома (чи навіть п'ятьма) розширеннями файлів, які можуть бути програмами, то, звичайно-ж, треба завжди вказувати повну назву файлу, разом з розширенням.
В Юніксі послідовність пошуку програми * має трохи інший вигляд:
Як бачите відсутня частина з пошуком в даній директорії. Чи можна зробити так, щоб система все-таки шукала файл в даній директорії? Можна. Для цього треба додати крапку (".") в маршрут пошуку - тобто в PATH . Але про це йдеться в...
По-друге : чому цього не варто робити?
Для безпеки. Перш за все. Помічено давно, що параноїки сплять спокійніше від оптимістів. Бо на всіх дверях у них висять величезні запори, на вікнах - ґрати, а самі віконниці позабивані трьохдюймовими цвяхами.
Мати крапку в змінній PATH вважається (і справедливо вважається) дуже небезпечно. Розгляд цього питання аж занадто далеко відходить від програмування в Перлі, тому я зупинюся просто на констатуванні цього факту без будь-яких пояснень, а перейду до пояснення того, як зробити так, щоб ваша програма виконувалася і щоб вам не треба було кожного разу друкувати назви директорій в командному рядку.
Можливі два підходи до цього.
Перш за все - а як дізнатися, які директорії записані в змінній PATH . Виконайте для цього в командній оболонці команду:
dk@sophy$ echo $PATH /usr/local/qt/bin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin dk@sophy$
Назви директорій відділяються одна від одної двокрапками. Якщо ви маєте дозвіл на запис в одну із директорій виданих командою echo, то просто можете скопіювати свій файл в цю директорію. Якщо ж всі директорії недоступні для запису, то залишається варіант з модифікацією PATH . Команда для цього змінюється в залежності від того, яким shell'ом ви користуєтесь. Тому, зразу кілька варіантів. Для конкретності візьмемо директорію /tmp/bin і додамо її до змінної PATH .
csh та його похідні ( tcsh ):
setenv PATH $PATH:/tmp/bin
sh та його похідні ( ksh , bash , ash ):
PATH=$PATH:/tmp/bin export PATH
В bash та ksh можливий також дещо коротший варіант:
export PATH=$PATH:/tmp/bin
Ці зміни пропадуть в наступній сесії (після того, як ви вийдете з системи і ввійдете в неї знов). Тому, щоб зробити ці установки постійними ці команди треба додати до одного з конфіґураційних файлів вашого shell'у. Для csh (tcsh) це буде файл .login або .cshrc в домашній директорії. Для sh (ksh, zsh, bash, ash) це - файл .profile. bash також має кілька інших файлів, які визначають середовище користувача. Ці файли - [[.bashrc, .bash_login, .bash_profile]]. Але відмінності між всима цими файлами краще пояснені в man bash .
А на закінчення цього розділу все таки не можу втриматися від ще однієї шпильки на адресу Віндовсів. Чому, знаючи про явний провал в безпеці, ДОС все-таки дозволяє виконувати файли в поточній директорії? Та тому що, так чи інакше ви будете перевстановлювати систему (якби вона не називалась: DOS, Window NT, 3.1, 95, 98, 2000, 3000 ... 15000 - не дай Боже дожити до такого (!) ) в середньому двічі на місяць. Тому яка різниця від чого вона загинула - чи від "." в PATH, чи від того, що MS Word'у не сподобалось те, що ви надрукували, а чи ще від чого?
На цьому розділ про виконання програм написаних у Перлі можна вважати закінченим. Дуже мало в цьому розділі йшло мови власне про програмування в Перлі. Розділ стосувався (як можливо хтось дуже уважний помітив), про загальні вимоги до будь-якого скрипту в Юніксі, не тільки скрипту написаного в Перлі. Але розуміння основ виконання програм повинно передувати написанню програм. Ви ж не збираєтесь писати програми, які не виконуються?
А хто вже ці основи знав, той все одно нічого не втратив, бо я не можу повірити в те, що він марнував свій власний час на ще одне перечитування.
Цей розділ я закінчу підсумуком тільки-що написаного.
В цьому розділі я спробую підійти трохи ближче до програмування в Перлі. Ми вже більше не будемо розглядати питання про те, як написані нами програми повинні виконуватися. Не будемо звертати уваги також і на те, чи має програма в першому своєму рядку " #!/usr... ". Якщо результатом роботи скрипту все ще є "Command not found", то моєю єдиною порадою будео ще раз уважніше перечитати попередній розділ.
Мабуть зрозуміло, що в середньому програми на Перлі складаються з більше, ніж однієї команди. Тому, для того, щоб інтерпретатор розумів, де закінчується одна команда і починається інша, повинні існувати деякі правила, які визначають межі команд.
В Перлі використовується поняття "блоку", яке є трохи ширшим, ніж команда. Блоком може бути, як одна одинока команда (яка називається "оператором"), так і деяка логічно поєднана послідовність операторів. Кожен блок повинен закінчуватись крапкою з комою ";". І блок, який складається з більше, ніж одного оператора, береться у фігурні дужки:
{ ... ... деякий_перл_оператор; ... інший_перл_оператор; ... ще_один_перл_оператор; ... };
Оскільки оператор є не що інше, як блок, який складається з одного оператора, то в подальшому я не буду робити великої різниці між обома поняттями "оператор" та "блок", і обидва терміни будуть вживатися рівноправно, крім особливих випадків, коли така різниця суттєва. Але в таких випадках це завжди буде оговорюватися особливо.
Фраза про те, що _кожен_ блок повинен закінчуватися крапкою з комою є, взагалі-то, невірною. Якби це було так, то наша програма з першого розділу не працювала б. Вірніше буде сказати, що "блоки повинні відділятися один від одного крапкою з комою". З цього випливає, що останній оператор в програмі не обов'язково має мати крапку з комою в кінці. Те ж стосується і останнього оператора в блоці. Але поставити її не буде великою помилкою.
Практично всі мови програмування мають в собі ту чи іншу форму коментарів. Коментарем вважається шматок програми, на який компілятор чи інтерпретатор не звертають ніякої уваги. Застосовуються коментарі тільки для зручності програмістів, які мають потім читати цей код. Гарно відкоментовану програму завжди легше зрозуміти.
В Перлі для коментарів використовується знак "#". Все що йде після цього символу до самого кінця рядка вважається коментарем і ігнорується Перлом.
Зважаючи на все сказане ми можемо переписати нашу першу програму у такому вигляді:
#!/usr/bin/perl # This script will print short message on the screen # and exit after this. # Оскільки коментарі ігноруються, то їх можна писати будь-якою мовою print "Hello!"; # Результат роботи цього скрипту такий же, як і попереднього print " I have written my first program! \n"
І виконавши його ми отримаємо:
dk@sophy$ ./mytest.pl Hello! I have written my first program! dk@sophy$
Всі мови програмування оперують змінними. Деякі з мов мають також поняття констант. Константа - це така змінна, яка не є змінна (значення якої встановлюється один раз і не змінюється в процесі роботи програми). Тобто, це практично одне і те ж. Тому Перл не має констант. А змінну можна уявити як деяку комірку в яку можуть вкладатися ті або інші дані.
Більшість мов програмування мають поняття про типи даних. Перш, ніж користуватися якоюсь змінною, треба визначитися, до якого типу належить ця змінна. Тобто, якщо ви будете оперувати цілими числами, то відповідна змінна повинна бути цілого типу. Якщо вам потрібно оперувати текстовими даними, то змінні повині мати тип символу або рядка. В загальному випадку, не дозволяться в одній операції використовувати дані різних типів. (Тобто, це більше схоже на запитання: "Що буде, якщо до п'яти центнерів додати 2 кілометри?"
В різних мовах існують різні підходи до визначення та оперування різними типами даних. В деяких мовах всі дані потрібно декларувати перш, ніж використовувати. В інших декларувати дані не потрібно, але все визначається префіксами до назв змінних, тощо. В багатьох мовах програмування перш, ніж оперувати даними різних типів, їх треба перетворити в інший тип. Наприклад, звичайно-ж з точки зору здорової логіки, можна додати ціле число 5 і десятковий дріб 0,78. Але для такої операції (наприклад в Паскалі) потрібно спочатку 5 перетворити із цілого числа в десятковий дріб 5,0. Значення числа при цьому не змінюється, але змінюється тип змінної. І після такої операції вже обидва доданки стають десяктовими дробами, над якими дозволяється виконувати арифметичні дії.
Різні мови програмування відрізняються одна від одної також щодо операцій перетворення даних. Деякі з них вимагають завжди обов'язкового явного перетворення типу даних. Тобто не можна просто написати "5 + 0,78", а обов'язково треба написати щось подібне до "(дробове число, числове значення якого дорівнює 5) + 0,78". Інші мови розуміють, в деяких випадках, що до чого ви хочете додати, і роблять деякі перетворення, покладаючись на правила перетворень даних.
Перл в цьому відношенні особливий. Типів даних в розумінні інших ("дорослих") мов прогрмування в Перлі немає. Є один-єдиний скалярний тип даних в Перлі, який покриває всі необхідні типи: цілі числа, дробові числа, символи та рядки. І всі необхідні перетворення робляться Перлом неявно, непомітно для програміста і робляться вони в залежності від контексту, в якому це перетворення трапляється.
Крім скалярного типу даних Перл має також масиви скалярів та асоціативні масиви, про які буде трохи далі. Всі ці три типи даних відрізняються один від одного префіксами.
Скалярний тип даних в Перлі позначається префіксом "$". Тому, побачивши написане $a= 5 або $b= 'one' , ви не помилитесь, якщо скажете, що обидва ці вирази присвоюють значення не масивам, а скалярним змінним. Правда, якщо в другому випадку, можна з великою долею ймовірності сказати, що $a є текстовим рядком, то в першому випадку нічого певного сказати не можна.
Подивіться уважніше на наступні приклади (для деякої лаконічності я буду надалі опускати системні запрошення в командному рядку):
perl -e '$a=5; $b="one"; print $a + $b, "\n"' 5 perl -e '$a=5; $b="one"; print $a . $b, "\n"' 5one
В першому випадку змінна $a трактується, як числова змінна (бо виконується операція додавання) і змінна $b, не є числовою, тому сума дорівнює першому доданку. В другому прикладі виконується операція над двома текстовими рядками * . Тому обидві змінні трактуються в цьому випадку, як текстові змінні і ви бачите результат цього.
Можливі навіть зовсім дикі ситуації, коли в одній частині скрипту одна і та ж змінна трактується по одному, і в іншій частині - по іншому . В наступному прикладі змінна $a в одному випадку виступає як текстова змінна, а в іншому як числова.
perl -e '$a=5; $b="one"; $c=3; print $a . $b, "\n", $a + $c ,"\n"'; 5one 8
Або ще один, навіть більш дивний, варіант, в якому до результату конкатенації двох текстових змінних додається число. Тобто, спочатку до п'яти кілометрів додається ще шість кілометрів, і потім результат складається з двадцятьма коровами:
perl -e '$a=5; $b=15; $c=3; $d =$a . $b; print $d, "\n", $d + $c ,"\n"'; 515 518
В самому першому прикладі я звертав увагу на те, що порядок використання лапок - важливий. Давайте трохи зупинимось на цьому питанні.
Перш за все - навіщо взагалі використовувати лапки? Чи не простіше просто написати що-небудь типу:
print Hello! I have written my first program! \n
Написати можна, але самі можете впевнитися, що працювати це не буде. Так само, як пишучи твора, ми виділяємо в творі пряму мову, так само потрібно виділяти текстові епізоди в програмі. Текст, взятий в лапки є прямою мовою в Перл-програмі.
Ну, з цим зрозуміло, це була проста задача. Але як бути з оцими різними типами лапок: з одинарними (''), та з подвійними ("") ?
Проведемо деякі дослідження:
$a=" шматок тексту "; $b=" ще один шматок "; print $a . $b, "\n"; print "$a $b ","\n"; print '$a $b ',"\n"; ./mytest.pl шматок тексту ще один шматок шматок тексту ще один шматок $a $b
Що відбувається? Перший варіант зрозумілий: просто конкатенуються дві текстові змінні і результат цього об'єднання друкується. Другий рядок схожий на перший, але в ньому є зайвий пропуск, але третій варіант суттєво відмінний від обох попередніх.
Почнемо з третього варіанту, бо з ним розібратися простіше. Все взяте в одинарні лапки друкується так, як воно написане, тобто, не зважаючи на те, що всередині лапок записане щось таке, що за виглядом нагадує скалярні змінні - $a $b . Але написане всередині лапок трактується не як змінні, а як просто текст, який друкується буквально. Інтерпретатор Перла "не бачить" того, що знаходиться всередині одинарних лапок.
В другому варіанті (подвійні лапки) все, що лежить всередині лапок, є видимим для Перла - подвійні лапки "прозорі". І Перл підставляє замість змінних їх значення, або як ще кажуть Перл розширяє значення змінних.
Це стосується не тільки оператора print , але і будь якої іншої операції в Перлі. Значення в подвійних лапках завжди розширяються:
$a=" шматок тексту "; $b=" ще один шматок "; $c = "$a плюс $b"; $d = '$a плюс $b'; print $c, "\n"; print $d, "\n"; ./mytest.pl шматок тексту плюс ще один шматок $a плюс $b
Все, про що йшлося в останніх абзацах, носить назву "цитування" - quoting. І справді, все це дуже схоже на те, як в тексті вставляються цитати. Чи є ще інші правила цитування, крім двох вже описаних правил з подвійними та одинарними лапками? Є, ще одне. Коли потрібно захистити від розширення один єдиний символ, перед ним ставиться зворотня коса (або backslash) - "\". Наступний приклад демонструє це правило:
$a=" шматок тексту "; $b=" ще один шматок "; print "$a плюс $b"; print "$a плюс \$b"; ./mytest.pl шматок тексту плюс ще один шматок шматок тексту плюс $b
Але ситуація з розширенням зворотьої косої насправді дещо складніша, ніж з розширенням змінних. Всередині одинарних лапок вона розширюється. В наступному прикладі зворотня коса "маскує" одинарну лапку, і через це Перл видає помилку.
perl -e "print '\'"
Can't find string terminator "'" anywhere before EOF at -e line 1.
Крім описаного вже типу скалярних даних (які, як ми бачили, можуть в Перлі бути чим завгодно) Перл має ще масиви скалярних даних та асоціативні масиви скалярів.
META * масиви * асоціативні масиви - hash
Питання роботи з файлами насправді є трохи ширшим, ніж просто перелік операцій для того, щоб прочитати файл на диску або щоб створити новий файл чи дописати щось до вже існуючого. В Перлі (особливо в тих системах, які підтримують командний рядок -- тобто не Macintosh -- та стандартні дескриптори файлів, такі як STDIN , STDOUT , STDERR -- тобто не Windows) питання роботи з файлами включає в собі такі операції, як виконання системних команд, перехоплення виводу від них та його використання, відкриття системних каналів (pipe) із Перл-програми, тощо. Якщо Ви не дуже розбираєтесь у термінолоґії, яка тут так часто вживається, ми спробуємо з цим трохи далі розібратися, а про все, що не зрозуміло з пояснення в цій книжці, доведеться почитати в книжці по Юніксу.
Але що то таке - STDIN ? Це є скорочення від англійського "standard input" і означає "стандартний ввід". Тепер вже зрозуміло, що "standard output" або STDOUT - це стандартний вивід, а "standard error" - це STDERR , який за неможливістю відшукати більш пристойного українського еквіваленту, я перекладаю як "стандартний пристрій для повідомлення помилок". Розуміння правил роботи з цими пристроями є суттєвим для розуміння роботи більшості програм у Юніксі, оскільки абсолютна більшість програм, написаних в "юніксівському стилі", вміють читати дані з STDIN , передавати результати своєї роботи на STDOOUT та повідомляти про помилки в роботі на STDERR .
Оскільки поняття "файл" використовується в Юніксі дуже часто, ми говоритимо далі про стандартний ввід, вивід та пристрій для повідомлення помилок, як про звичайні файли. І в своїй суті всі ці троє дуже мало відрізняються від звичайних файлів (тобто файлів на диску). Відмінністю з точки зору програміста є тільки те, що всі ці троє є вулицями з одностороннім рухом. Із STDIN можна тільки читати, в STDOUT та STDERR можна тільки писати.
Перш, ніж братися до вивчення програмування з застосуванням нових для нас понять, давайте спробуємо розібратися з ними на простих прикладах. Розглянемо дуже просту команду cat (1) . Можна було-б описати цю команду, як таку, яка роздруковує один або кілька файлів на екрані дисплея. І це буде вірно до деяких пір. Так, наприклад, команда
cat myfile.txt myfile2.txt
дійсно не зробить нічого надприроднього. Вона просто надрукує файли з назвами myfile.txt та myfile2.txt на екрані один за одним. Але якщо змінити цю команду на таку:
cat myfile.txt myfile2.txt > myfiles.txt
то все зміниться дуже швидко. Замість того, щоб друкувати файли на екрані, ця команда зіллє два файли і запише їх обидва в третій файл із назвою myfiles.txt . Якщо такого файлу не існує, команда його створить, а якщо він існує, то замінить його новим.
якщо Вашою робочою оболонкою є csh або tcsh cat myfile.txt myfile2.txt > myfiles.txt myfiles.txt: File exists або якщо Ви працюєте в bash 'і cat myfile.txt myfile2.txt > myfiles.txt bash: myfiles.txt: cannot overwrite existing file
Це означає, що Ви пробуєте переписати командою cat вже існуючий файл. При цьому змінна `noclobber' встановлена в Вашій командній оболонці. Існують два можливих шляхи вирішення цієї проблеми:
Перший - відшукати де саме в Ваших стартових скриптах ( .profile , .login , .bashrc чи .cshrc ) встановлюється ця змінна і відмінити цю установку (Установка має такий вигляд: set noclobber або set -o noclobber ),
або Другий - використати такий синтаксис команди:
cat myfile.txt myfile2.txt >| myfiles.txt
Саме тут і виявляється, що визначення команди cat , як такої, що роздруковує файли на екрані, яке було дане всього кілька абзаців тому вже по суті не вірне. Але в роботі команди нічого не змінилося. Отже проблема скоріше з нашим визначенням. Тому вірним визначенням команди буде таке: "Команда cat конкатенує файли, які вказуються в командному рядку і роздруковує їх на стандартний вивід." Просто в першому випадку стандартним виводом був дисплей системи, а в другому - файл на диску. Стандартним виводом для команд є дисплей комп'ютера, якщо не вказано інакше. В тому випадку, коли особливо сказано, що вихідні дані мають бути записаними у файл (тобто ">file" , командна оболонка перебирає на себе функції по підтриманню такого файлу та пересиланню у нього даних від команди.
Відмітьте: Записом даних у файл займається не сама команда, а командна оболонка ( shell ). Тому, аналоґічно працюють всі команди Юнікса. Тож варто весь час пам'ятати, що ці команди друкують не на екрані, а на STDOUT .
Інші дві команди допоможуть нам трохи розібратися з STDIN та STDOUT разом. Розглянемо для початку таку команду, як bc(1) . Документація до bc визначає його, як мову калькулятора з довільною точністю. Якщо Ви ніколи до цього не користувалися цією командою, проведемо коротенький екскурс в основи користуванням нею. Коли Ви просто в командному рядку надрукуєте bc , Ви потрапляєте в інтерактивний режим роботи калькулятора. Тут Ви можете виконувати арифметичні дії довільної точності і складності над (Увага!) цілими числами. Ви просто набираєте дії на клавіатурі, тиснете Return і маєте результат. Ось, наприклад, що я маю на своєму екрані:
$ bc bc 1.05 Copyright 1991, 1992, 1993, 1994, 1997, 1998 Free Software Foundation, Inc. This is free software with ABSOLUTELY NO WARRANTY. For details type `warranty'. 2+2 4 5/2 2 quit $
Я просто додав 2 і 2 ( 2+2 Return ), а потім розділив 5 на 2 ( 5/2 Return ).
В такому простому (звичайному калькуляторному) режимі використання команда сприймає дані із STDIN (яким в цьому випадку є клавіатура комп'ютера) і передає результати роботи на STDOUT (як ми вже знаємо з попередньої дискусії, ним є екран комп'ютера). Але Юнікс би не був Юніксом, якби все було так просто - є собі програма калькулятора, рахуєш в ньому що тобі потрібно, записуєш результат на папірці і після цього з папірця переписуєш у текстовому редакторі цифірки туди, де вони потрібні. Ні! В Юніксі люди поводять себе інакше. Коли мені, скажімо, треба перевести IP-адресу, виражену в десятковій формі у шістнадцяткове її подання, я займусь тим, що напишу скрипт, який буде виконувати все однією командою. На написання скрипту у мене піде вп'ятеро більше часу, ніж на обчислення цих даних за допомогою калькулятора і папірця, але ж у мене буде готовий скрипт, який я зможу використати наступного разу, коли мені потрібно буде зробити таке саме перетворення. Одна тільки біда - навряд чи мені коли-небудь знадобиться переводити десятковий IP в шістнадцятковий...
Тож, як написати скрипт (поки-що не Перл, а звичайний шел), який би переводив числа із однієї системи числення в іншу? Для цього, як Ви вже мабуть зрозуміли, треба зайнятися перенаправленнями STDIN та STDOUT туди-сюди. Тобто, треба всі ті команди, які ми друкуємо на клавіатурі, подати на STDIN команди bc . Зробити це можна якоюсь іншою командою, або просто записати у файл. Ми виберемо перший - командний - шлях. Невеличкий експеримент:
$ echo "2+2" | bc 4
Працює! Тепер треба було-б і кілька команд bc підучити... Зробимо таке:
$ echo "obase=16; 127" | bc 7F
Команда obase в bc встановлює вихідну систему числення, тобто те в якому вигляді калькулятор видає результати обчислень. Неважко здогадатися, що ibase в такому випадку визначає вхідну систему числення. І тому наступний приклад переводить число із двійкової системи у вісімкову:
$ echo "obase=8; ibase=2; 11110100010101000101" | bc 3642505
Тож ми вже повістю готові до того, щоб написати наш знаменитий скрипт. Втн буде мати такий вигляд:
#!/bin/sh echo "obase=16; $1" | bc
Оце і все? Так, все... А працює він так:
$ ./h2d 20 14 $ ./h2d 100 64 $ ./h2d 15 F $ ./h2d 225 E1
Як все це сказане про екран та STDOUT співвідноситься із програмуванням на Перлі? Дуже просто. Оператор print Перла друкує все, що його просять, не на екрані, а на STDOUT , крім тих випадків коли вказано інакше. Саме ті випадки, коли вказано інакше, ми і будемо розглядати далі.
Повна форма оператора print ( man perlfunc ) має наступний вигляд:
print FILEHANDLE LIST
В цьому визначенні LIST є тим списком даних, що має надрукувати оператор. До цього моменту ми не вживали FILEHANDLE і не знаємо, що це таке. Ми обмежимся тим, що будемо розглядати FILEHANDLE як саму звичайну змінну спеціального типу, яка пов'язує файл, в який потрібно виводити дані з його назвою. Зауважте, FILEHANDLE - це не назва файлу (так саме, як, наприклад, STDOUT - теж не є назвою файла)! Це - всього лиш деякий символ, який зв'язує файл із зовнішнім світом, така собі "ручка від файлу" (саме так можна перекласти з англійської "filehandle").
Навіщо потрібен FILEHANDLE , якщо можна було б просто надрукувати щось подібне до:
print "/home/user/MyFile.txt" "Hello"
Причина цього таж сама, що і існування змінних в мовах програмування. Набагато зручніше оперувати змінними в програмах, ніж їх значеннями.
Перш, ніж у файл можна буде надрукувати будь-що, його треба відкрити. Після закінчення виводу у файл, його треба закрити. Хоча ця остання операція і необов'язкова - після виконання скрипту, всі відкриті файли автоматично закриваються. З точки зору програміста операція відкриття файлу - це просто операція, яка зв'язує назву файлу із відповідним FILEHANDLE'ом.
Добре, якщо кожен файл потрібно відкривати перш, ніж писати у нього, то чому ж тоді ми нічно не говорили раніше про те, що треба відкрити STDOUT коли ми прекрасно обходилися без цього в наших попередніх скриптах? Виявляється, що є ще деякі речі, якими STDIN та STDOUT відрізняються від звичайних файлів. Відміна заключається в тому, що STDOUT не треба відкривати перед записом у нього - він автоматично відкритий при старті будь-якого Перлівського скрипта. Теж саме стосується і STDIN - він також автоматично відкривається при старті інтерпретатора Перла. Тому то ми можемо без будь-яких зайвих слів просто написати print "Hello" і ця операція виконається.
Тож розібравшись трохи з тим, що треба відкривати, а що не треба, ми можем на деякий чам забути про те, як саме відкривати файли. Замість цього ми можемо зайнятися вивченням використання тих файлів, які відкривати не потрібно - тобто STDIN та STDOUT .
Про STDOUT вже писалося вище. Поки що ми не можемо додати чогось суттєво нового до того, що вже відомо. Тому давайте займемося дослідженням STDIN . Які існують способи використання цього файлу, який так послужливо відкритий для нас самим Перлом?
Мабуть це буде - найпростіший варіант використання STDIN у Перл скрипті. Хоча, відмітимо зразу ж, і не самий розповсюджений, а також у більшості випадків і не рекомендований для використання через деякі не зовсім улагоджені відносини цього оператора з правилами безпеки.
Коли ми в перший раз починаємо цікавитися тим, що таке STDIN , STDOUT та STDERR при вивченні Юнікса? Саме тоді, коли нам потрібно виконати, щось на зразок того, що ми нещодавно проробили із командою cat - перехопити STDOUT від команди і записати його в файл. Або коли нам потрібно перехопити STDOUT і передати його на STDIN іншої команди (мова йде про pipe або про канали в Юніксі), як, наприклад у такому випадку:
ls | wc -l
Що нам робити, коли ми хочемо використати деяку команду Юнікса ізсередини програми, написаної в Перлі? Тут на допомогу приходить оператор `` (зворотні апострофи). Синтаксис оператора дуже простий:
`<деякі команди Юнікса>`
Оператор виконує весь рядок, вказаний між двома зворотніми апострофами у командній оболонці /bin/sh або аналоґічній до неї і повертає весь STDOUT назад до Перла. Як, напевне вже помітив Допитливий Читач, тут нічого не говориться про STDERR , і вірно - цей оператор просто ігнорує STDERR , тому якщо Ви дійсно хочете мати також і STDERR у своєму скрипті, то Ви самі маєте виконати певні діі для цього. Далі ми розглянемо, що саме потрібно для цього робити. А поки-що розберемся з тим, як нам використовувати вивід від команди.
Вигляд, у якому дані повертаються назад до Перлу залежить перш за все від того, як використовується оператор ``. Найпростіший варіант - Вам начхати на те, що саме повертає цей оператор. Тобто, це може бути тоді, коли Ви на 100% впевнені в тому, що оператор виконається без помилок і Вас дійсно не хвилює, що може видати цей оператор.
Але ми, все-таки, розглянемо, як воно працює...
# touch myprog.log # -rw-r--r-- 1 root root 0 Jan 6 18:55 myprog.log # perl -e "`mv /var/log/myprog.log /var/log/myprog.log.0`" # -rw-r--r-- 1 root root 0 Jan 6 18:55 myprog.log.0
Цей простенький shell-діалог демонструє найпростіше використання ``. (Хоча для такого використання, мабуть і не варто було б користуватися Перлом, але ми робимо це для навчання). Перлівський оператор в цьому випадку просто змінює назву файлу за допомогою команди mv (1) . Наступний скрипт робить тіж самі дії, але він переіменовує кілька реєстраційних ( log ) файлів. Скрипт здійснює так звану ротацію лоґ-файлів, яка використовується дуже часто практично в усіх сучасних Юніксах.
#!/usr/bin/perl `mv /var/log/myprog.log.3 /var/log/myprog.log.4`; `mv /var/log/myprog.log.2 /var/log/myprog.log.3`; `mv /var/log/myprog.log.1 /var/log/myprog.log.2`; `mv /var/log/myprog.log.0 /var/log/myprog.log.1`; `mv /var/log/myprog.log /var/log/myprog.log.0`;
Розглянемо такий дуже простий скрипт:
#!/usr/bin/perl while (<>) { print }
Що він робить? Та майже нічого... Тобто майже нічого корисного в плані практичного його використання. Але зате він нам допоможе розібратися з тим, що таке STDIN
Meta:
Чи пробували ви коли-небудь виправляти помилки в надрукованому на друкарській машинці тексті? Нічого складного в цій процедурі немає. Берете пляшечку з чудовою білою рідиною і авторучку. Акуратно замальовуєте рідиною всі ті місця, де в слові "головнокомандуючий" пропущені скрізь одні й ті ж самі дві літери, а ручкою дописуєте те, що потрібно. Нічого складного! Справді? Але, що якщо це злополучне слово трапляється в тексті 156 разів? Під кінець цієї процедури ви вже забудете, які літери вам треба вписувати. Саме для такої роботи і були створені текстові редактори. Друкуєте в командному рядку emacs і запускаєте автоматичний пошук і заміну, і вже через 2 хвилини можете віднести віддрукований звіт в штаб округу.
Складніше, якщо ви працюєте не в військовій комендатурі. Помилки в цих випадках бувають більш різноманітними, і вам треба прикласти значні зусилля щоб їх виправити. Скажімо таке: всі назви місяців в тексті повинні бути написані не скороченими, а вони написані як завгодно - і "Січень", і "Січ.", і навіть "Січ". Почнете змінювати всі "Січ" на "Січень" і "Січень" після такої операції перетвориться у вас на "Січеньень". Як бути? Тут прийдуть на поміч регулярні вирази. В нашому випадку потрібно замінити на "Січень" всі випадки слова "Січ", в якому після "ч" іде або пропуск " ", або крапка ".", і ігнорувати випадки коли після "ч" іде "е". Оце і є зразок регулярного виразу. (До речі, дуже простого регулярного виразу. Реальне життя приносить задачі набагато складніші, ніж ця.)
Тим, хто хоч раз працював з командним рядком (будь-де чи то в ДОСі, чи у Юніксі), регулярні вирази знайомі. Підстановка зірочки (*) та знаку запитання (?) є зразком найпростіших регулярних виразів. В командній оболонці Юнікса (практично в будь-якій з них) зірочка означає "будь-яка послідовність будь-яких символів", а знак запитання означає один будь-який символ. Тому, якщо вам, наприклад, потрібно дізнатися назви всіх файлів, що починаються на "l", ви можете дуже просто надрукувати: ls l*
Теж саме стосується і закінчень файлів. Щоб дізнатися, які файли закінчуються на "z": ls *z (з однією відчутною різницею між ДОСом та Юніксом: останній приклад в ДОСі не працює. В ДОСі зірочка означає фактично "будь-яка послідовність літер від цього місця до кінця рядка").
Тож, звернімося тепер до найпростіших регулярних виразів у Перлі.
Подібно до зірочки та знаку запитання в командній оболонці, в Перлі є такі ж вирази для визначення "будь-якого одного символа" та "будь-якої послідовності символів". Але в Перлі це робиться інакше - будь-який символ позначається крапкою ".", а після крапки ставиться так-би мовити "множник". В найпростішому випадку таким множником є таж сама зірочка, що і в командній оболонці, і тут вона означає те ж саме - "будь-яка кількість". Тобто, сказане в цьому абзаці означає, що ".*" визначає будь-яку послідовність будь-яких літер.
Перш, ніж іти далі давайте розберемося як користуватися регулярними виразами в Перлі.
Можуть бути різні застосування для них, але, мабуть, два основних це такі: по-перше, ви захочете дізнатися чи входить той чи інший рядок, який описується регулярним виразом у текст (програма має крикнути: "Знайшов!", коли натрапить на такий вираз) і, по-друге, знайшовши вираз, ви схочете замінити його на щось таке, що вам більше до вподоби.
Наступний шматок коду демонструє наше перше застосування регулярних виразів:
#!/usr/bin/perl #### A. $a = "anyky "; if ($a =~ m/a.*/) { print $a; } #### B. $b = "benyky "; if ($b =~ m/a.*/) { print $b; } #### C. $c ="iily varenyky "; if ($c =~ m/a.*/) { print $c; }
А результат його виконання такий:
dk@sophy $ ./mytest.pl anyky iily varenyky dk@sophy $
Давайте спробуємо розібратися з анатомією цього коду. Анатомія - це, як відомо, копання у внутрощах. Давайте і ми спробуємо розібрати один із регулярних виразів і відділити в ньому мухи окремо, а котлети окремо. Для конкретності будемо розглядати один з трьох виразів, а саме той, що йде на початку:
if ($a =~ m/a.*/) { print $a; }
Тим, хто знайомий з програмуванням, безперечно здасться знайомою форма if (...) {...} , але ж наша книжка для непрограмістів. Тому невеличке пояснення: if разом з двома групами дужок - круглими та фігурними становить одну форму. Ця форма носить назву умовного оператора, і з назви її повинно бути зрозумілим, що, перш, ніж виконувати якусь дію, умовний оператор перевіряє певну умову - чи треба цю дію виконувати взагалі, чи можна її спокійно проіґнорувати. В нашому прикладі ми користуємося найпростішою формою умовного оператора. Ця форма має такий вигляд:
if ( <умова> ) { <блок> }
Якщо умова записана в круглих дужках справедлива, то тоді виконується блок операторів, записаний у фігурних дужках після цього. Блок, який записується у фіґурних дужках після умови часто називають тілом умовного оператора.
Умовою виконання блоку є справедливість чи несправедливість операції над регулярним виразом в круглих дужках. Якщо операція дає в результаті "істину", то виконується "<блок>" ( в нашому випадку оператор 'print'). Якщо ж в результаті істина не виходить, то і 'print' не виконується.
Операція, яка здійснюється над регулярним виразом, в свою чергу складається з трьох частин. Ці частини такі (розглядаємо випадок A.):
ліва частина - "$a", середня частина - "=~", права частина - "m/a.*/".
Середня частина в цьому виразі є оператором, який з'єднує скалярний вираз, вказаний в лівій частині з операцією над регулярним виразом, вказаним в правій частині.
Саме права частина виразу і є одним з тих регулярних виразів, про які ми зараз говоримо. Але сам регулярний вираз є тільки частиною цієї третьої (правої) частини. В нашому конкретному випадку регулярним виразом є "a.*". І означає цей вираз: "літера a, після якої йде будь-яка кількість будь-яких літер".
Операція над регулярним виразом позначається літерою "m" та двома "нібито-дужками", в які заключено регулярний вираз. Тобто:
"<операція>/<регулярний-вираз>/".
Літера "m" позначає операцію пошуку (або порівняння) в регулярному виразі. Походить ця літера від слова "match". Перл, побачивши такий вираз проводить порівняння зразку тексту (який стоїть ліворуч від оператора "~=") із регулярним виразом, який стоїть праворуч від оператора. Якщо знайдена відповідність, то Перл кричить (ненашою мовою): "Match!" і швиденько кидається друкувати те, що іде у фіґурних дужках.
Якщо спробувати оце "match" виразити українською, то найбільше підійде слово: "Знайшов!". Саме оце "знайшов" виражає тут результат "істина". Протилежний до нього результат виражається в українській як: "Та не переживай ти! Всяке буває!". А коли "всяке буває", оператор 'print' не виконується.
Поглянувши пильніше на результат виконання скрипту з регулярними виразами, помітите, що виконалися два блоки цього скрипту, а саме: блок A. та блок C. Дві змінні $a та $c мають в собі літеру "a". У змінній $b її немає, тому і оператор print не виконувався в цьому блоці.
Перл широко відомий серед програмістів своїми скороченнями та спрощеннями. Один і той же регулярний вираз в Перлі можливо записати багатьма різними способами (кожен наступний з них коротший від попереднього). Інколи така лаконічність приводить до спрощення написання програми у Перлі, інколи - навпаки, але практично завжди це приводить до того, що програму в Перлі неможливо прочитати.
В цьому підрозділі ми спробуємо скоротити і так короткий скрипт, який використовувався в попередньому підрозділі (звичайно ж намагаючись робити так, щоб він все-таки продовжував працювати).
Таку надзвичайно складну штуку, як одна літера, в Перлі можна замінити простішою штукою - нічим. Два таких вирази тотожні в Перлі: m/a.*/ та /a.*/ . А якщо два вирази тотожні, то зрозуміло, що кожна нормальна людина з двох виразів, які приводять до одного й того ж результату, вибере той вираз, який коротший. І це є причиною того, що в більшості скриптів ви будете бачити " " і не побачите "m ".
З урахуванням цього наш скрипт перетворюється на ось таке:
#!/usr/bin/perl $a = "anyky "; if ($a =~ /a.*/) { print $a; } $b = "benyky "; if ($b =~ /a.*/) { print $b; } $c ="iily varenyky "; if ($c =~ /a.*/) { print $c; }
Простіше? Так. Але не набагато. Йдемо далі...
Інколи буває не дуже зручно користуватися стандартними "дужками", в які береться регулярний вираз - "//". Наприклад, якщо ви робите пошук в тексті блоків із косими всередині. Для того, щоб ізолювати ці косі від механізму пошуку існують різні засоби. Але коли таких "ізоляторів" занадто багато, буває так, що регулярний вираз перетворюється на щось таке, що прочитати неможливо. І у таких випадках було б непагано мати щось на заміну для стандартних дужок.
Такою заміною є ... практично будь що. Але тільки в тому випадку, коли використовується форма m// (літера m перед дужками обов'язкова). Заміною для // може бути будь-який не алфавітно-цифровий і не нульовий символ. Всі наступні вирази будуть тотожними:
if ($a =~ m/a.*/) { print $a; } if ($a =~ m:a.*:) { print $a; } if ($a =~ m~a.*~) { print $a; }
Крім цього мається ще один варіант запису цього ж самого виразу, в якому використовуються фіґурні дужки:
if ($a =~ m{a.*}) { print $a; }
В попередніх абзацах ми конкретно вказували з якою змінною ведеться порівняння регулярного виразу. Але в дуже багатьох випадках ці конкретні змінні можна замінити на одну спеціально передбачену для цього змінну, яка і служить спеціально для того, щоб провадити різні операції над текстовими даними і регулярними виразами. Ця змінна в Перлі записується, як $_ і вживається наступним чином.
Цій змінній можна присвоювати будь-які значення, так саме, як будь-якій іншій "нормальній" змінній. Але, в багатьох випадках, коли змінна $_ використовується в операціях, на неї можна не вказувати ссилку (тобто, простіше кажучи її можна не називати) і, крім того, сам знак операнду може опускатися так саме.
З усим цим сказаним, наш попередній приклад приймає зовсім лаконічну форму:
#!/usr/bin/perl $_ = "anyky "; if (/a.*/) { print } $_ ="benyky "; if (/a.*/) { print } $_="iily varenyky "; if (/a.*/) { print }
Розглянемо ближче в цьому прикладі два наступні рядки:
$_ = "anyky "; if (/a.*/) { print }
В першому рядку змінній $_ присвоюється якесь значення. З цим все зрозуміло, і питань тут немає. Трохи складніше з другим рядком. У фіґурних дужках стоїть просто 'print'. Саме час запитати: "Print - що?". І справді - що?
Оператор print, підпадає під туж саму конвенцію, що і більшість операторів Перла. А саме: якщо він не знаходить явно вказаних даних, над якими він повинен працювати (друкувати - у випадку з print), він друкує стандартну змінну $_.
Теж саме стосується і лівої частиною виразу - коли в круглих дужках вказаний сам тільки регулярний вираз і не вказано текстової змінної, цей регулярний вираз порівнюється із стандартною змінною $_.
Якщо виразити те, що написане в рядку if (/a.*/) { print } і перекласти це з мови Перл на мову людську то вийде таке: "Якщо стандартна текстова змінна $_ містить в собі літеру a після якої йде будь-що, то треба цю стандартну змінну надрукувати".
Що робити, якщо нам навпаки потрібно вибрати текстові рядки, в яких не зустрічається той чи інший регулярний вираз? Чи потрібно для цього писати якийсь спецільний вираз, чи може користуватися іншою формою умовного оператора if ? Чи ще що-небудь?
Можна користуватися будь-яким з названих методів. Але спеціально для таких ситуацій існує інша форма порівняння. Замість =~ потрібно користуватися оператором !~ , і алґоритм роботи команди при цьому змінюється на прямо протилежний: блок, що йде в тілі умовного оператора виконується тільки тоді, коли не виконується дія над регулярним виразом.
Тобто:
#!/usr/bin/perl $a = "anyky "; if ($a !~ /a.*/) { print $a; } $b = "benyky "; if ($b !~ /a.*/) { print $b; } $c ="iily varenyky "; if ($c !~ /a.*/) { print $c; }
дає такий результат:
./mytest.pl benyky
Знак оклику всередині круглих дужок оператора if також змінює значення умовного оператора на протилежне. Тобто, тільки що приведений скрипт буде аналоґічним до наступного:
#!/usr/bin/perl $_ = "anyky "; if (!/a.*/) { print } $_ ="benyky "; if (!/a.*/) { print } $_="iily varenyky "; if (!/a.*/) { print }
І нарешті - ще одна форма умовного оператора. Наскільки мені відомо - Перл - це єдина мова, яка в даний момент має таку форму умовного оператора. В Перлі оператор unless є зворотнім до оператора if . Тобто, if (!<умова>) {<дія>} є аналоґічним до unless (<умова>) {<дія>} , і з урахуванням тільки що сказаного попередній скрипт можна переписати у вигляді:
#!/usr/bin/perl $_ = "anyky "; unless (/a.*/) { print } $_ ="benyky "; unless (/a.*/) { print } $_="iily varenyky "; unless (/a.*/) { print }
Що-ж, після того, як ми вже трохи познайомилися і із самими регулярними виразами, і із деякими їх застосуваннями, мабуть час вже просто витягти руки з кишень, простягнути їх один одному і формально представитися і роззнайомитися ближче з подробицями. Але ж, звичайно, під час таких формальних знайомств про себе багато не кажуть. Тільки й того що, такий-то такий-то, працюю там-то, а роблю... Та ладно, чого там!
Тож, наше офіційне знайомство з регулярними виразами теж буде не занадто фамільярним. Досить короткі відомості про синтаксис, кілька коротеньких прикладів, тощо. Тож, будьмо!
А. Ейнштейн
^ - означає початок рядка, якщо стоїть на початку регулярного виразу. Інше застосування ми розглянемо пізніше. $ - означає кінець рядка (звичайно ж, аналоґічно до попереднього випадку, тільки якщо стоїть в кінці регулярного виразу).
Як обіцяно, кілька коротеньких прикладів.
Приклад 1.
$_="anyky"; if (/^a.*/) { print } $_ ="benyky "; if (/^a.*/) { print } $_=" varenyky"; if (/^a.*/) { print }
дає в результаті:
./mytest.pl anyky
Зрозуміло, бо тільки в першому виразі a стоїть на початку рядка.
Приклад 2.
А в такому варіанті
$_="anyky"; if (/.*y$/) { print } $_ ="benyky "; if (/.*y$/) { print } $_=" varenyky"; if (/.*y$/) { print }
ми отримуємо таке:
./mytest.pl anyky varenyky
(Зверніть увагу на пропуск, що іде в кінці другого виразу, і стане зрозуміло, чому не надруковано другий вираз.)
Приклад 3.
Якщо об'єднати початок рядка та кінець рядка, то отримаємо таке:
$_="anyky"; if (/^a.*y$/) { print } $_ ="benyky "; if (/^a.*y$/) { print } $_=" varenyky"; if (/^a.*y$/) { print } ./mytest.pl anyky
Приклад 4.
Як знайти пустий рядок? Дуже просто! Пустий рядок складається з початку і кінця, і нічого посередині:
$_=""; if (/^$/) { print "Пустий рядок!" } ./mytest.pl Пустий рядок!
І невеличка вправа під кінець:
Вправа
Попробуйте наперед сказати, що надрукують такі оператори?
$_="слово"; if (/^.*$/) { print "Рядок не пустий!" } $_=""; if (/^.*$/) { print "Рядок не пустий!" }
Напишіть невеличкий скрипт з цим оператором і перевірте, чи вірне було ваше передбачення.
Регулярні вирази в Перлі мають певну кількість символів, які мають дещо спеціальне трактування. Ми вже знайомі з кількома з них, це крапка, зірочка, стрілка вгору ( ^ ) та знак долара ( $ ). Є ще деякі спеціальні символи крім цих, але їх не дуже багато. За виключенням цього досить невеликого набору символів, всі інші трактуються в Перлі досить просто - вони означають самі себе. Так, як, наприклад, літера a , у виразі if (^a) означає саме це - літеру a .
Тобто, більшість алфавітно-цифрових символів у регулярних виразах Перла не мають якогось особливого значення і перше правило буде таке:
будь-який символ за - означає сам цей символ. виключенням спеціальних символів
Але далі якраз і починається розмова про множники. Вони потрібні, коли треба відшукати рядки, в яких та чи інша літера трапляється не один раз (як в if (/a/) ), або два рази ( як в if (/aa/) ), а певну (наперед задану або, навіть, і невідому наперед) кількість разів.
До цього моменту ми користувалися єдиним модифікатором (множником) коли треба було відшукати довільну кількість літер у виразі. Це була комбінація із крапки та зірочки " .* ". Трапляються і інші ситуації - треба знайти рядки, в яких літера "зю" зустічається 5 (не більше, і не менше) разів, або коли відмітити ті речення, в яких стоїть по три коми підряд (Навіть і дві підряд буде вже багато, тому може виникнути задача відшукати всі місця, де стоїть більше однієї коми, і змінити їх ... ну, скажімо на знак запитання). Саме в таких випадках потрібно вдаватися до "множників" -- множаться літери на рази. Всі наступні вирази вживаються, коли потрібно відшукати (по-перлівськи кажучи "match") певний символ, який трапляється певну кількість разів. І всі ці вирази вживаються так:
<символ><множник>
Де в якості множників вживаються такі речі (в наступних визначеннях N і M - це цілі числа):
"*" - вже відома нам зірочка, яка буквально означає "будь-яка кількість разів" (включаючи нуль); + - дуже схожий за вжитком до зірочки *, але якщо зірочка означає "будь-яку кількість разів", то + означає "один, або більше разів"; {N} - означає "N разів". {N,M} - означає "від N до M разів".
{N,} - означає "N або більше разів". {,M} - означає "не більше, ніж M разів".
N.B.
Зауважте, що під кількістю разів в усіх перечислених випадках мається на увазі кількість разів скільки та чи інша літера стоїть підряд в текстовому рядку, а не загальна кількість літер в рядку.
І зразу-ж не відкладаючи на завтра приклади:
Приклад 1
Вираз /b+/ буде відповідати таким текстовим фраґментам:
, але не буде відповідати такому як
Приклад 2
Вираз /b*/ відповідає всім виразам з попереднього прикладу.
Приклад 3
/o{2}/ відповідає тільки одному єдиному виразу: "abooboo", а /b{2}/ відповідає тільки "abba".
Приклад 4
Регулярний вираз /a+/ є тотожним таким регулярним виразам, як /a{1,}/ та /aa*/ .
Друге правило формулюється таким чином: все, що стоїть в квадратних дужках, розглядається Перлом як вибір. - підходить будь-який символ із тих, що вказані в цьому переліку. Тобто, [ab] означає "або a , або b ".
Людині, якій потрібно відшукати в тексті схожі слова, як наприклад, "трава" та "дрова" потрібно було-б написати таке:
if (/[тд]р[оа]ва/) { print }
(Але зверніть увагу, що під цю категорію підпадають також такі варіанти, як "драва" та "трова".
Приклад 5
Якщо Вам потрібно із списку дат, надрукувати тільки ті, що відносяться або до 1998 або до 1999 року, Ви можете написати таке:
if (/199[89]/) { print }
Деяким розширенням до цього правила є введення спеціальних символів усередині квадратних дужок. Щоб не переписувати весь підряд алфавіт, коли Вам просто потрібно сказати "Будь-яка літера", Ви просто можете записати, так, як би вираз "Від а до я" Вам треба було б записати на шматку паперу: "а-я". Виглядатиме це дуже просто:
if (/a-z/) { print }
Що просто означає, що буде надрукаваний кожен рядок, в якому трапляється будь-яка літера (маленька !) англійської абетки. Тобто:
Приклад 6
Вищенаведений регулярний вираз відповідає таким текстовим рядкам:
і не буде відповідати жодному з наведених нижче:
Теж саме стосується і цифр: будь-яка десяткова цифра, що приймає значення від 3 до 8 може бути записана як [3-7] .
І вже, фактично, в багатьох наведених прикладах ми сформулювали третє правило : простіші регулярні вирази комбінуються і утворюють більш складні вирази.
Приклад 7
Регулярний вираз /^ 199[7-9] [a-z]+/ складається фактично з п'яти "атомарних" регулярних виразів:
Наступні рядки всі будуть відпвідати цьому регулярному виразу:
+------------------------- | 1998 omega | 1999 alpha | 1997 beta | 1997 gamma $20 | 1998 sigma Proxima | 1999 s $40 |
, а наступні - не підходять під це визначення:
+------------------------- |1999 omega |1998alpha |1997 Beta |1999 $100 | 1999 omega |
Невеличкі вправи, що випливає з цього прикладу:
До цього ми мали тільки одне застосування регулярних виразів, а саме - надрукувати рядок, якщо регулярний вираз справдився. Але, саме собою зрозуміле, що цього може виявитися занадто мало. Дуже часто потрібно буває виконати якісь дії саме над тією частиною рядка, яка задовольняє певному регулярному виразу.
В цьому випадку на допомогу приходять дужки. Застосування круглих дужок дає можливість із цілого регулярного виразу виділити певну його частину, яка присвоюється після перевірки спеціальній змінній. Над цією змінною можна пізніше виконувати будь-які дії, дозволені над змінними. Такі змінні мають спеціальний вигляд і позначаються одним із двох способів: або зворотньою косою, за якою іде цифра від 1 до 9 (наприклад, \1, \5), або знаком долара за яким іде натуральне число (як наприклад, $1, $5 або $29). Кожна така змінна ($n або \n) означає ту частину рядка, що поставлена у відповідність регулярному виразу, який записаний у n-х зліва дужках.
Приклад 1
Візьмемо регулярний вираз із попереднього параграфу і трохи його змінимо. Нехай деяка змінна приймає по черзі такі значення:
$a = " 1998 omega"; $a = " 1999 alpha"; $a = " 1997 beta" $a = " 1997 gamma $20" $a = " 1998 sigma Proxima"; $a = " 1999 s $40"; $a = " 1999 omega"; $a = " 1998alpha"; $a = " 1997 Beta"; $a = " 1999 $100"; $a = " 1999 omega";
До всих цих змінних ми застосуємо такий оператор:
if ($a =~ /^ (199[7-9]) ([a-z]+)/) { $year = $1; $item = $2; print "Рік: ", $year," пункт: ", $item, "\n"; }
Тоді результатом роботи цього скрипту буде такий вихід:
Рік: 1998 пункт: omega Рік: 1999 пункт: alpha Рік: 1997 пункт: beta Рік: 1997 пункт: gamma Рік: 1998 пункт: sigma Рік: 1999 пункт: s
Приклад 2
Перша робоча програмка, яка може хоч на що-небудь згодитись. Давайте спробуємо написати скрипт, який буде працювати з реальними даними в будь-якому Юніксі. Увага! Це Юнікс специфічний скрипт, і він не має аж ніякого сенсу у інших системах типу Віндовс, ДОС, тощо. "Юнікс" тут використовується у "широкому" смислі - Лінакс теж попадає під це означення.
З часом, освоївши Перл трохи більше, Ви помітите, що цей же скрипт можна написати набагато простіше. Але ми напишемо його, використовуючи тільки ті засоби, що ми вже вивчили до цього моменту. Тому в ньому можливі деякі "важкі" конструкції.
Але нам все-ж таки не обійтись від кількох нових речей. Це речі такі:
Наш скрипт буде такий:
#!/usr/bin/perl if (open (STDIN, "who|")) { while (<>) { if (/^([a-z][a-z0-9]*) /) { print $1, "\n" } } } else { die "Не можу створити процес who !\n"; }
Уявіть собі, що Вам потрібно знати, хто працює на даний момент у Вашій системі. У Юніксі про це можна дізнатися, виконавши команду who .
META ((( non greedy вирази з -- ? ))) META ((( дужки і $<цифра> ))) * scope of $(digit) * difference \$ (more: \11, $11, \011) * othe matches $+, $&, $`, $', $0 META ((( приклад бази даних - телефонний довідник ))) ==== META : (спеціальні символи (metacharacters) - egrep ) \ Quote the next metacharacter ^ Match the beginning of the line . Match any character (except newline) $ Match the end of the line (or before newline at the end) | Alternation () Grouping [] Character class META : (спеціальні символи - розширення до egrep )
\t tab (HT, TAB) \n newline (LF, NL) \r return (CR) \f form feed (FF) \a alarm (bell) (BEL) \e escape (think troff) (ESC) \033 octal char (think of a PDP-11) \x1B hex char \c[ control char \l lowercase next char (think vi) \u uppercase next char (think vi) \L lowercase till \E (think vi) \U uppercase till \E (think vi) \E end case modification (think vi) \Q quote (disable) pattern metacharacters till \E