Поекранно висновок: команда p

Досі для дослідження файлів застосовувалася команда cat Але якщо файл досить довгий, а зєднання з системою високошвидкісне, то СAT виводить дані занадто швидко – так, що навіть хороша реакція (Швидке натискання ctl-S і ctl-Q) не допомагає прочитати його

Потрібна програма, яка друкувала б файл невеликими заданими порціями Подібної стандартної програми не існує, ймовірно, тому, що в роки створення системи UNIX комунікаційні канали були повільними, а терміналами служили друкують устрою Тому напишемо тепер програму (назвемо її p), яка буде виводити файл порціями, поміщається на екрані терміналу, при цьому, перш ніж перейти до демонстрації наступного екрану, програма чекає підтвердження користувача на виконання цієї дії («P» – це гарне коротку назву для програми, яка використовується постійно) Як і інші програми, p зчитує вхідні дані з файлів, зазначених в аргументах, або з пристрою стандартного введення:

$ p visc

$ grep  #define  *[ch] | p

$

Цю програму найкраще писати на Сі, тому що на Сі простіше – стандартні засоби не підходять при змішуванні введення з файлу або каналу з термінальним вводом

Основна (без жодних надмірностей) конструкція забезпечує виведення невеликими фрагментами Відповідним розміром порції представляються 22 рядки: це трохи менше, ніж 24 рядки (що вміщуються на екран більшості відеотерміналів), і це третина стандартної сторінки (66 рядків) Запропонуємо простий спосіб запросити користувача до дій – не друкувати останній символ нового рядка кожної 22строчной порції Тоді курсор зупинятиметься в правому кінці останнього рядка, а не на лівій межі наступної Коли користувач натисне клавмшу RETURN, Зявиться бракуючий символ нового рядка, і наступна рядок буде виведена на правильному місці Якщо користувач натисне ctl-D або q наприкінці екрану, відбудеться вихід з p

Довгі рядки НЕ будуть оброблятися спеціальним чином Якщо задано декілька файлів, будемо просто переходити з одного на інший, без яких би то не було коментарів Тобто поведінка

$ p імена5файлов..

буде аналогічно

$ cat імена5файлов .. | p

Імена файлів можна додати за допомогою циклу for:

$ for i in імена5файлов..

&gt do

&gt            echo $i:

&gt            cat  $i

&gt done | p

Безсумнівно, є маса функцій, які можна було б додати в програму Краще почати з «голої» версії, а потім розвивати її так, як велить розум Якщо піти таким шляхом, то додаватися будуть властивості, які дійсно потрібні користувачам, а не ті, що нам здаються потрібними

Структура p в основному аналогічна команді vis: функція main циклічно проходить по файлах, звертаючись до функції print, яка виконується для кожного файлу

/ * P: друкувати вхідні дані порціями (версія 1) * /

#include &ltstdioh&gt

#define PAGESIZE         22

char * progname / * Імя програми для повідомлення про помилку * /

main(argc,  argv) int  argc

char  *argv[]

{

218             Глава 6 Програмування з використанням стандартного введення-виведення

int i

FILE *fp, *efopen()

progname  =  argv[0] if  (argc  ==  1)

print(stdin,  PAGESIZE) else

for  (i = 1  i &lt argc i++)  {

fp  =  efopen(argv[i],  &quotr&quot) print(fp,  PAGESIZE) fclose(fp)

} exit(0)

}

Функція efopen інкапсулює дуже поширене дію: намагається відкрити файл, а якщо це неможливо, виводить повідомлення про помилку і завершується Для того щоб в повідомленні про помилку ідентифікувалась програма, що порушила нормальну роботу (чи та, чия робота була порушена), efopen посилається на зовнішню строкову змінну progname, що містить імя програми, яка встановлюється в main

FILE * efopen (file, mode) / * відкрити файл за допомогою fopen file, завершитися якщо це неможливо * /

char  *file, *mode

{

FILE  *fp,  *fopen() extern  char  *progname

if ((fp =  fopen(file,  mode))  =  NULL) return  fp

fprintf(stderr, &quot%s: cant  open file %s  mode  %s\n&quot, progname, file,  mode)

exit(1)

}

