goto err;
for(i=0; i < 16; i++){
p.line[ strlen("/dev/ptyp") ] =
"0123456789abcdef" [i] ;
if((p.pfd = open( p.line, O_RDWR )) >= 0 ){
p.line[ strlen("/dev/") ] = 't';
return p;
}
}
}
#endif
err: return p;
}


















А. Богатырев, 1992-95 - 267 - Си в UNIX

/* Ведение статистики по вызовам script */
void write_stat( in_bytes, out_bytes, time_here , name, line, at )
long in_bytes, out_bytes;
time_t time_here;
char *name;
char *line;
char *at;
{
FILE *fplog;
struct flock lock;

if((fplog = fopen( LOGFILE, "a" )) == NULL )
return;

lock.l_type = F_WRLCK;
lock.l_whence = 0;
lock.l_start = 0;
lock.l_len = 0; /* заблокировать весь файл */
fcntl ( fileno(fplog), F_SETLKW, &lock );

fprintf( fplog, "%s (%s) %ld bytes_in %ld bytes_out %ld secs %s %s %s",
PP.command, Reason, in_bytes, out_bytes,
time_here, name, line, at );
fflush ( fplog );

lock.l_type = F_UNLCK;
lock.l_whence = 0;
lock.l_start = 0;
lock.l_len = 0; /* разблокировать весь файл */
fcntl ( fileno(fplog), F_SETLK, &lock );

fclose ( fplog );
}


void wm_done(sig){
char *getlogin(), *getenv(), *logname = getlogin();
time( &PP.t_stop ); /* запомнить время окончания */

wm_resettty(); /* восстановить режим базового терминала */
if( fpscript )
fclose(fpscript);
if( PP.pid > 0 ) kill( SIGHUP, PP.pid ); /* "обрыв связи" */

if( go ) write_stat( PP.in_bytes, PP.out_bytes,
PP.t_stop - PP.t_start,
logname ? logname : getenv("LOGNAME"),
PP.line, ctime(&PP.t_stop) );
printf( "\n" );
exit(0);
}













А. Богатырев, 1992-95 - 268 - Си в UNIX

/* Запуск трассируемого процесса на псевдотерминале */
void wm_startshell (ac, av)
char **av;
{
int child, fd, sig;

if( ac == 0 ){
static char *avshell[] = { "/bin/sh", "-i", NULL };
av = avshell;
}
if((child = fork()) < 0 ){
perror("fork");
wm_done(errno);
}
if( child == 0 ){ /* SON */
if( tty_stdin )
setpgrp(); /* отказ от управляющего терминала */

/* получить новый управляющий терминал */
if((fd = open( PP.line, O_RDWR )) < 0 ){
exit(errno);
}

/* закрыть лишние каналы */
if( fpscript )
fclose(fpscript);
close( PP.pfd );


#ifdef __SVR4
/* Push pty compatibility modules onto stream */
ioctl(fd, I_PUSH, "ptem"); /* pseudo tty module */
ioctl(fd, I_PUSH, "ldterm"); /* line discipline module */
ioctl(fd, I_PUSH, "ttcompat"); /* BSD ioctls module */
#endif


/* перенаправить каналы, связанные с терминалом */
if( fd != STDIN && tty_stdin ) dup2(fd, STDIN);
if( fd != STDOUT && tty_stdout ) dup2(fd, STDOUT);
if( fd != STDERR && tty_stderr ) dup2(fd, STDERR);
if( fd > STDERR )
(void) close(fd);

/* установить моды терминала */
STTY(TTYFD, &ttypmodes);

/* восстановить реакции на сигналы */
for(sig=1; sig < NSIG; sig++)
signal( sig, SIG_DFL );

execvp(av[0], av);
system( "echo OBLOM > HELP.ME");
perror("execl");
exit(errno);









А. Богатырев, 1992-95 - 269 - Си в UNIX

} else { /* FATHER */
PP.pid = child;
PP.command = av[0];
time( &PP.t_start ); PP.t_stop = PP.t_start;

signal( SIGHUP, wm_done );
signal( SIGINT, wm_done );
signal( SIGQUIT, wm_done );
signal( SIGTERM, wm_done );
signal( SIGILL, wm_done );
signal( SIGBUS, wm_done );
signal( SIGSEGV, wm_done );
}
}


