антиро- ванно быть корректными на диске, используется открытие файла fd = open( имя, O_RDWR | O_SYNC); которое означает, что при каждом write блок из кэш-буфера немедленно записывается на диск. Это делает работу надежнее, но существенно медленнее. Специальные файлы устройств не могут быть созданы вызовом creat, создающим только обычные файлы. Файлы устройств создаются вызовом mknod: #include <sys/sysmacros.h> dev_t dev = makedev(major, minor); /* (major << 8) | minor */ mknod( имяФайла, кодыДоступа|тип, dev); где dev - пара (мажор,минор) создаваемого устройства; кодыДоступа - коды доступа к файлу (0777)|=; тип - это одна из констант S_IFIFO, S_IFCHR, S_IFBLK из include-файла <sys/stat.h>. mknod доступен для выполнения только суперпользователю (за исключением случая S_IFIFO). Если бы это было не так, то можно было бы создать файл устройства, связан- ный с существующим диском, и читать информацию с него напрямую, в обход механизмов логической файловой системы и защиты файлов кодами доступа. Можно создать файл устройства с мажором и/или минором, не отвечающим никакому реальному устройству (нет такого драйвера или минор слишком велик). Открытие таких ____________________ |= Обычно к блочным устройствам (дискам) доступ разрешается только суперпользова- телю, в противном случае можно прочитать с "сырого" диска (в обход механизмов файло- вой системы) физические блоки любого файла и весь механизм защиты окажется неработаю- щим. А. Богатырев, 1992-95 - 251 - Си в UNIX устройств выдает код ошибки ENODEV. Из нашей программы мы можем вызовом stat() узнать код устройства, на котором расположен файл. Он будет содержаться в поле dev_t st_dev; а если файл является спе- циальным файлом (интерфейсом драйвера устройства), то код самого этого устройства можно узнать из поля dev_t st_rdev; Рассмотрим пример, который выясняет, относятся ли два имени к одному и тому же файлу: #include <sys/types.h> #include <sys/stat.h> void main(ac, av) char *av[]; { struct stat st1, st2; int eq; if(ac != 3) exit(13); stat(av[1], &st1); stat(av[2], &st2); if(eq = (st1.st_ino == st2.st_ino && /* номера I-узлов */ st1.st_dev == st2.st_dev)) /* коды устройств */ printf("%s и %s - два имени одного файла\n",av[1],av[2]); exit( !eq ); } Наконец, вернемся к склейке нескольких файловых систем в одну объединенную иерархию: ino=2 *------ корневая файловая система / \ /\ на диске /dev/hd0 / /\ /\ \ *-/mnt/hd1 : * ino=2 FS на диске /dev/hd1 / \ (removable FS) /\ \ Для того, чтобы поместить корневой каталог файловой системы, находящейся на диске /dev/hd1, вместо каталога /mnt/hd1 уже "собранной" файловой системы, мы должны издать сисвызов mount("/dev/hd1", "/mnt/hd1", 0); Для отключения смонтированной файловой системы мы должны вызвать umount("/dev/hd1"); (каталог, к которому она смонтирована, уже числится в таблице ядра, поэтому его зада- вать не надо). При монтировании все содержимое каталога /mnt/hd1 станет недоступным, зато при обращении к имени /mnt/hd1 мы на самом деле доберемся до (безымянного) кор- невого каталога на диске /dev/hd1. Такой каталог носит название mount point и может быть выявлен по тому признаку, что "." и ".." в нем лежат на разных устройствах: struct stat st1, st2; stat("/mnt/hd1/.", &st1); stat("/mnt/hd1/..", &st2); if( st1.st_dev != st2.st_dev) ... ; /*mount point*/ Для st1 поле st_dev означает код устройства /dev/hd1, а для st2 - устройства, содер- жащего корневую файловую систему. Операции монтирования и отмонтирования файловых систем доступны только суперпользователю. И напоследок - сравнение структур I-узла. на диске в памяти в вызове stat <sys/ino.h> <sys/inode.h> <sys/stat.h> А. Богатырев, 1992-95 - 252 - Си в UNIX struct dinode struct inode struct stat // коды доступа и тип файла ushort di_mode i_mode st_mode // число имен файла short di_nlink i_nlink st_nlink // номер I-узла ushort --- i_number st_ino // идентификатор владельца ushort di_uid i_uid st_uid // идентификатор группы владельца ushort di_gid i_gid st_gid // размер файла в байтах off_t di_size i_size st_size // время создания time_t di_ctime i_ctime st_ctime // время последнего изменения (write) time_t di_mtime i_mtime st_mtime // время последнего доступа (read/write) time_t di_atime i_atime st_atime // устройство, на котором расположен файл dev_t --- i_dev st_dev // устройство, к которому приводит спец.файл dev_t --- i_rdev st_rdev // адреса блоков char di_addr[39] i_addr[] // счетчик ссылок на структуру в ядре cnt_t i_count // и кое-что еще Минусы означают, что данное поле не хранится на диске, а вычисляется ядром. В совре- менных версиях UNIX могут быть легкие отличия от вышенаписанной таблицы. 6.10.1. Напишите программу pwd, определяющую полное имя текущего рабочего каталога. #define U42 определяет файловую систему с длинными именами, отсутствие этого флага - с короткими (14 символов). А. Богатырев, 1992-95 - 253 - Си в UNIX /* Команда pwd. * Текст getwd() взят из исходных текстов библиотеки языка Си. */ #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #define ediag(e,r) (e) /* * getwd() возвращает полное имя текущего рабочего каталога. * При ошибке возвращается NULL, а в pathname копируется сообщение * об ошибке. */ #ifndef MAXPATHLEN #define MAXPATHLEN 128 #endif #define CURDIR "." /* имя текущего каталога */ #define PARENTDIR ".." /* имя родительского каталога */ #define PATHSEP "/" /* разделитель компонент пути */ #define ROOTDIR "/" /* корневой каталог */ #define GETWDERR(s) strcpy(pathname, (s)); #define CP(to,from) strncpy(to,from.d_name,DIRSIZ),to[DIRSIZ]='\0' char *strcpy(char *, char *); char *strncpy(char *, char *, int); char *getwd(char *pathname); static char *prepend(char *dirname, char *pathname); static int pathsize; /* длина имени */ #ifndef U42 char *getwd(char *pathname) { char pathbuf[MAXPATHLEN]; /* temporary pathname buffer */ char *pnptr = &pathbuf[(sizeof pathbuf)-1]; /* pathname pointer */ dev_t rdev; /* root device number */ int fil = (-1); /* directory file descriptor */ ino_t rino; /* root inode number */ struct direct dir; /* directory entry struct */ struct stat d ,dd; /* file status struct */ /* d - "." dd - ".." | dname */ char dname[DIRSIZ+1]; /* an directory entry */ pathsize = 0; *pnptr = '\0'; if (stat(ROOTDIR, &d) < 0) { GETWDERR(ediag("getwd: can't stat /", "getwd: нельзя выполнить stat /")); return (NULL); } rdev = d.st_dev; /* код устройства, на котором размещен корень */ rino = d.st_ino; /* номер I-узла, представляющего корневой каталог */ А. Богатырев, 1992-95 - 254 - Си в UNIX for (;;) { if (stat(CURDIR, &d) < 0) { CantStat: GETWDERR(ediag("getwd: can't stat .", "getwd: нельзя выполнить stat .")); goto fail; } if (d.st_ino == rino && d.st_dev == rdev) break; /* достигли корневого каталога */ if ((fil = open(PARENTDIR, O_RDONLY)) < 0) { GETWDERR(ediag("getwd: can't open ..", "getwd: нельзя открыть ..")); goto fail; } if (chdir(PARENTDIR) < 0) { GETWDERR(ediag("getwd: can't chdir to ..", "getwd: нельзя перейти в ..")); goto fail; } if (fstat(fil, &dd) < 0) goto CantStat; if (d.st_dev == dd.st_dev) { /* то же устройство */ if (d.st_ino == dd.st_ino) { /* достигли корня ".." == "." */ close(fil); break; } do { if (read(fil, (char *) &dir, sizeof(dir)) < sizeof(dir) ){ ReadErr: close(fil); GETWDERR(ediag("getwd: read error in ..", "getwd: ошибка чтения ..")); goto fail; } } while (dir.d_ino != d.st_ino); CP(dname,dir); } else /* ".." находится на другом диске: mount point */ do { if (read(fil, (char *) &dir, sizeof(dir)) < sizeof(dir)) goto ReadErr; if( dir.d_ino == 0 ) /* файл стерт */ continue; CP(dname,dir); if (stat(dname, &dd) < 0) { sprintf (pathname, "getwd: %s %s", ediag ("can't stat", "нельзя выполнить stat"), dname); goto fail; } } while(dd.st_ino != d.st_ino || dd.st_dev != d.st_dev); close(fil); pnptr = prepend(PATHSEP, prepend(dname, pnptr)); } А. Богатырев, 1992-95 - 255 - Си в UNIX if (*pnptr == '\0') /* текущий каталог == корневому */ strcpy(pathname, ROOTDIR); else { strcpy(pathname, pnptr); if (chdir(pnptr) < 0) { GETWDERR(ediag("getwd: can't change back to .", "getwd: нельзя вернуться в .")); return (NULL); } } return (pathname); fail: close(fil); chdir(prepend(CURDIR, pnptr)); return (NULL); } #else /* U42 */ extern char *strcpy (); extern DIR *opendir(); char *getwd (char *pathname) { char pathbuf[MAXPATHLEN];/* temporary pathname buffer */ char *pnptr = &pathbuf[(sizeof pathbuf) - 1];/* pathname pointer */ char *prepend (); /* prepend dirname to pathname */ dev_t rdev; /* root device number */ DIR * dirp; /* directory stream */ ino_t rino; /* root inode number */ struct dirent *dir; /* directory entry struct */ struct stat d, dd; /* file status struct */ pathsize = 0; *pnptr = '\0'; stat (ROOTDIR, &d); rdev = d.st_dev; rino = d.st_ino; for (;;) { stat (CURDIR, &d); if (d.st_ino == rino && d.st_dev == rdev) break; /* reached root directory */ if ((dirp = opendir (PARENTDIR)) == NULL) { GETWDERR ("getwd: can't open .."); goto fail; } if (chdir (PARENTDIR) < 0) { closedir (dirp); GETWDERR ("getwd: can't chdir to .."); goto fail; } А. Богатырев, 1992-95 - 256 - Си в UNIX fstat (dirp -> dd_fd, &dd); if (d.st_dev == dd.st_dev) { if (d.st_ino == dd.st_ino) { /* reached root directory */ closedir (dirp); break; } do { if ((dir = readdir (dirp)) == NULL) { closedir (dirp); GETWDERR ("getwd: read error in .."); goto fail; } } while (dir -> d_ino != d.st_ino); } else do { if ((dir = readdir (dirp)) == NULL) { closedir (dirp); GETWDERR ("getwd: read error in .."); goto fail; } stat (dir -> d_name, &dd); } while (dd.st_ino != d.st_ino || dd.st_dev != d.st_dev); closedir (dirp); pnptr = prepend (PATHSEP, prepend (dir -> d_name, pnptr)); } if (*pnptr == '\0') /* current dir == root dir */ strcpy (pathname, ROOTDIR); else { strcpy (pathname, pnptr); if (chdir (pnptr) < 0) { GETWDERR ("getwd: can't change back to ."); return (NULL); } } return (pathname); fail: chdir (prepend (CURDIR, pnptr)); return (NULL); } #endif А. Богатырев, 1992-95 - 257 - Си в UNIX /* * prepend() tacks a directory name onto the front of a pathname. */ static char *prepend ( register char *dirname, /* что добавлять */ register char *pathname /* к чему добавлять */ ) { register int i; /* длина имени каталога */ for (i = 0; *dirname != '\0'; i++, dirname++) continue; if ((pathsize += i) < MAXPATHLEN) while (i-- > 0) *--pathname = *--dirname; return (pathname); } #ifndef CWDONLY void main(){ char buffer[MAXPATHLEN+1]; char *cwd = getwd(buffer); printf( "%s%s\n", cwd ? "": "ERROR:", buffer); } #endif 6.10.2. Напишите функцию canon(), канонизирующую имя файла, т.е. превращающую его в полное имя (от корневого каталога), не содержащее компонент "." и "..", а также лиш- них символов слэш '/'. Пусть, к примеру, текущий рабочий каталог есть /usr/abs/C- book. Тогда функция преобразует . -> /usr/abs/C-book .. -> /usr/abs ../.. -> /usr ////.. -> / /aa -> /aa /aa/../bb -> /bb cc//dd/../ee -> /usr/abs/C-book/cc/ee ../a/b/./d -> /usr/abs/a/b/d Ответ: #include <stdio.h> /* слэш, разделитель компонент пути */ #define SLASH '/' extern char *strchr (char *, char), *strrchr(char *, char); struct savech{ char *s, c; }; #define SAVE(sv, str) (sv).s = (str); (sv).c = *(str) #define RESTORE(sv) if((sv).s) *(sv).s = (sv).c /* Это структура для использования в таком контексте: void main(){ char *d = "hello"; struct savech ss; SAVE(ss, d+3); *(d+3) = '\0'; printf("%s\n", d); RESTORE(ss); printf("%s\n", d); } */ /* ОТСЕЧЬ ПОСЛЕДНЮЮ КОМПОНЕНТУ ПУТИ */ struct savech parentdir(char *path){ char *last = strrchr( path, SLASH ); А. Богатырев, 1992-95 - 258 - Си в UNIX char *first = strchr ( path, SLASH ); struct savech sp; sp.s = NULL; sp.c = '\0'; if( last == NULL ) return sp; /* не полное имя */ if( last[1] == '\0' ) return sp; /* корневой каталог */ if( last == first ) /* единственный слэш: /DIR */ last++; sp.s = last; sp.c = *last; *last = '\0'; return sp; } #define isfullpath(s) (*s == SLASH) /* КАНОНИЗИРОВАТЬ ИМЯ ФАЙЛА */ void canon( char *where, /* куда поместить ответ */ char *cwd, /* полное имя текущего каталога */ char *path /* исходное имя для канонизации */ ){ char *s, *slash; /* Сформировать имя каталога - точки отсчета */ if( isfullpath(path)){ s = strchr(path, SLASH); /* @ */ strncpy(where, path, s - path + 1); where[s - path + 1] = '\0'; /* или даже просто strcpy(where, "/"); */ path = s+1; /* остаток пути без '/' в начале */ } else strcpy(where, cwd); /* Покомпонентный просмотр пути */ do{ if(slash = strchr(path, SLASH)) *slash = '\0'; /* теперь path содержит очередную компоненту пути */ if(*path == '\0' || !strcmp(path, ".")) ; /* то просто проигнорировать "." и лишние "///" */ else if( !strcmp(path, "..")) (void) parentdir(where); else{ int len = strlen(where); /* добавить в конец разделяющий слэш */ if( where[len-1] != SLASH ){ where[len] = SLASH; where[len+1] = '\0'; } strcat( where+len, path ); /* +len чисто для ускорения поиска * конца строки внутри strcat(); */ } if(slash){ *slash = SLASH; /* восстановить */ path = slash + 1; } } while (slash != NULL); } char cwd[256], input[256], output[256]; void main(){ /* Узнать полное имя текущего каталога. * getcwd() - стандартная функция, вызывающая * через popen() команду pwd (и потому медленная). */ getcwd(cwd, sizeof cwd); while( gets(input)){ canon(output, cwd, input); printf("%-20s -> %s\n", input, output); } } А. Богатырев, 1992-95 - 259 - Си в UNIX В этом примере (изначально писавшемся для MS DOS) есть "странное" место, помеченное /*@*/. Дело в том, что в DOS функция isfullpath была способна распознавать имена фай- лов вроде C:\aaa\bbb, которые не обязательно начинаются со слэша. 6.11. Мультиплексирование ввода-вывода. Данная глава посвящена системному вызову select, который, однако, мы предостав- ляем вам исследовать самостоятельно. Его роль такова: он позволяет опрашивать нес- колько дескрипторов открытых файлов (или устройств) и как только в файле появляется новая информация - сообщать об этом нашей программе. Обычно это бывает связано с дескрипторами, ведущими к сетевым устройствам. 6.11.1. /* Пример использования вызова select() для мультиплексирования * нескольких каналов ввода. Этот вызов можно также использовать * для получения таймаута. * Вызов: войти на терминалах tty01 tty02 и набрать на каждом * sleep 30000 * затем на tty00 сказать select /dev/tty01 /dev/tty02 * и вводить что-либо на терминалах tty01 и tty02 * Сборка: cc select.c -o select -lsocket */ #include <stdio.h> #include <fcntl.h> #include <sys/types.h> /* fd_set, FD_SET, e.t.c. */ #include <sys/param.h> /* NOFILE */ #include <sys/select.h> #include <sys/time.h> #include <sys/filio.h> /* для FIONREAD */ #define max(a,b) ((a) > (b) ? (a) : (b)) char buf[512]; /* буфер чтения */ int fdin, fdout; /* дескрипторы каналов stdin, stdout */ int nready; /* число готовых каналов */ int nopen; /* число открытых каналов */ int maxfd = 0; /* максимальный дескриптор */ int nfds; /* сколько первых дескрипторов проверять */ int f; /* текущий дескриптор */ fd_set set, rset; /* маски */ /* таблица открытых нами файлов */ struct _fds { int fd; /* дескриптор */ char name[30]; /* имя файла */ } fds[ NOFILE ] = { /* NOFILE - макс. число открытых файлов на процесс */ { 0, "stdin" }, { 1, "stdout" }, { 2, "stderr" } /* все остальное - нули */ }; struct timeval timeout, rtimeout; /* выдать имя файла по дескриптору */ char *N( int fd ){ register i; for(i=0; i < NOFILE; i++) if(fds[i].fd == fd ) return fds[i].name; return "???"; } А. Богатырев, 1992-95 - 260 - Си в UNIX void main( int ac, char **av ){ nopen = 3; /* stdin, stdout, stderr */ for( f = 3; f < NOFILE; f++ ) fds[f].fd = (-1); fdin = fileno(stdin); fdout = fileno(stdout); setbuf(stdout, NULL); /* отмена буферизации */ FD_ZERO(&set); /* очистка маски */ for(f=1; f < ac; f++ ) if((fds[nopen].fd = open(av[f], O_RDONLY)) < 0 ){ fprintf(stderr, "Can't read %s\n", av[f] ); continue; } else { FD_SET(fds[nopen].fd, &set ); /* учесть в маске */ maxfd = max(maxfd, fds[nopen].fd ); strncpy(fds[nopen].name, av[f], sizeof(fds[0].name) - 1); nopen++; } if( nopen == 3 ){ fprintf(stderr, "Nothing is opened\n"); exit(1); } FD_SET(fdin, &set); /* учесть stdin */ maxfd = max(maxfd, fdin ); nopen -= 2; /* stdout и stderr не участвуют в select */ timeout.tv_sec = 10; /* секунд */ timeout.tv_usec = 0; /* миллисекунд */ /* nfds - это КОЛИЧЕСТВО первых дескрипторов, которые надо * просматривать. Здесь можно использовать * nfds = NOFILE; (кол-во ВСЕХ дескрипторов ) * или nfds = maxfd+1; (кол-во = номер последнего+1) * ( +1 т.к. нумерация fd идет с номера 0, а количество - с 1). */ nfds = maxfd + 1; while( nopen ){ rset = set; rtimeout = timeout; /* копируем, т.к. изменятся */ /* опрашивать можно FIFO-файлы, терминалы, pty, socket-ы, stream-ы */ nready = select( nfds, &rset, NULL, NULL, &rtimeout ); /* Если вместо &rtimeout написать NULL, то ожидание будет * бесконечным (пока не собьют сигналом) */ if( nready <= 0 ){ /* ничего не поступило */ fprintf(stderr, "Timed out, nopen=%d\n", nopen); continue; } А. Богатырев, 1992-95 - 261 - Си в UNIX /* опрос готовых дескрипторов */ for(f=0; f < nfds; f++ ) if( FD_ISSET(f, &rset)){ /* дескриптор f готов */ int n; /* Вызов FIONREAD позволяет запросить * число байт готовых к передаче * через дескриптор. */ if(ioctl(f, FIONREAD, &n) < 0) perror("FIONREAD"); else printf("%s have %d bytes.\n", N(f), n); if((n = read(f, buf, sizeof buf)) <= 0 ){ eof: FD_CLR(f, &set); /* исключить */ close(f); nopen--; fprintf(stderr, "EOF in %s\n", N(f)); } else { fprintf(stderr, "\n%d bytes from %s:\n", n, N(f)); write(fdout, buf, n); if( n == 4 && !strncmp(buf, "end\n", 4)) /* ncmp, т.к. buf может не оканчиваться \0 */ goto eof; } } } exit(0); } 6.11.2. В качестве самостоятельной работы предлагаем вам пример программы, ведущей протокол сеанса работы. Информацию о псевдотерминалах изучите самостоятельно. А. Богатырев, 1992-95 - 262 - Си в UNIX /* * script.c * Программа получения трассировки работы других программ. * Используется системный вызов опроса готовности каналов * ввода/вывода select() и псевдотерминал (пара ttyp+ptyp). */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <sys/param.h> /* NOFILE */ #include <sys/times.h> #include <sys/wait.h> #include <errno.h> #ifdef TERMIOS # include <termios.h> # define TERMIO struct termios # define GTTY(fd, tadr) tcgetattr(fd, tadr) # define STTY(fd, tadr) tcsetattr(fd, TCSADRAIN, tadr) #else # include <termio.h> # define TERMIO struct termio # define GTTY(fd, tadr) ioctl(fd, TCGETA, tadr) # define STTY(fd, tadr) ioctl(fd, TCSETAW, tadr) #endif А. Богатырев, 1992-95 - 263 - Си в UNIX #ifdef __SVR4 # include <stropts.h> /* STREAMS i/o */ extern char *ptsname(); #endif #if defined(ISC2_2) # include <sys/bsdtypes.h> #else # include <sys/select.h> #endif #ifndef BSIZE # define BSIZE 512 #endif #define LOGFILE "/usr/spool/scriptlog" #define max(a,b) ((a) > (b) ? (a) : (b)) extern int errno; TERMIO told, tnew, ttypmodes; FILE *fpscript = NULL; /* файл с трассировкой (если надо) */ int go = 0; int scriptflg = 0; int halfflag = 0; /* HALF DUPLEX */ int autoecho = 0; char *protocol = "typescript"; #define STDIN 0 /* fileno(stdin) */ #define STDOUT 1 /* fileno(stdout) */ #define STDERR 2 /* fileno(stderr) */ /* какие каналы связаны с терминалом? */ int tty_stdin, tty_stdout, tty_stderr; int TTYFD; void wm_checkttys(){ TERMIO t; tty_stdin = ( GTTY(STDIN, &t) >= 0 ); tty_stdout = ( GTTY(STDOUT, &t) >= 0 ); tty_stderr = ( GTTY(STDERR, &t) >= 0 ); if ( tty_stdin ) TTYFD = STDIN; else if( tty_stdout ) TTYFD = STDOUT; else if( tty_stderr ) TTYFD = STDERR; else { fprintf(stderr, "Cannot access tty\n"); exit(7); } } А. Богатырев, 1992-95 - 264 - Си в UNIX /* Описатель трассируемого процесса */ struct ptypair { char line[25]; /* терминальная линия: /dev/ttyp? */ int pfd; /* дескриптор master pty */ long in_bytes; /* прочтено байт с клавиатуры */ long out_bytes; /* послано байт на экран */ int pid; /* идентификатор процесса */ time_t t_start, t_stop; /* время запуска и окончания */ char *command; /* запущенная команда */ } PP; /* Эта функция вызывается при окончании трассируемого процесса - * по сигналу SIGCLD */ char Reason[128]; void ondeath(sig){ int pid; extern void wm_done(); int status; int fd; /* выявить причину окончания процесса */ while((pid = wait(&status)) > 0 ){ if( WIFEXITED(status)) sprintf( Reason, "Pid %d died with retcode %d", pid, WEXITSTATUS(status)); else if( WIFSIGNALED(status)) { sprintf( Reason, "Pid %d killed by signal #%d", pid, WTERMSIG(status)); #ifdef WCOREDUMP if(WCOREDUMP(status)) strcat( Reason, " Core dumped" ); #endif } else if( WIFSTOPPED(status)) sprintf( Reason, "Pid %d suspended by signal #%d", pid, WSTOPSIG(status)); } wm_done(0); } void wm_init(){ wm_checkttys(); GTTY(TTYFD, &told); /* Сконструировать "сырой" режим для нашего _базового_ терминала */ tnew = told; tnew.c_cc[VINTR] = '\0'; tnew.c_cc[VQUIT] = '\0'; tnew.c_cc[VERASE] = '\0'; tnew.c_cc[VKILL] = '\0'; #ifdef VSUSP tnew.c_cc[VSUSP] = '\0'; #endif А. Богатырев, 1992-95 - 265 - Си в UNIX /* CBREAK */ tnew.c_cc[VMIN] = 1; tnew.c_cc[VTIME] = 0; tnew.c_cflag &= ~(PARENB|CSIZE); tnew.c_cflag |= CS8; tnew.c_iflag &= ~(ISTRIP|ICRNL); tnew.c_lflag &= ~(ICANON|ECHO|ECHOK|ECHOE|XCASE); tnew.c_oflag &= ~OLCUC; /* но оставить c_oflag ONLCR и TAB3, если они были */ /* моды для псевдотерминала */ ttypmodes = told; /* не выполнять преобразования на выводе: * ONLCR: \n --> \r\n * TAB3: \t --> пробелы */ ttypmodes.c_oflag &= ~(ONLCR|TAB3); (void) signal(SIGCLD, ondeath); } void wm_fixtty(){ STTY(TTYFD, &tnew); } void wm_resettty(){ STTY(TTYFD, &told); } /* Подобрать свободный псевдотерминал для трассируемого процесса */ struct ptypair wm_ptypair(){ struct ptypair p; #ifdef __SVR4 p.pfd = (-1); p.pid = 0; p.in_bytes = p.out_bytes = 0; /* Открыть master side пары pty (еще есть slave) */ if((p.pfd = open( "/dev/ptmx", O_RDWR)) < 0 ){ /* Это клонируемый STREAMS driver. * Поскольку он клонируемый, то есть создающий новое псевдоустройство * при каждом открытии, то на master-стороне может быть только * единственный процесс! */ perror( "Open /dev/ptmx" ); goto err; } А. Богатырев, 1992-95 - 266 - Си в UNIX # ifdef notdef /* Сделать права доступа к slave-стороне моими. */ if( grantpt (p.pfd) < 0 ){ perror( "grantpt"); exit(errno); } # endif /* Разблокировать slave-сторону псевдотерминала: позволить первый open() для нее */ if( unlockpt(p.pfd) < 0 ){ perror( "unlockpt"); exit(errno); } /* Получить и записать имя нового slave-устройства-файла. */ strcpy( p.line, ptsname(p.pfd)); #else register i; char c; struct stat st; p.pfd = (-1); p.pid = 0; p.in_bytes = p.out_bytes = 0; strcpy( p.line, "/dev/ptyXX" ); for( c = 'p'; c <= 's'; c++ ){ p.line[ strlen("/dev/pty") ] = c; p.line[ strlen("/dev/ptyp")] = '0'; if( stat(p.line, &st) < 0 ) 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; nline++; /* номер строки */ }/* endwhile */ if( nline ) puts(prev); /* последняя строка всегда выдается */ } 7.13. Составьте программу, которая будет удалять в конце (и в начале) каждой строки файла пробелы и табуляции, а также удалять строки, целиком состоящие из пробелов и табуляций. 7.14. Для экономии места в файле, редакторы текстов при записи отредактированного файла сжимают подряд идущие пробелы в табуляцию. Часто это неудобно для программ обработки текстов (поскольку требует особой обработки табуляций - это ОДИН символ, который на экране и в тексте занимает НЕСКОЛЬКО позиций!), поэтому при чтении файла мы должны расширять табуляции в нужное количество пробелов, например так: /* заменять табуляции на пробелы */ void untab(s) register char *s; { char newstr[256]; /* новая строка */ char *src = s; int n; /* счетчик */ register dstx; /* координата x в новой строке */ for(dstx = 0; *s != '\0'; s++) if( *s == '\t'){ for(n = 8 - dstx % 8 ; n > 0 ; n--) newstr[dstx++] = ' '; }else newstr[dstx++] = *s; newstr[dstx] = '\0'; strcpy(src, newstr); /* строку на старое место */ } 7.15. Напишите обратную функцию, сжимающую подряд идущие пробелы в табуляции. А. Богатырев, 1992-95 - 286 - Си в UNIX void tabify(){ int chr; int icol, ocol; /* input/output columns */ for(icol = ocol = 0; ; ){ if((chr = getchar()) == EOF) break; switch(chr){ case ' ': icol++; break; case '\n': case '\r': ocol = icol = 0; putchar(chr); break; case '\t': icol += 8; icol &= ~07; /* icol -= icol % 8; */ break; default: while(((ocol + 8) & ~07) <= icol){ #ifdef NOTDEF if(ocol + 1 == icol) break; /* взять ' ' вместо '\t' */ #endif putchar('\t'); ocol += 8; ocol &= ~07; } while(ocol < icol){ putchar(' '); ocol++; } putchar(chr); icol++; ocol++; break; } } } 7.16. Составьте программу, укорачивающую строки исходного файла до заданной величины и помещающую результат в указанный файл. Учтите, что табуляция разворачивается в нес- колько пробелов! 7.17. Разработайте программу, укорачивающую строки входного файла до 60 символов. Однако теперь запрещается обрубать слова. А. Богатырев, 1992-95 - 287 - Си в UNIX 7.18. Разработайте программу, заполняющую промежутки между словами строки дополни- тельными пробелами таким образом, чтобы длина строки была равна 60 символам. 7.19. Напишите программу, переносящую слишком длинные строки. Слова разбивать нельзя (неумешающееся слово следует перенести целиком). Ширину строки считать равной 60. 7.20. Составьте программу, центрирующую строки файла относительно середины экрана, т.е. добавляющую в начало строки такое количество пробелов, чтобы середина строки печаталась в 40-ой позиции (считаем, что обычный экран имеет ширину 80 символов). 7.21. Напишите программу, отсекающую n пробелов в начале каждой строки (или n первых любых символов). Учтите, что в файле могут быть строки короче n (например пустые строки). #include <stdio.h> /* ... текст функции untab(); ... */ void process(char name[], int n, int spacesOnly){ char line[256]; int length, shift, nline = 0; char newname[128]; FILE *fpin, *fpout; if((fpin = fopen(name, "r")) == NULL){ fprintf(stderr, "Не могу читать %s\n", name); return; } sprintf(newname, "_%s", name); /* например */ if((fpout = fopen(newname, "w")) == NULL){ fprintf(stderr, "Не могу создать %s\n", newname); fclose(fpin); return; } while(fgets(line, sizeof line, fpin)){ ++nline; if((length = strlen(line)) && line[length-1] == '\n') line[--length] = '\0'; /* обрубить '\n' */ untab(line); /* развернуть табуляции */ for(shift=0; line[shift] != '\0' && shift < n ; ++shift) if(spacesOnly && line[shift] != ' ') break; if(*line && shift != n ) /* Предупреждение */ fprintf(stderr, "Начало строки #%d слишком коротко\n", nline); fprintf(fpout, "%s\n", line+shift); /* нельзя было fputs(line+n, fpout); * т.к. эта позиция может быть ЗА концом строки */ } fclose(fpin); fclose(fpout); } void main(int argc, char **argv){ if( argc != 3 ) exit(1); process(argv[2], atoi(argv[1]) /* 8 */, 1); exit(0); } 7.22. Напишите программу, разбивающую файл на два по вертикали: в первый файл попа- дает левая половина исходного файла, во второй - правая. Ширину колонки задавайте из аргументов main(). Если же аргумент не указан - 40 позиций. 7.23. Напишите программу сортировки строк в алфавитном порядке. Учтите, что функция strcmp() сравнивает строки в порядке кодировки, принятой на данной конкретной машине. Русские буквы, как правило, идут не в алфавитном порядке! Следует написать функцию А. Богатырев, 1992-95 - 288 - Си в UNIX для алфавитного сравнения отдельных символов и, пользуясь ею, переписать функцию strcmp(). 7.24. Отсортируйте массив строк по лексикографическому убыванию, игнорируя различия между строчными и прописными буквами. 7.25. Составьте программу дихотомического поиска в отсортированном массиве строк (методом деления пополам). /* Поиск в таблице методом половинного деления: dihotomia */ #include <stdio.h> struct elem { char *name; /* ключ поиска */ int value; } table[] = { /* имена строго по алфавиту */ { "andrew", 17 }, { "bill", 23 }, { "george", 55 }, { "jack", 54 }, { "jaw", 43 }, { "john", 33 }, { "mike", 99 }, { "paul", 21 }, { "sue", 66 }, /* SIZE - 2 */ { NULL, -1 }, /* SIZE - 1 */ /* NULL введен только для распечатки таблицы */ }; #define SIZE (sizeof(table) / sizeof(struct elem)) /* Дихотомический поиск по таблице */ struct elem *find(s, table, size) char *s; /* что найти ? */ struct elem table[]; /* в чем ? */ int size; /* среди первых size элементов */ { register top, bottom, middle; register code; top = 0; /* начало */ bottom = size - 1; /* конец: индекс строки "sue" */ while( top <= bottom ){ middle = (top + bottom) / 2; /* середина */ /* сравнить строки */ code = strcmp( s, table[middle].name ) ; if( code > 0 ){ top = middle + 1; }else if( code < 0 ){ bottom = middle - 1; }else return &table[ middle ]; } return (struct elem *) NULL; /* не нашел */ } А. Богатырев, 1992-95 - 289 - Си в UNIX /* распечатка таблицы */ void printtable(tbl) register struct elem *tbl; { for( ; tbl->name != NULL ; tbl++ ){ printf( "%-15s %d\n", tbl->name, tbl->value ); } } int main(){ char buf[80]; struct elem *ptr; printtable(table); for(;;){ printf( "-> " ); if( gets( buf ) == NULL) break; /* EOF */ if( ! strcmp( buf, "q" )) exit(0); /* quit: выход */ ptr = find( buf, table, SIZE-1 ); if( ptr ) printf( "%d\n", ptr->value ); else { printf( "--- Не найдено ---\n" ); printtable(table); } } return 0; } 7.26. Напишем функцию, которая преобразует строку так, что при ее печати буквы в ней будут подчеркнуты, а цифры - выделены жирно. Формат текста с выделениями, который создается этим примером, является общепринятым в UNIX и распознается некоторыми прог- раммами: например, программа просмотра файлов less (more) выделяет такие буквы на экране специальными шрифтами или инверсией фона. #define LEN 9 /* потом напишите 256 */ char input[] = "(xxx+yyy)/123.75=?"; char output[LEN]; void main( void ){ int len=LEN, i; void bi_conv(); char c; bi_conv(input, output, &len); if(len > LEN){ printf("Увеличь LEN до %d\n", len); len = LEN; /* доступный максимум */ } for(i=0; i < len && (c = output[i]); ++i) putchar(c); putchar('\n'); } /* Заметьте, что include-файлы не обязательно * должны включаться в самом начале программы! */ #include <stdio.h> #include <ctype.h> #define PUT(c) { count++; \ if(put < *len){ *p++ = (c); ++put;}} #define GET() (*s ? *s++ : EOF) void bi_conv( А. Богатырев, 1992-95 - 290 - Си в UNIX /*IN*/ char *s, /*OUT*/ char *p, /*INOUT*/ int *len ){ int count, put, c; for(count=put=0; (c=GET()) != EOF; ){ /* жирный: C\bC */ /* подчеркнутый: _\bC */ if(isalpha(c)){ PUT('_'); PUT('\b'); } else if(isdigit(c)){ PUT( c ); PUT('\b'); } PUT(c); } PUT('\0'); /* закрыть строку */ *len = count; #undef PUT #undef GET } Напишите программу для подобной обработки файла. Заметим, что для этого не нужны промежуточные строки input и output и построчное чтение файла; все, что надо сделать, это определить #define PUT(c) if(c)putchar(c) #define GET() getchar() Напишите подобную функцию, удваивающую буквы в ссттррооккее. 7.27. Напишите программу, удаляющую из файла выделения. Для этого надо просто уда- лять последовательности вида C\b #include <stdio.h> #define NOPUT (-1) /* не символ ASCII */ /* Названия шрифтов - в перечислимом типе */ typedef enum { NORMAL=1, ITALICS, BOLD, RED=BOLD } font; int ontty; font textfont; /* текущее выделение */ #define setfont(f) textfont=(f) #define getfont() (textfont) #define SetTtyFont(f) if(ontty) tfont(f) /* Установить выделение на экране терминала */ void tfont(font f){ /* только для ANSI терминала */ static font ttyfont = NORMAL; if(ttyfont == f) return; printf("\033[0m"); /* set NORMAL font */ switch(ttyfont = f){ case NORMAL: /* уже сделано выше */ break; case BOLD: printf("\033[1m"); break; case ITALICS: /* use reverse video */ printf("\033[7m"); break; } } void put(int c){ /* Вывод символа текущим цветом */ if(c == NOPUT) return; /* '\b' */ SetTtyFont(getfont()); putchar(c); setfont(NORMAL); /* Ожидать новой C\b посл-ти */ } void main(){ register int c, cprev = NOPUT; /* Стандартный вывод - это терминал ? */ ontty = isatty(fileno(stdout)); setfont(NORMAL); while((c = getchar()) != EOF){ А. Богатырев, 1992-95 - 291 - Си в UNIX if(c == '\b'){ /* выделение */ if((c = getchar()) == EOF) break; if(c == cprev) setfont(BOLD); else if(cprev == '_') setfont(ITALICS); else /* наложение A\bB */ setfont(RED); } else put(cprev); cprev = c; } put(cprev); /* последняя буква файла */ SetTtyFont(NORMAL); } 7.28. Напишите программу печати на принтере листинга Си-программ. Ключевые слова языка выделяйте двойной надпечаткой. Для выдачи на терминал напишите программу, под- черкивающую ключевые слова (подчеркивание - в следующей строке). Упрощение: выде- ляйте не ключевые слова, а большие буквы. Указание: для двойной печати используйте управляющий символ '\r' - возврат к началу той же строки; затем строка печатается повторно, при этом символы, которые не должны печататься жирно, следует заменить на пробелы (или на табуляцию, если этот символ сам есть '\t'). 7.29. Напишите программу, печатающую тексты Си-программ на принтере. Выделяйте клю- чевые слова языка жирным шрифтом, строки "строка", символы 'c' и комментарии - курси- вом. Шрифты для EPSON-FX совместимых принтеров (например EP-2424) переключаются такими управляющими последовательностями (ESC означает символ '\033'): ВКЛЮЧЕНИЕ ВЫКЛЮЧЕНИЕ жирный шрифт (bold) ESC G ESC H утолщенный шрифт (emphasized) ESC E ESC F курсив (italics) ESC 4 ESC 5 подчеркивание (underline) ESC - 1 ESC - 0 повышенное качество печати ESC x 1 ESC x 0 (near letter quality) nlq draft верхние индексы (superscript) ESC S 0 ESC T нижние индексы (subscript) ESC S 1 ESC T сжатый шрифт (17 букв/дюйм) '\017' '\022' (condensed) двойная ширина букв ESC W 1 ESC W 0 (expanded) пропорциональная печать ESC p 1 ESC p 0 (proportional spacing) Можно включить одновременно несколько из перечисленных выше режимов. В каждой из следующих двух групп надо выбрать одно из трех: pitch (плотность печати) pica (10 букв/дюйм) ESC P elite (12 букв/дюйм) ESC M micron (15 букв/дюйм) ESC g font (шрифт) черновик (draft (Roman)) ESC k '\0' текст (text (Sans Serif)) ESC k '\1' курьер (courier) ESC k '\2' Всюду выше 0 означает либо '0' либо '\0'; 1 означает либо '1' либо '\1'. Пример: printf( "This is \033Gboldface\033H word\n"); А. Богатырев, 1992-95 - 292 - Си в UNIX 7.30. Составьте программу вывода набора файлов на печать, начинающую каждый очеред- ной файл с новой страницы и печатающую перед каждым файлом заголовок и номер текущей страницы. Используйте символ '\f' (form feed) для перевода листа принтера. 7.31. Напишите программу печати текста в две колонки. Используйте буфер для форми- рования листа: файл читается построчно (слишком длинные строки обрубать), сначала заполняется левая половина листа (буфера), затем правая. Когда лист полностью запол- нен или файл кончился - выдать лист построчно, расписать буфер пробелами (очистить лист) и повторить заполнение очередного листа. Указание: размеры листа должны переда- ваться как аргументы main(), для буфера используйте двумерный массив букв, память для него заказывайте динамически. Усложнение: не обрубайте, а переносите слишком длинные строки (строка может потребовать даже переноса с листа на лист). /* ПРОГРАММА ПЕЧАТИ В ДВЕ ПОЛОСЫ: pr.c */ #include <stdio.h> #include <string.h> #define YES 1 #define NO 0 #define FORMFEED '\f' #define LINEFEED '\n' extern char *malloc(unsigned); extern char *strchr(char *, char); void untab(register char *s); void resetsheet( void ); void addsheet( char *s, FILE *fpout ); void flushsheet( FILE *fpout ); void printline( int y, char *s, char *attr, FILE *fpout ); void doattr( register char *abuf, register char *vbuf ); void printcopy( FILE *fpin, FILE *fpout ); void main(void); char *strdup (const char *s){ char *p = malloc(strlen(s)+1); strcpy(p,s); return p; /* return strcpy((char *) malloc(strlen(s)+1), s); */ } /* ... текст функции untab() ... */ int Sline; /* строка на листе */ int Shalf; /* половина листа */ int npage; /* номер страницы */ int startpage = 1; /* печать начиная с 1ой страницы */ int fline; /* номер строки файла */ int topline = 0; /* смещение до начала листа */ int halfwidth; /* ширина полулиста */ int twocolumns = YES; /* в две колонки ? */ int lshift, rshift = 1; /* поля слева и справа */ typedef unsigned short ushort; int COLS = 128; /* ширина листа (букв) */ int LINES = 66; /* длина листа (строк) */ ushort *mem; /* буфер листа */ #define AT(x,y) mem[ (x) + (y) * COLS ] /* Выделить буфер под лист и зачистить его */ void resetsheet ( void ){ register x; if( mem == NULL ){ /* выделить память */ А. Богатырев, 1992-95 - 293 - Си в UNIX if ((mem = (ushort *) malloc (COLS * LINES * sizeof(ushort))) == NULL ){ fprintf(stderr, "Out of memory.\n"); exit(1); } } /* очистить */ for( x= COLS * LINES - 1 ; x >= 0 ; x-- ) mem[x] = ' ' & 0xFF; halfwidth = (twocolumns ? COLS/2 : COLS ) - (lshift + rshift ); Sline = topline; Shalf = 0; } #define NEXT_HALF \ if( twocolumns == YES && Shalf == 0 ){ \ /* закрыть данную половину листа */ \ Shalf = 1; /* перейти к новой половине */ \ Sline = topline; \ } else \ flushsheet(fpout) /* напечатать лист */ /* Записать строку в лист */ void addsheet ( char *s, FILE *fpout ) { register x, y; register i; char *rest = NULL; int wrap = NO; /* YES когда идет перенос слишком длинной строки */ /* в какое место поместить строку? */ x = (Shalf == 0 ? 0 : COLS/2) + lshift; y = Sline; i = 0; /* позиция в строке s */ while (*s) { if( *s == '\f' ){ /* вынужденный form feed */ rest = strdup( s+1 ); /* остаток строки */ NEXT_HALF; if( *rest ) addsheet(rest, fpout); free( rest ); return; } if( i >= halfwidth ){ /* перенести длинную строку */ wrap = YES; rest = strdup(s); break; } /* Обработка выделений текста */ if( s[1] == '\b' ){ while( s[1] == '\b' ){ AT(x, y) = (s[0] << 8) | (s[2] & 0xFF); /* overstrike */ s += 2; } s++; x++; i++; } else { AT (x, y) = *s++ & 0xFF; А. Богатырев, 1992-95 - 294 - Си в UNIX x++; i++; } } /* Увеличить строку/половину_листа */ Sline++; if (Sline == LINES) { /* полулист заполнен */ NEXT_HALF; } if( wrap && rest ) { /* дописать остаток строки */ addsheet(rest, fpout); free(rest); } } int again; /* нужна ли повторная надпечатка? */ /* Напечатать заполненный лист */ void flushsheet ( FILE *fpout ){ register x, y, xlast; char *s, *p; static char outbuf[BUFSIZ], attr[BUFSIZ]; /* attr - буфер под атрибуты выделений */ ushort c; if( npage >= startpage ) for (y = 0; y < LINES; y++) { /* обрезать концевые пробелы */ for (xlast = (-1), x = COLS - 1; x >= 0; x--) if (AT (x, y) != ' ') { xlast = x; break; } again = NO; s = outbuf; p = attr; for (x = 0; x <= xlast; x++){ c = AT(x, y); *s++ = c & 0xFF; /* имеет атрибуты ? */ c >>= 8; c &= 0xFF; *p++ = c ? c : ' '; if( c ) again = YES; } *s = '\0'; *p = '\0'; printline(y, outbuf, attr, fpout); } npage++; /* next page */ resetsheet(); /* зачистить новый лист */ } /* Напечатать одну строку листа */ void printline ( int y, char *s, char *attr, FILE *fpout ){ register x; if( again ){ doattr(attr, s); fprintf(fpout, "%s\r", attr ); } fprintf(fpout, "%s", s); /* перевод листа или строки */ fputc( y == LINES-1 ? FORMFEED : LINEFEED, fpout ); } /* Проверить - нет ли атрибутов выделений */ void doattr ( register char *abuf, register char *vbuf ){ for(; *abuf; abuf++, vbuf++ ) if( !strchr(" _-!|\177", *abuf)) *abuf = *vbuf; } А. Богатырев, 1992-95 - 295 - Си в UNIX /* Копирование файла на принтер */ void printcopy ( FILE *fpin, FILE *fpout ) { char inbuf[BUFSIZ]; npage = 1; /* первая страница имеет номер 1 */ fline = 0; /* текущая строка файла - 0 */ resetsheet(); /* зачистить буфер листа */ while( fgets(inbuf, sizeof inbuf - 1, fpin ) != NULL ){ register l = strlen( inbuf ); if( l && inbuf[l-1] == '\n' ) inbuf[--l] = '\0' ; fline++; untab ( inbuf ); addsheet( inbuf, fpout ); } if( !(Sline == topline && Shalf == 0)) /* если страница не была только что зачищена ... */ flushsheet(fpout); fprintf(stderr, "%d строк, %d листов.\n", fline, npage-1); } /* Вызов: pr < файл > /dev/lp */ void main (){ printcopy(stdin, stdout); } Файл-принтер имеет в UNIX имя /dev/lp или подобное ему, а в MS DOS - имя prn. 7.32. Напишите программу, которая построчно считывает небольшой файл в память и печатает строки в обратном порядке. Указание: используйте динамическую память - функции malloc() и strcpy(). Объясним, почему желательно пользоваться динамической памятью. Пусть мы знаем, что строки имеют максимальную длину 80 символов и максимальное количество строк равно 50. Мы могли бы хранить текст в двумерном массиве: char text[50][80]; занимающем 50*80 = 4000 байт памяти. Пусть теперь оказалось, что строки файла в действительности имеют длину по 10 букв. Мы используем 50 * (10 + 1) = 550 байт не используем 4000 - 50 * (10 + 1) = 3450 байт (+1 нужен для символа '\0' на конце строки). Пусть мы теперь пишем char *text[50]; int i=0; и при чтении очередной строки сохраняем ее так: char buffer[81], *malloc(), *gets(); while( gets(buffer) != NULL ){ text[i] = (char *) malloc(strlen(buffer)+1); /* +1 для хранения \0, который не учтен strlen-ом */ strcpy(text[i++], buffer); } то есть заказываем ровно столько памяти, сколько надо для хранения строки и ни байтом больше. Здесь мы (если sizeof(char *)==4) используем А. Богатырев, 1992-95 - 296 - Си в UNIX 50 * 4 + 50 * (10 + 1 + 4) = 950 байт массив указателей + заказанная malloc память (+4 - служебная информация malloc), но зато у нас не остается неиспользуемой памяти. Преимуществом выделения памяти в виде массива является то, что эта память выделится ГАРАНТИРОВАННО, тогда как malloc()-у может не хватить памяти (если мы ее прежде очень много захватывали и не освобождали free()). Если malloc не может выделить участок памяти требуемого размера, он возвращает значение NULL: if((text[i] = malloc(....)) == NULL) { fprintf(stderr, "Мало памяти\n"); break; } Распечатка строк: for(--i; i >= 0; i-- ){ printf("%s\n", text[i]); free( text[i] ); } Функция free(ptr) "освобождает"|- отведенную ранее malloc()ом или calloc()ом область памяти по адресу ptr так, что при новых вызовах malloc() эта область может быть пере- использована. Данные в освобожденной памяти ПОРТЯТСЯ после free(). Ошибочно (и опасно) освобождать память, которая НЕ БЫЛА отведена malloc()-ом! Организация текста в виде массива ссылок на строки или списка ссылок на строки, а не в виде двумерного текстового поля, выгодна еще тем, что такие строки проще переставлять, сортировать, вставлять строку в текст, удалять строку из текста. При этом переставляются лишь указатели в линейном массиве, а сами строки никуда не копи- руются. В двумерном же байтовом массиве нам пришлось бы для тех же перестановок копировать целые массивы байт - строки этой текстовой матрицы. 7.33. Напишите программу, печатающую строки файла в обратном порядке. Не считывать файл целиком в память! Следует использовать метод "обратного чтения" либо метод "быстрого доступа" к строкам файла, описанный в главе "Работа с файлами". ____________________ |- На самом деле все освобожденные куски включаются в список свободной памяти, и склеиваются вместе, если два освобожденных куска оказались рядом. При новых вызовах malloc сначала просматривается список свободной памяти - нет ли там области достаточ- ного размера? Этот алгоритм описан у Кернигана и Ритчи. А. Богатырев, 1992-95 - 297 - Си в UNIX /* Инвертирование порядка строк в файле. * Используется та идея, что файл-результат имеет тот же * размер, что и исходный */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #define BUFS 4096 /* максимальная длина строки */ void main(int argc, char **argv ) { FILE *fp; struct stat st; long len; char buffer[ BUFS+1 ]; FILE *fpnew; /* инверсный файл */ int lgt; if( argc != 2 ){ printf("Error: must be filename\n"); exit(1); } if( (fp= fopen( argv[1], "r" )) == NULL ){ printf( "Can not open %s\n", argv[1] ); exit(2); } stat( argv[1], &st ); /* fstat(fileno(fp), &st); */ len = st.st_size; /* длина фай