et, 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++ ){