char buf[ BSIZE ]; /* буфер для передачи данных */

/* /dev/pty? /dev/ttyp?
экран *--------* *--------*
/||| | | PP.pfd | |
|||||<-STDOUT--| мой |<---------| псевдо |<-STDOUT---|
\||| |терминал| |терминал|<-STDERR---|трассируемый
|(базовый) | | |процесс
------- | | STDIN | | |
|.....|-STDIN--> |----------> |--STDIN--->|
|_____| | | | |
клавиатура *--------* *--------*
master slave

*/


/* Опрос дескрипторов */
void wm_select(){
int nready;
int nfds;
int maxfd;
int nopen; /* число опрашиваемых дескрипторов */
register f;

fd_set set, rset; /* маски */
struct timeval timeout, rtimeout;

FD_ZERO(&set); nopen = 0; /* очистка маски */

FD_SET (PP.pfd, &set); nopen++; /* учесть в маске */
FD_SET (STDIN, &set); nopen++;
maxfd = max(PP.pfd, STDIN);

timeout.tv_sec = 3600; /* секунд */
timeout.tv_usec = 0; /* миллисекунд */












А. Богатырев, 1992-95 - 270 - Си в UNIX

nfds = maxfd + 1;
while( nopen ){
rset = set;
rtimeout = timeout;

/* опросить дескрипторы */
if((nready = select( nfds, &rset, NULL, NULL, &rtimeout )) <= 0)
continue;

for(f=0; f < nfds; f++ )
if( FD_ISSET(f, &rset)){ /* дескриптор f готов */
int n;

if((n = read(f, buf, sizeof buf)) <= 0 ){
FD_CLR(f, &set); nopen--; /* исключить */
close(f);

} else {
int fdout;

/* учет и контроль */
if( f == PP.pfd ){
fdout = STDOUT;
PP.out_bytes += n;
if( fpscript )
fwrite(buf, 1, n, fpscript);


} else if( f == STDIN ) {
fdout = PP.pfd;
PP.in_bytes += n;
if( halfflag && fpscript )
fwrite(buf, 1, n, fpscript);
if( autoecho )
write(STDOUT, buf, n);
}
write(fdout, buf, n);
}
}
}
}























А. Богатырев, 1992-95 - 271 - Си в UNIX

int main(ac, av) char **av;
{
while( ac > 1 && *av[1] == '-' ){
switch(av[1][1]){
case 's':
scriptflg++;
break;
case 'f':
av++; ac--;
protocol = av[1];
scriptflg++;
break;
case 'h':
halfflag++;
break;
case 'a':
autoecho++;
break;
default:
fprintf(stderr, "Bad key %s\n", av[1]);
break;
}
ac--; av++;
}
if( scriptflg ){
fpscript = fopen( protocol, "w" );
}
ac--; av++;


wm_init();
PP = wm_ptypair();
if( PP.pfd < 0 ){
fprintf(stderr, "Cannot get pty. Please wait and try again.\n");
return 1;
}
wm_fixtty();
wm_startshell(ac, av);
go++;
wm_select();
wm_done(0);
/* NOTREACHED */
return 0;
}


    6.12. Простой интерпретатор команд.


Данный раздел просто приводит исходный текст простого интерпретатора команд.
Функция match описана в главе "Текстовая обработка".















А. Богатырев, 1992-95 - 272 - Си в UNIX

/* Примитивный интерпретатор команд. Распознает построчно
* команды вида: CMD ARG1 ... ARGn <FILE >FILE >>FILE >&FILE >>&FILE
* Сборка: cc -U42 -DCWDONLY sh.c match.c pwd.c -o sh
*/