Перш ніж зупинитися на такому варіанті efopen, автори випробували декілька інших В одному з них функція повертала після виведення повідомлення нульовий покажчик, який свідчить про невдачу Так у коду, що викликає функцію, зявлялася можливість вибору: продовжувати або вийти В іншому варіанті у efopen був третій аргумент, що визначає, чи потрібно повертатися в разі невдалої спроби відкрити файл Проте практично у всіх розглянутих прикладах немає ніякого сенсу продовжувати роботу, якщо файл не вдалося відкрити, тому краще використовувати версію efopen, представлену вище

Основна діяльність програми p здійснюється функцією print:

print (fp, pagesize) / * друкувати fp порціями розміром з pagesize * / FILE * fp

int pagesize

{

static int lines = 0 / * Кількість рядків до цього моменту * / char buf [BUFSIZ]

while  (fgets(buf,  sizeof buf,  fp) =  NULL) if (++lines &lt  pagesize)

fputs(buf,  stdout) else  {

buf[strlen(buf)–1]  =  \0; fputs(buf,  stdout) fflush(stdout)

ttyin()

lines = 0

}

}

Розмір буфера введення задається константою BUFSIZ, визначеної у фай ле Функція fgets (buf, size, fp) переносить наступний рядок введення (аж до символу нового рядка, включаючи його) з fp в buf, додаючи завершальний символ \ 0 максимальну кількість копійованих символів одно size-1 У кінці файлу повертається NULL (Функція fgets могла б бути побудована зручніше: вона повертає не кількість символів, а buf до того ж не видається попередження у випадку занадто довгою рядка введення Правда символи не втрачаються, але при ходиться переглядати buf, щоб зрозуміти, що ж відбувається на самому справі)

Функція strlen повертає довжину рядка вона потрібна для того, щоб прибрати замикає символ нового рядка з останнього рядка введення Функція fputs (buf, fp) записує рядок buf в файл fp Виклик fflush в кінці сторінки закриває буферізованние висновок

Рішення завдання читання відповіді користувача, що надходить після виведення кожної сторінки, відводиться процедурі ttyin Вона не може читати стандартний ввід, так як p повинна працювати коректно, навіть якщо вхідні дані надходять з файлу або каналу Для обробки даної ситуації програма відкриває файл / dev / tty, відповідний терміналу користувача, куди б не перенаправлявся стандартний ввід Процедура ttyin написана так, щоб повертати перший символ відповіді, але тут це її властивість не використано

ttyin () / * обробка відповіді з / dev / tty (версія 1) * /

{

char  buf[BUFSIZ] FILE  *efopen()

static FILE *tty  = NULL

if (tty == NULL)

tty = efopen(&quot/dev/tty&quot,  &quotr&quot)

if (fgets(buf,  BUFSIZ,  tty) == NULL  || buf[0] ==  q)

exit(0)

else        /*  ordinary  line  */ return  buf[0]

}

Покажчик на файл tty оголошується як static, тому він зберігає значення від одного виклику ttyin до наступного файл / dev / tty відкривається тільки при першому виклику

Очевидно, що є безліч властивостей, якими можна було б забезпечити p, причому без особливих зусиль, але перша версія цієї програми, написана авторами, робила тільки те, що тут описано: вона виво дила 22 рядки і чекала відповіді користувача Минуло багато часу, перш ніж додалося щось ще, і до сьогоднішнього дня дуже мало хто звертається до її додатковим можливостям

Одне з легко реалізованих доповнень – це введення змінної pa-gesize, яка задає кількість рядків на сторінці, вона може встановлюватися в командному рядку:

$ p n  ..

друк проводиться порціями по n рядків Для цього треба просто додати кілька рядків звичайного коду в початок функції main:

/ * P: друкувати вхідні дані порціями (версія 2) * /

int i, pagesize = PAGESIZE

progname = argv[0]

if  (argc &gt  1  &amp&amp  argv[1][0]  == –)  { pagesize  =  atoi(&ampargv[1][1]) argc––

argv++

}

Функція atoi перетворює символьну рядок в ціле число (див

atoi(3))

