Скачиваний:
56
Добавлен:
08.01.2014
Размер:
2.6 Mб
Скачать

11.13. Запуск программ при помощи библиотек стандартного ввода/вывода

Стандартная библиотека ввода/вывода содержит несколько процедур для запуска одних программ из других. Основной из них является уже известная процедура runshell.

Описание

uses stdio;

function runshell(comstring:pchar):longint;

uses linux;

function shell(comstring:pchar):longint;

Функция runshell из файла stdio, как и shell из linux, выполняет команду, заданную строкой comstring. Вначале она создает дочерний процесс, который, в свою очередь, осуществляет вызов exec для запуска стандартного командного интерпретатора UNIX с командной строкой comstring. В это время процедура runshell в первом процессе выполняет вызов wait, гарантируя тем самым, что выполнение продолжится только после того, как запущенная команда завершится. Возвращаемое после этого значение retval содержит статус выхода командного интерпретатора, по которому можно определить, было ли выполнение программы успешным или нет. В случае неудачи любого из вызовов fork или exec значение переменной retval будет равно –1.

Поскольку в качестве посредника выступает командный интерпретатор, строка comstring может содержать любую команду, которую можно набрать на терминале. Это позволяет программисту воспользоваться такими преимуществами командного интерпретатора, как перенаправление ввода/вывода, поиск файлов в пути и т.д. Следующий оператор использует процедуру runshell для создания подкаталога при помощи программы mkdir:

retval := runshell('mkdir workdir');

if retval <> 0 then

writeln(stderr, 'Процедура runshell вернула значение ', retval);

Остановимся на некоторых важных моментах. Во-первых, процедура runshell в вызывающем процессе будет игнорировать сигналы SIGINT и SIGQUIT. Это позволяет пользователю прерывать выполнение команды, не затрагивая родительский процесс. Во-вторых, команда, выполняемая процедурой runshell, будет наследовать из вызывающего процесса некоторые открытые дескрипторы файлов. В частности, стандартный ввод команды будет получен из того же источника, что и в родительском процессе. При вводе из файла могут возникнуть проблемы, если процедура runshell используется для запуска интерактивной программы, так как ввод программы также будет производиться из файла.

Процедура runshell имеет один серьезный недостаток. Он не позволяет программе получать доступ к выводу запускаемой программы. Для этого можно использовать две другие процедуры из стандартной библиотеки ввода/вывода: pipeopen/popen и pipeclose/pclose.

Описание

uses stdio;

function pipeopen(comstring, _type:pchar):pfile;

function pipeclose(strm:pfile):integer;

Procedure POpen(Var F:FileType; comstring:pathstr; _type:char);

Function PClose(Var F:FileType):longint;

Как и процедура runshell, процедуры popen и pipeopen создает дочерний процесс командного интерпретатора для запуска команды, заданной параметром comstring. Но, в отличие от процедуры runshell, она также создает канал между вызывающим процессом и командой. При этом pipeopen возвращает структуру TFILE, связанную с этим каналом, а popen – переменную файлового типа. Если значение параметра _type равно w, то программа может выполнять запись в стандартный ввод при помощи структуры TFILE. Если же значение параметра _type равно r, то программа сможет выполнять чтение из стандартного вывода программы. Таким образом, процедуры popen и pipeopen представляют простой и понятный метод взаимодействия с другой программой.

Для закрытия потока, открытого при помощи процедуры popen, должна всегда использоваться процедура pclose. Она будет ожидать завершения команды, после чего вернет статус ее завершения.

Пример использования POpen:

uses linux;

var f : text;

i : longint;

begin

writeln ('Creating a shell script to which echoes its arguments');

writeln ('and input back to stdout');

assign (f,'test21a');

rewrite (f);

writeln (f,'#!/bin/sh');

writeln (f,'echo this is the child speaking.... ');

writeln (f,'echo got arguments \*"$*"\*');

writeln (f,'cat');

writeln (f,'exit 2');

writeln (f);

close (f);

chmod ('test21a',octal (755));

popen (f,'./test21a arg1 arg2','W');

if linuxerror<>0 then

writeln ('error from POpen : Linuxerror : ', Linuxerror);

for i:=1 to 10 do

writeln (f,'This is written to the pipe, and should appear on stdout.');

Flush(f);

Writeln ('The script exited with status : ',PClose (f));

writeln;

writeln ('Press <return> to remove shell script.');

readln;

assign (f,'test21a');

erase (f)

end.

Следующий пример, процедура getlist, использует процедуру popen и команду ls для вывода списка элементов каталога. Каждое имя файла затем помещается в двухмерный массив символов, адрес которого передается процедуре getlist в качестве параметра.

(* getlist - процедура для получения списка файлов в каталоге *)

uses stdio, strings;

const

MAXLEN=255; (* Максимальная длина имени файла *)

MAXCMD=100; (* Максимальная длина команды *)

ERROR=-1;

SUCCESS=0;

type

sarray=array [0..MAXLEN] of char;

darray=array [0..MAXCMD] of sarray;

function getlist(namepart:pchar; var dirnames:darray;

maxnames:integer):integer;

var

cmd:array [0..MAXCMD] of char;

in_line:array [0..MAXLEN+1] of char;

i:integer;

lsf:pfile;

begin

(* Основная команда *)

strcopy(cmd, 'ls ');

(* Дополнительные параметры команды *)

if namepart <> nil then

strlcat(cmd, namepart, MAXCMD - strlen(cmd));

lsf := pipeopen(cmd, 'r'); (* Запускаем команду *)

if lsf = nil then

begin