#include <sys/types.h>/* определение типов, используемых системой */
#include <stdio.h> /* описание библиотеки ввода/вывода */
#include <signal.h> /* описание сигналов */
#include <fcntl.h> /* определение O_RDONLY */
#include <errno.h> /* коды системных ошибок */
#include <ctype.h> /* макросы для работы с символами */
#include <dirent.h> /* эмуляция файловой системы BSD 4.2 */
#include <pwd.h> /* работа с /etc/passwd */
#include <sys/wait.h> /* описание формата wait() */


char cmd[256]; /* буфер для считывания команды */
#define MAXARGS 256 /* макс. количество аргументов */
char *arg[MAXARGS]; /* аргументы команды */
char *fin, *fout; /* имена для перенаправления ввода/вывода */
int rout; /* флаги перенаправления вывода */

char *firstfound; /* имя найденной, но невыполняемой программы */
#define LIM ':' /* разделитель имен каталогов в path */
extern char *malloc(), *getenv(), *strcpy(), *getwd();
extern char *strchr(), *execat();
extern void callshell(), printenv(), setenv(), dowait(), setcwd();
extern struct passwd *getpwuid();
/* Предопределенные переменные */
extern char **environ; /* окружение: изначально смотрит на тот же
* массив, что и ev из main() */
extern int errno; /* код ошибки системного вызова */


char *strdup(s)char *s;
{ char *p; return(p=malloc(strlen(s)+1), strcpy(p,s)); }
/* strcpy() возвращает свой первый аргумент */
char *str3spl(s, p, q) char *s, *p, *q;
{ char *n = malloc(strlen(s)+strlen(p)+strlen(q)+1);
strcpy(n, s); strcat(n, p); strcat(n, q); return n;
}

int cmps(s1, s2) char **s1, **s2;
{ return strcmp(*s1, *s2); }




















А. Богатырев, 1992-95 - 273 - Си в UNIX

/* Перенаправить вывод */
#define APPEND 0x01
#define ERRTOO 0x02
int output (name, append, err_too, created) char *name; int *created;
{
int fd;
*created = 0; /* Создан ли файл ? */

if( append ){ /* >>file */
/* Файл name существует? Пробуем открыть на запись */
if((fd = open (name, O_WRONLY)) < 0) {
if (errno == ENOENT) /* Файл еще не существовал */
goto CREATE;
else
return 0; /* Не имеем права писать в этот файл */
}
/* иначе fd == открытый файл, *created == 0 */
}else{
CREATE: /* Пытаемся создать (либо опустошить) файл "name" */
if((fd = creat (name, 0666)) < 0 )
return 0; /* Не могу создать файл */
else *created = 1; /* Был создан новый файл */
}
if (append)
lseek (fd, 0l, 2); /* на конец файла */
/* перенаправить стандартный вывод */
dup2(fd, 1);
if( err_too ) dup2(fd, 2); /* err_too=1 для >& */
close(fd); return 1;
}


/* Перенаправить ввод */
int input (name) char *name;
{
int fd;
if((fd = open (name, O_RDONLY)) < 0 ) return 0;/* Не могу читать */
/* перенаправить стандартный ввод */
dup2(fd, 0); close(fd); return 1;
}
























А. Богатырев, 1992-95 - 274 - Си в UNIX