Ще одним доповненням p може бути можливість тимчасового вихо да в кінці кожної сторінки для виконання якоїсь іншої команди За аналогією з ed і багатьма іншими програмами, якщо користувач вводить рядок, яка починається зі знаку оклику, то решта рядка сприймається як команда і передається на виконання оболонці Така зміна також тривіально піддається реалізації, так як існує функція system (3), яка робить саме те, що потрібно (але слід проявити обережність, далі буде пояснено, чому) Ось і нова версія ttyin:

ttyin () / * обробка відповіді з / dev / tty (версія 2) * /

{

char  buf[BUFSIZ]

FILE *efopen()

static FILE *tty  = NULL

if (tty == NULL)

tty =  efopen(&quot/dev/tty&quot,  &quotr&quot) for  (;) {

if (fgets(buf,BUFSIZ,tty)  == NULL  || buf[0]  ==  q) exit(0)

else if (buf [0] == !’) {system (buf +1) / * Тут ПОМИЛКА * / printf (! \ N)

}

else        /*  ordinary  line  */ return  buf[0]

}

}

На жаль, в цій версії є насилу помітна, але фатальна помилка Команда, запущена system, успадковує стандартний введення від p, так що якщо p зчитувала дані з каналу або файлу, то команда може втрутитися і перешкодити її введенню:

$ cat  /etc/passwd | p -1

root:3DfHR5KoB3s:0:1:SUser:/: Ed Викликаєed зp

?                                                                                 ed  читає /etc/passwd ..

!                                                         .. Заплутується і виходить

Для того щоб вирішити цю проблему, необхідно знати, як в UNIX відбувається управління процесами, про це буде розказано в розділі 74 А поки памятаєте, що використання стандартної функції sys-tem з бібліотеки може призводити до помилок, але знайте, що ttyin працює коректно, якщо вона скомпільована з тією версією system, яка представлена ​​в розділі 7

Отже, написані дві програми, vis і p, які можна розглядати як кілька прикрашені варіанти cat Може бути, варто було б зробити їх частинами cat, доступними при вказівці відповідного необовязкового аргументу-v або-p Подібне питання – чи писати нову програму або додавати нові можливості до вже існуючий, виникає кожного разу, коли потрібно щось зробити Остаточного відповіді ми не знаємо, але є кілька правил, які можуть допомогти визначитися

Основний принцип полягає в тому, що програма повинна виконувати тільки одну базову функцію, якщо вона почне робити занадто багато всього, вона стане громіздкою, повільної, її складніше буде супроводжувати і використовувати Насправді додаткові можливості часто залишаються невикористаними, тому що люди не в змозі запамятати параметри

Тому краще не комбінувати cat і vis, адже cat просто копіює свої вхідні дані, не змінюючи їх, в той час як vis їх перетворити-

ет При їх злитті вийшла б програма, що виконує два раз них дії Ситуація з cat і p практично настільки ж очевидна: cat призначена для швидкого, продуктивного копіювання, а p – для перегляду До того ж p перетворює висновок: кожен 22-й символ нового рядка видаляється Схоже, що кращим рішенням буде збе нання трьох окремих програм

Вправа 66Чи буде p розумно працювати у випадку, якщо pagesize

є непозитивним числом ~

Вправа 67Що ще можна зробити з p Оцініть і реалізуйте (якщо вважаєте доцільним) можливість виводити заново частини, прочитані раніше (Авторам подобається це властивість) Додайте можливість виводити менше, ніж екран вхідних даних після кожної паузи Додайте можливість перегляду вперед і назад в пошуку рядка, для якої зазначений номер або вміст ~

Вправа 68Використовуйте засоби обробки файлів, властиві вбудованої в оболонку функції exec (див sh (1)), для того щоб виправити звернення ttyin до system ~

Вправа 69Якщо не вказати p, звідки брати введення, програма буде спокійно чекати введення з терміналу Чи варто виявити цю можливу помилку Якщо так, то як Підказка: isatty (3) ~

Джерело: Керниган Б, Пайк Р, UNIX Програмне оточення – Пер з англ – СПб: Символ-Плюс, 2003 – 416 с, Мул

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*