getlist:=ERROR;

exit;

end;

for i:=0 to maxnames-1 do

begin

if fgets(in_line, MAXLEN+2, lsf) = nil then

break;

(* Удаляем символ перевода строки *)

if in_line[strlen(in_line)-1] = #$a then

in_line[strlen(in_line)-1] := #0;

strcopy(dirnames[i], in_line);

end;

if i < maxnames then

dirnames[i][0] := #0;

pipeclose (lsf);

getlist:=SUCCESS;

end;

var

namebuf:darray;

i:integer;

begin

getlist('*.pas', namebuf, 100);

i:=0;

while namebuf[i][0]<>#0 do

begin

writeln(namebuf[i]);

inc(i);

end;

end.

Процедура getlist может быть вызвана следующим образом:

getlist('*.pas', namebuf, 100);

при этом в переменную namebuf будут помещены имена всех Паскаль-программ в текущем каталоге.

Следующий пример разрешает обычную проблему, с который часто сталкиваются администраторы UNIX: как быстро «освободить» терминал, который был заблокирован какой-либо программой, например, неотлаженной программой, работающей с экраном. Программа unfreeze принимает в качестве аргументов имz терминала и список программ. Затем она запускает команду вывода списка процессов ps при помощи процедуры popen для получения списка связанных с терминалом процессов и выполняет поиск указанных программ в этом списке процессов. Далее программа unfreeze запрашивает разрешение пользователя на завершение работы каждого из процессов, удовлетворяющих критерию.

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

$ ps -t ttyname

где ttyname является именем специального файла терминала в каталоге /dev, например, tty1, console, pts/8 и др. Выполнение этой команды ps дает следующий вывод:

PID TTY TIME COMMAND

29 со 0:04 sh

39 со 0:49 vi

42 со 0:00 sh

43 со 0:01 ps

Первый столбец содержит идентификатор процесса. Второй – имя терминала, в данном случае со соответствует консоли. В третьем столбце выводится суммарное время выполнения процесса. В последнем, четвертом, столбце выводится имя выполняемой программы. Обратите внимание на первую строку, которая является заголовком. В программе unfreeze, текст которой приведен ниже, нам потребуется ее пропустить.

(* Программа unfreeze - освобождение терминала *)

uses stdio,linux,strings;

const

LINESZ =150;

SUCCESS=0;

ERROR =(-1);

const

killflag:integer=0;

(* Инициализация этой переменной зависит от вашей системы *)

pspart:pchar = 'ps t ';

fmt:pchar = '%d %*s %*s %*s %s';

var

comline, inbuf, header, name:array [0..LINESZ-1] of char;

f:pfile;

j:integer;

pid:longint;

begin

if paramcount <2 then

begin

writeln (stderr, 'синтаксис: ',paramstr(0),' терминал программа ...');

halt (1);

end;

(* Сборка командной строки *)

strcopy (comline, pspart);

strcat (comline, argv[1]);

(* Запуск команды ps *)

f := pipeopen (comline, 'r');

if f = nil then

begin

writeln (stderr, paramstr(0),': не могу запустить команду ps ');

halt (2);

end;

(* Получить первую строку от ps и игнорировать ее *)

if fgets (header, LINESZ, f) = nil then

begin

writeln (stderr, paramstr(0),': нет вывода от ps?');

halt (3);

end;

(* Поиск программы, которую нужно завершить *)

while fgets (inbuf, LINESZ, f) <> nil do

begin

if sscanf (inbuf, fmt, [@pid, pchar(name)]) < 2 then

break;

for j := 2 to argc-1 do

begin

if strcomp (name, argv[j]) = 0 then

begin

if dokill (pid, inbuf, header) = SUCCESS then

inc(killflag);

end;

end;

end;

(* Это предупреждение, а не ошибка *)

if killflag=0 then

writeln(stderr, paramstr(0),': работа программы не завершена ',

paramstr(1));

pipeclose(f);

halt (0);

end.

Ниже приведена реализация процедуры dokill, вызываемой программой unfreeze. Обратите внимание на использование процедуры readln для чтений первого не пробельного символа (вместо нее можно было бы использовать и функцию yesno, представленную в разделе 11.8).

(* Получить подтверждение, затем завершить работу программы *)

function dokill(procid:longint;line,hd:pchar):integer;

var

c:char;

begin

writeln (#$a'Найден процесс, выполняющий заданную программу :');

writeln (#9,hd,#9,line);

writeln ('Нажмите `y` для завершения процесса ', procid);

write (#$a'Yes\No? > ');

(* Введите следующий не пробельный символ *)

readln (c);

if (c = 'y') or (c = 'Y') then

begin

kill (procid, SIGKILL);

dokill:=SUCCESS;

exit;

end;

dokill:=ERROR;

end;

Упражнение 11.9. Напишите свою версию процедуры getcwd, которая возвращает строку с именем текущего рабочего каталога. Назовите вашу программу wdir. Совет: используйте стандартную команду pwd.

Упражнение 11.10. Напишите программу arrived, которая запускает программу who при помощи процедуры popen для проверки (с 60-секундными интервалами), находится ли в системе пользователи из заданного списка. Список пользователей должен передаваться программе arrived в качестве аргумента командной строки. При обнаружении кого-нибудь из перечисленных в списке пользователей, программа arrived должна выводить сообщение. Программа должна быть достаточно эффективной, для этого используйте вызов sleep между выполнением проверок. Программа who должна быть описана в справочном руководстве системы.

Соседние файлы в папке Полищук, Семериков. Системное программирование в UNIX средствами Free Pascal