/* запуск команды */
int cmdExec(progr, av, envp, inp, outp, outflg)
char *progr; /* имя программы */
char **av; /* список аргументов */
char **envp; /* окружение */
char *inp, *outp; /* файлы ввода-вывода (перенаправления) */
int outflg; /* режимы перенаправления вывода */
{
void (*del)(), (*quit)();
int pid;
int cr = 0;

del = signal(SIGINT, SIG_IGN); quit = signal(SIGQUIT, SIG_IGN);
if( ! (pid = fork())){ /* ветвление */
/* порожденный процесс (сын) */
signal(SIGINT, SIG_DFL); /* восстановить реакции */
signal(SIGQUIT,SIG_DFL); /* по умолчанию */
/* getpid() выдает номер (идентификатор) данного процесса */
printf( "Процесс pid=%d запущен\n", pid = getpid());

/* Перенаправить ввод-вывод */
if( inp ) if(!input( inp )){
fprintf(stderr, "Не могу <%s\n", inp ); goto Err;
}
if( outp )
if(!output (outp, outflg & APPEND, outflg & ERRTOO, &cr)){
fprintf(stderr, "Не могу >%s\n", outp ); goto Err;
}
/* Заменить программу: при успехе
* данная программа завершается, а вместо нее вызывается
* функция main(ac, av, envp) программы, хранящейся в файле progr.
* ac вычисляет система.
*/
execvpe(progr, av, envp);


Err:
/* при неудаче печатаем причину и завершаем порожденный процесс */
perror(firstfound ? firstfound: progr);
/* Мы не делаем free(firstfound),firstfound = NULL
* потому что данный процесс завершается (и тем ВСЯ его
* память освобождается) :
*/
if( cr && outp ) /* был создан новый файл */
unlink(outp); /* но теперь он нам не нужен */

exit(errno);
}
/* процесс - отец */

/* Сейчас сигналы игнорируются, wait не может быть оборван
* прерыванием с клавиатуры */
dowait(); /* ожидать окончания сына */
/* восстановить реакции на сигналы от клавиатуры */
signal(SIGINT, del); signal(SIGQUIT, quit);
return pid; /* вернуть идентификатор сына */
}







А. Богатырев, 1992-95 - 275 - Си в UNIX

/* Запуск программы с поиском по переменной среды PATH */
int execvpe(progr, av, envp) char *progr, **av, **envp;
{
char *path, *cp;
int try = 1;
register eacces = 0;
char fullpath[256]; /* полное имя программы */

firstfound = NULL;
if((path = getenv("PATH")) == NULL )
path = ".:/bin:/usr/bin:/etc";
/* имя: короткое или путь уже задан ? */
cp = strchr(progr, '/') ? "" : path;
do{ /* пробуем разные варианты */
cp = execat(cp, progr, fullpath);
retry:
fprintf(stderr, "пробуем \"%s\"\n", fullpath );
execve(fullpath, av, envp);
/* если программа запустилась, то на этом месте данный
* процесс заменился новой программой. Иначе - ошибка. */
switch( errno ){ /* какова причина неудачи ? */
case ENOEXEC: /* это командный файл */
callshell(fullpath, av, envp);
return (-1);
case ETXTBSY: /* файл записывается */
if( ++try > 5 ) return (-1);
sleep(try); goto retry;
case EACCES: /* не имеете права */
if(firstfound == NULL)
firstfound = strdup(fullpath);
eacces++; break;
case ENOMEM: /* программа не лезет в память */
case E2BIG:
return (-1);
}
}while( cp );
if( eacces ) errno = EACCES;
return (-1);
}


/* Склейка очередной компоненты path и имени программы name */
static char *execat(path, name, buf)
register char *path, *name;
char *buf; /* где будет результат */
{
register char *s = buf;
while(*path && *path != LIM )
*s++ = *path++; /* имя каталога */
if( s != buf ) *s++ = '/';
while( *name )
*s++ = *name++; /* имя программы */
*s = '\0';
return ( *path ? ++path /* пропустив LIM */ : NULL );
}









А. Богатырев, 1992-95 - 276 - Си в UNIX

/* Запуск командного файла при помощи вызова интерпретатора */
void callshell(progr, av, envp) char *progr, **av, **envp;
{
register i; char *sh; char *newav[MAXARGS+2];
int fd; char first = 0;

if((fd = open(progr, O_RDONLY)) < 0 )
sh = "/bin/sh";
else{
read(fd, &first, 1); close(fd);
sh = (first == '#') ? "/bin/csh" : "/bin/sh";
}
newav[0] = "Shellscript"; newav[1] = progr;
for(i=1; av[i]; i++)
newav[i+1] = av[i];
newav[i+1] = NULL;
printf( "Вызываем %s\n", sh );
execve(sh, newav, envp);
}


/* Ожидать окончания всех процессов, выдать причины смерти. */
void dowait(){
int ws; int pid;

while((pid = wait( &ws)) > 0 ){
if( WIFEXITED(ws)){
printf( "Процесс %d умер с кодом %d\n",
pid, WEXITSTATUS(ws));
}else if( WIFSIGNALED(ws)){
printf( "Процесс %d убит сигналом %d\n",
pid, WTERMSIG(ws));
if(WCOREDUMP(ws)) printf( "Образовался core\n" );
/* core - образ памяти процесса для отладчика adb */
}else if( WIFSTOPPED(ws)){
printf( "Процесс %d остановлен сигналом %d\n",
pid, WSTOPSIG(ws));
}
}
}
























А. Богатырев, 1992-95 - 277 - Си в UNIX

/* Расширение шаблонов имен. Это упрощенная версия, которая
* расширяет имена только в текущем каталоге.
*/
void glob(dir, args, indx, str /* что расширять */, quote )
char *args[], *dir; int *indx; char *str;
char quote; /* кавычки, в которые заключена строка str */
{
static char globchars[] = "*?[";
char *p; char **start = &args[ *indx ];
short nglobbed = 0;

register struct dirent *dirbuf;
DIR *fd; extern DIR *opendir();

/* Затычка для отмены глоббинга: */
if( *str == '\\' ){ str++; goto noGlob; }

/* Обработка переменных $NAME */
if( *str == '$' && quote != '\'' ){
char *s = getenv(str+1);
if( s ) str = s;
}
/* Анализ: требуется ли глоббинг */
if( quote ) goto noGlob;
for( p=str; *p; p++ ) /* Есть ли символы шаблона? */
if( strchr(globchars, *p))
goto doGlobbing;
noGlob:
args[ (*indx)++ ] = strdup(str);
return;


doGlobbing:
if((fd = opendir (dir)) == NULL){
fprintf(stderr, "Can't read %s\n", dir); return;
}
while ((dirbuf = readdir (fd)) != NULL ) {
if (dirbuf->d_ino == 0) continue;
if (strcmp (dirbuf->d_name, ".") == 0 ||
strcmp (dirbuf->d_name, "..") == 0) continue;
if( match( dirbuf->d_name, str)){
args[ (*indx)++ ] = strdup(dirbuf->d_name);
nglobbed++;
}
}
closedir(fd);
if( !nglobbed){
printf( "%s: no match\n", str);
goto noGlob;
}else{ /* отсортировать */
qsort(start, nglobbed, sizeof (char *), cmps);
}
}











А. Богатырев, 1992-95 - 278 - Си в UNIX

/* Разбор командной строки */
int parse(s) register char *s;
{
int i; register char *p;
char tmp[80]; /* очередной аргумент */
char c;

/* очистка старых аргументов */
for(i=0; arg[i]; i++) free(arg[i]), arg[i] = NULL;
if( fin ) free(fin ), fin = NULL;
if( fout ) free(fout), fout = NULL;
rout = 0;


/* разбор строки */
for( i=0 ;; ){
char quote = '\0';

/* пропуск пробелов - разделителей слов */
while((c = *s) && isspace(c)) s++;
if( !c ) break;
/* очередное слово */
p = tmp;
if(*s == '\'' || *s == '"' ){
/* аргумент в кавычках */
quote = *s++; /* символ кавычки */
while((c = *s) != '\0' && c != quote){
if( c == '\\' ){ /* заэкранировано */
c = *++s;
if( !c ) break;
}
*p++ = c; ++s;
}
if(c == '\0')
fprintf(stderr, "Нет закрывающей кавычки %c\n", quote);
else s++; /* проигнорировать кавычку на конце */




























А. Богатырев, 1992-95 - 279 - Си в UNIX

} else
while((c = *s) && !isspace(c)){
if(c == '\\') /* заэкранировано */
if( !(c = *++s))
break /* while */;
*p++ = c; s++;
}
*p = '\0';
/* Проверить, не есть ли это перенаправление
* ввода/вывода. В отличие от sh и csh
* здесь надо писать >ФАЙЛ <ФАЙЛ
* >< вплотную к имени файла.
*/
p = tmp; /* очередное слово */
if( *p == '>'){ /* перенаправлен вывод */
p++;
if( fout ) free(fout), rout = 0; /* уже было */
if( *p == '>' ){ rout |= APPEND; p++; }
if( *p == '&' ){ rout |= ERRTOO; p++; }
if( !*p ){
fprintf(stderr, "Нет имени для >\n");
fout = NULL; rout = 0;
} else fout = strdup(p);
} else if( *p == '<' ){ /* перенаправлен ввод */
p++;
if( fin ) free(fin); /* уже было */
if( !*p ){
fprintf(stderr, "Нет имени для <\n");
fin = NULL;
} else fin = strdup(p);
} else /* добавить имена к аргументам */
glob( ".", arg, &i, p, quote );
}
arg[i] = NULL; return i;
}


/* Установить имя пользователя */
void setuser(){
int uid = getuid(); /* номер пользователя, запустившего Шелл */
char *user = "mr. Nobody"; /* имя пользователя */
char *home = "/tmp"; /* его домашний каталог */
struct passwd *pp = getpwuid( uid );
if( pp != NULL ){
if(pp->pw_name && *pp->pw_name ) user = pp->pw_name;
if( *pp->pw_dir ) home = pp->pw_dir;
}
setenv("USER", user); setenv("HOME", home);
}


void setcwd(){ /* Установить имя текущего каталога */
char cwd[512];
getwd(cwd); setenv( "CWD", cwd );
}









А. Богатырев, 1992-95 - 280 - Си в UNIX

void main(ac, av, ev) char *av[], *ev[]; {
int argc; /* количество аргументов */
char *prompt; /* приглашение */

setuser(); setcwd();
signal(SIGINT, SIG_IGN);
setbuf(stdout, NULL); /* отменить буферизацию */
for(;;){
prompt = getenv( "prompt" ); /* setenv prompt -->\ */
printf( prompt ? prompt : "@ ");/* приглашение */
if( gets(cmd) == NULL /* at EOF */ ) exit(0);
argc = parse(cmd);
if( !argc) continue;
if( !strcmp(arg[0], "exit" )) exit(0);


if( !strcmp(arg[0], "cd" )){
char *d = (argc==1) ? getenv("HOME"):arg[1];
if(chdir(d) < 0)
printf( "Не могу войти в %s\n", d );
else setcwd();
continue;
}


if( !strcmp(arg[0], "echo" )){
register i; FILE *fp;
if( fout ){
if((fp = fopen(fout, rout & APPEND ? "a":"w"))
== NULL) continue;
} else fp = stdout;
for(i=1; i < argc; i++ )
fprintf( fp, "%s%s", arg[i], i == argc-1 ? "\n":" ");
if( fp != stdout ) fclose(fp);
continue;
}


if( !strcmp(arg[0], "setenv" )){
if( argc == 1 ) printenv();
else if( argc == 2 ) setenv( arg[1], "" );
else setenv( arg[1], arg[2]);
continue;
}
cmdExec(arg[0], (char **) arg, environ, fin, fout, rout);
}
}

















А. Богатырев, 1992-95 - 281 - Си в UNIX

/* -----------------------------------------------------------*/
/* Отсортировать и напечатать окружение */
void printenv(){
char *e[40]; register i = 0; char *p, **q = e;

do{
p = e[i] = environ[i]; i++;
} while( p );

#ifdef SORT
qsort( e, --i /* сколько */, sizeof(char *), cmps);
#endif
while( *q )
printf( "%s\n", *q++ );
}


/* Сравнение имени переменной окружения с name */
static char *envcmp(name, evstr) char *name, *evstr;
{
char *p; int code;
if((p = strchr(evstr, '=')) == NULL ) return NULL; /* error ! */
*p = '\0'; /* временно */
code = strcmp(name, evstr);
*p = '='; /* восстановили */
return code==0 ? p+1 : NULL;
}


/* Установить переменную окружения */
void setenv( name, value ) char *name, *value;
{
static malloced = 0; /* 1, если environ перемещен */
char *s, **p, **newenv;
int len, change_at = (-1), i;

/* Есть ли переменная name в environ-е ? */
for(p = environ; *p; p++ )
if(s = envcmp(name, *p)){ /* уже есть */
if((len = strlen(s)) >= strlen(value)){
/* достаточно места */
strcpy(s, value); return;
}
/* Если это новый environ ... */
if( malloced ){
free( *p ); *p = str3spl(name, "=", value);
return;
}
/* иначе создаем копию environ-а */
change_at = p - environ; /* индекс */
break;
}












А. Богатырев, 1992-95 - 282 - Си в UNIX

/* Создаем копию environ-а. Если change_at == (-1), то
* резервируем новую ячейку для еще не определенной переменной */
for(p=environ, len=0; *p; p++, len++ );
/* вычислили количество переменных */
if( change_at < 0 ) len++;
if((newenv = (char **) malloc( sizeof(char *) * (len+1)))
== (char **) NULL) return;
for(i=0; i < len+1; i++ ) newenv[i] = NULL; /* зачистка */
/* Копируем старый environ в новый */
if( !malloced ) /* исходный environ в стеке (дан системой) */
for(i=0; environ[i]; i++ ) newenv[i] = strdup(environ[i]);
else for(i=0; environ[i]; i++ ) newenv[i] = environ[i];
/* Во втором случае строки уже были спасены, копируем ссылки */


/* Изменяем, если надо: */
if( change_at >= 0 ){
free( newenv[change_at] );
newenv[change_at] = str3spl(name, "=", value);
} else { /* добавить в конец новую переменную */
newenv[len-1] = str3spl(name, "=", value);
}
/* подменить environ */
if( malloced ) free( environ );
environ = newenv; malloced++;
qsort( environ, len, sizeof(char *), cmps);
}


/* Допишите команды:
unsetenv имя_переменной - удаляет переменную среды;
exit N - завершает интерпретатор с
кодом возврата N (это целое число);
*/






























А. Богатырев, 1992-95 - 283 - Си в UNIX

    7. Текстовая обработка.


Под "текстовой обработкой" (в противовес "вычислительным задачам") здесь понима-
ется огромный класс задач обработки информации нечислового характера, например редак-
тирование текста, форматирование документов, поиск и сортировка, базы данных, лекси-
ческий и синтаксический анализ, печать на принтере, преобразование формата таблиц,
и.т.п.

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

7.2. Напишите программу, которая удваивает пробелы в тексте с одиночными пробелами.

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

7.4. Напишите программу подсчета слов в файле. Слово определите как последователь-
ность символов, не включающую символы пробела, табуляции или новой строки. "Канони-
ческий" вариант решения, приведенный у Кернигана и Ритчи, таков:

#include <ctype.h>
#include <stdio.h>
const int YES=1, NO=0;
main(){
register int inWord = NO; /* состояние */
int words = 0, c;
while((c = getchar()) != EOF)
if(isspace(c) || c == '\n') inWord = NO;
else if(inWord == NO){
inWord = YES; ++words;
}
printf("%d слов\n", words);
}

Обратите внимание на конструкцию const. Это объявление имен как констант. Эта конст-
рукция близка к
#define YES 1
но позволяет компилятору
- более строго проверять тип, т.к. это типизированная константа;
- создавать более экономный код;
- запрещает изменять это значение.
Рассмотрим пример

main(){ /* cc 00.c -o 00 -lm */
double sqrt(double);
const double sq12 = sqrt(12.0);
#define SQRT2 sqrt(2.0)
double x;
x = sq12 * sq12 * SQRT2 * SQRT2; /* @1 */
sq12 = 3.4641; /* @2 */
printf("%g %g\n", sq12, x);
}

Использование #define превратит строку @1 в

x = sq12 * sq12 * sqrt(2.0) * sqrt(2.0);

то есть создаст код с двумя вызовами функции sqrt. Конструкция же const заносит
вычисленное выражение в ячейку памяти и далее просто использует ее значение. При этом



А. Богатырев, 1992-95 - 284 - Си в UNIX

компилятор не позволяет впоследствии изменять это значение, поэтому строка @2 оши-
бочна.
Теперь предложим еще одну программу подсчета слов, где слово определяется макро-
сом isWord, перечисляющим буквы допустимые в слове. Программа основана на переключа-
тельной таблице функций (этот подход применим во многих случаях):

#include <ctype.h>
#include <stdio.h>
int wordLength, inWord, words; /* = 0 */
char aWord[128], *wrd;

void space (c){}
void letter (c){ wordLength++; *wrd++ = c; }
void begWord(c){ wordLength=0; inWord=1;
wrd=aWord; words++; letter(c); }
void endWord(c){ inWord=0; *wrd = '\0';
printf("Слово '%s' длины %d\n",
aWord, wordLength); }
void (*sw[2][2])() = {
/* !isWord */ { space, endWord },
/* isWord */ { begWord, letter }
/* !inWord inWord */
};
#define isWord(c) (isalnum(c) || c=='-' || c=='_')

main(){ register c;
while((c = getchar()) != EOF)
(*sw[isWord(c)][inWord])(c);
printf("%d слов\n", words);
}


7.5. Напишите программу, выдающую гистограмму длин строк файла (т.е. таблицу: строк
длины 0 столько-то, длины 1 - столько-то, и.т.п., причем таблицу можно изобразить
графически).

7.6. Напишите программу, которая считывает слово из файла in и записывает это слово
в конец файла out.

7.7. Напишите программу, которая будет печатать слова из файла ввода, причем по
одному на строку.

7.8. Напишите программу, печатающую гистограмму длин слов из файла ввода.

7.9. Напишите программу, читающую слова из файла и размещающую их в виде двунаправ-
ленного списка слов, отсортированного по алфавиту. Указания: используйте динамическую
память (malloc) и указатели; напишите функцию включения нового слова в список на нуж-
ное место.
В конце работы распечатайте список дважды: в прямом и в обратном порядке.
Усложнение: не хранить в списке дубликаты; вместо этого вместе со словом хранить
счетчик количества его вхождений в текст.

7.10. Напишите программу, которая печатает слова из своего файла ввода, расположен-
ные в порядке убывания частоты их появления. Перед каждым словом напечатайте число
частоты его появления.

7.11. Напишите программу, читающую файл построчно и печатающую слова в каждой строке
в обратном порядке.






А. Богатырев, 1992-95 - 285 - Си в UNIX

7.12. Напишите программу копирования ввода на вывод таким образом, чтобы из каждой
группы последовательно одинаковых строк выводилась только одна строка. Это аналог
программы uniq в системе UNIX. Ответ:

#include <stdio.h> /* char *gets(); */
char buf1[4096], buf2[4096];
char *this = buf1, *prev = buf2;
main(){
long nline =0L; char *tmp;
while( gets(this)){
if(nline){ /* сравнить новую и предыдущую строки */
if( strcmp(this, prev)) /* различны ? */
puts(prev);
}
/* обмен буферов: */ tmp=prev; prev=this; this=tmp;