4.20.

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

4.21.

Напишите программу, которая выдает n-ую строку файла. Номер строки и имя файла задаются как аргументы main().

4.22.

Напишите программу


    slice -сКакой +сколько файл

которая выдает сколько строк файла файл, начиная со строки номер сКакой (нумерация строк с единицы).

    #include <stdio.h>

    #include <ctype.h>

    long line, count, nline, ncount; /* нули */

    char buf[512];

    void main(int argc, char **argv){

      char c; FILE *fp;

      argc--; argv++;

      /* Разбор ключей */

      while((c = **argv) == '-' || c == '+'){

        long atol(), val; char *s = &(*argv)[1];

        if( isdigit(*s)){

           val = atol(s);

           if(c == '-')     nline  = val;

           else             ncount = val;

        } else fprintf(stderr,"Неизвестный ключ %s\n", s-1);

        argc--; ++argv;

      }

      if( !*argv ) fp = stdin;

      else if((fp = fopen(*argv, "r")) == NULL){

        fprintf(stderr, "Не могу читать %s\n", *argv);

        exit(1);

      }

    for(line=1, count=0; fgets(buf, sizeof buf, fp); line++){

          if(line >= nline){

             fputs(buf, stdout); count++;

          }

          if(ncount && count == ncount)

             break;

      }

      fclose(fp); /* это не обязательно писать явно */

    }

    /* End_Of_File */

4.23.

Составьте программу, которая распечатывает последние n строк файла ввода.

4.24.

Напишите программу, которая делит входной файл на файлы по n строк в каждом.

4.25.

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

4.26.

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

4.27.

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

  1. Записать текст в файл.
  2. Дописать текст к концу файла.
  3. Просмотреть файл.
  4. Удалить файл.
  5. Закончить работу.

Текст вводится в файл построчно с клавиатуры. Конец ввода - EOF (т.е. CTRL/D), либо одиночный символ '.' в начале строки. Выдавайте число введенных строк.

Просмотр файла должен вестись постранично: после выдачи очередной порции строк выдавайте подсказку


    --more-- _

(курсор остается в той же строке и обозначен подчерком) и ожидайте нажатия клавиши. Ответ 'q' завершает просмотр. Если файл, который вы хотите просмотреть, не существует - выдавайте сообщение об ошибке.

После выполнения действия программа вновь запрашивает имя файла. Если вы ответите вводом пустой строки (сразу нажмете <ENTER>, то должно использоваться имя файла, введенное на предыдущем шаге. Имя файла, предлагаемое по умолчанию, принято писать в запросе в [] скобках.

Введите имя файла [oldfile.txt]: _

Когда вы научитесь работать с экраном дисплея (см. главу "Экранные библиотеки"), перепишите меню и выдачу сообщений с использованием позиционирования курсора в заданное место экрана и с выделением текста инверсией. Для выбора имени файла предложите меню: отсортированный список имен всех файлов текущего каталога (по поводу получения списка файлов см. главу про взаимодействие с UNIX). Просто для распечатки текущего каталога на экране можно также использовать вызов


     system("ls -x");

а для считывания каталога в программу*

    FILE *fp = popen("ls *.c", "r");

    ... fgets(...,fp); ... // в цикле, пока не EOF

    pclose(fp);

(в этом примере читаются только имена .c файлов).

4.28.

Напишите программу удаления n-ой строки из файла; вставки строки после m-ой. К сожалению, это возможно только путем переписывания всего файла в другое место (без ненужной строки) и последующего его переименования.

4.29.

Составьте программу перекодировки текста, набитого в кодировке КОИ-8, в альтернативную кодировку и наоборот. Для этого следует составить таблицу перекодировки из 256 символов: c_new=TABLE[c_old]; Для решения обратной задачи используйте стандартную функцию strchr(). Программа читает один файл и создает новый.

4.30.

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


    #include <fcntl.h>

    #include <stdio.h>

    #define min(a,b)  (((a) < (b)) ? (a) : (b))

    #define KB                   1024  /* килобайт */

    #define PORTION         (20L* KB)  /* < 32768  */

    long    ONEFILESIZE  = (300L* KB);

    extern char    *strrchr(char *, char);

    extern long     atol   (char *);

    extern errno;           /* системный код ошибки  */

    char    buf[PORTION];   /* буфер для копирования */

    void main (int ac, char *av[]) {

        char    name[128], *s, *prog = av[0];

        int     cnt=0, done=0, fdin, fdout;

    /* M_UNIX автоматически определяется

     * компилятором в UNIX */

    #ifndef M_UNIX  /* т.е. MS DOS */

        extern int _fmode; _fmode = O_BINARY;

        /* Задает режим открытия и создания ВСЕХ файлов */

    #endif

        if(av[1] && *av[1] == '-'){ /* размер одного куска */

            ONEFILESIZE = atol(av[1]+1) * KB; av++; ac--;

        }

        if (ac < 2){

          fprintf(stderr, "Usage: %s [-size] file\n", prog);

          exit(1);

        }

        if ((fdin = open (av[1], O_RDONLY)) < 0) {

    fprintf (stderr, "Cannot read %s\n", av[1]); exit (2);

        }

        if ((s = strrchr (av[1], '.'))!= NULL) *s = '\0';

        do { unsigned long sent;

             sprintf (name, "%s.%d", av[1], ++cnt);

             if ((fdout = creat (name, 0644)) < 0) {

    fprintf (stderr, "Cannot create %s\n", name); exit (3);

             }

             sent = 0L; /* сколько байт переслано */

             for(;;){ unsigned isRead, /* прочитано read-ом */

                need = min(ONEFILESIZE - sent, PORTION);

                if( need == 0 ) break;

                sent += (isRead = read (fdin, buf, need));

                errno = 0;

                if (write (fdout, buf, isRead) != isRead &&

                    errno){ perror("write"); exit(4);

                } else if (isRead < need){ done++; break; }

             }

             if(close (fdout) < 0){

                perror("Мало места на диске"); exit(5);

             }

             printf("%s\t%lu байт\n", name, sent);

        } while( !done ); exit(0);

    }

4.31.

Напишите обратную программу, которая склеивает несколько файлов в один. Это аналог команды cat с единственным отличием: результат выдается не в стандартный вывод, а в файл, указанный в строке аргументов последним. Для выдачи в стандартный вывод следует указать имя "-".


    #include <fcntl.h>

    #include <stdio.h>

    void main (int ac, char **av){

        int     i, err = 0;    FILE *fpin, *fpout;

        if (ac < 3) {

           fprintf(stderr,"Usage: %s from... to\n", av[0]);

           exit(1);

        }

        fpout = strcmp(av[ac-1], "-")  ? /* отлично от "-" */

                fopen (av[ac-1], "wb") : stdout;

        for (i = 1; i < ac-1; i++) {

            register int c;

            fprintf (stderr, "%s\n", av[i]);

            if ((fpin = fopen (av[i], "rb")) == NULL) {

                fprintf (stderr, "Cannot read %s\n", av[i]);

                err++; continue;

            }

            while ((c = getc (fpin)) != EOF)

                putc (c, fpout);

            fclose (fpin);

        }

        fclose (fpout); exit (err);

    }

Обе эти программы могут без изменений транслироваться и в MS DOS и в UNIX. UNIX просто игнорирует букву b в открытии файла "rb", "wb". При работе с read мы могли бы открывать файл как


    #ifdef M_UNIX

    # define O_BINARY       0

    #endif

    int fdin = open( av[1], O_RDONLY | O_BINARY);

4.32.

Каким образом стандартный ввод переключить на ввод из заданного файла, а стандартный вывод - в файл? Как проверить, существует ли файл; пуст ли он? Как надо открывать файл для дописывания информации в конец существующего файла? Как надо открывать файл, чтобы попеременно записывать и читать тот же файл? Указание: см. fopen, freopen, dup2, stat. Ответ про перенаправления ввода:


            способ 1        (библиотечные функции)

      #include <stdio.h>

      ...

      freopen( "имя_файла", "r", stdin );

            способ 2        (системные вызовы)

      #include <fcntl.h>

      int fd;

      ...

      fd = open( "имя_файла", O_RDONLY );

      dup2 ( fd, 0 ); /* 0 - стандартный ввод    */

      close( fd );    /* fd больше не нужен - закрыть

           его, чтоб не занимал место в таблице */

            способ 3        (системные вызовы)

      #include <fcntl.h>

      int fd;

      ...

      fd = open( "имя_файла", O_RDONLY );

      close (0);               /* 0 - стандартный ввод */

      fcntl (fd, F_DUPFD, 0 ); /* 0 - стандартный ввод */

      close (fd);

Это перенаправление ввода соответствует конструкции

    $ a.out < имя_файла

написанной на командном языке СиШелл. Для перенаправления вывода замените 0 на 1, stdin на stdout, open на creat, "r" на "w".

Рассмотрим механику работы вызова dup2**:


    new = open("файл1",...); dup2(new, old); close(new);

      таблица открытых

       файлов процесса

        ...##                    ##

    new----##---> файл1    new---##---> файл1

           ##                    ##

    old----##---> файл2    old---##     файл2

           ##                    ##

     0:до вызова     1:разрыв связи old с файл2

       dup2()     (закрытие канала old, если он был открыт)

           ##                    ##

    new----##--*--> файл1   new  ##  *----> файл1

           ##  |                 ##  |

    old----##--*            old--##--*

           ##                    ##

     2:установка old на файл1  3:после оператора close(new);

       на этом dup2 завершен.    дескриптор new закрыт.

Здесь файл1 и файл2 - связующие структуры "открытый файл" в ядре, о которых рассказывалось выше (в них содержатся указатели чтения/записи). После вызова dup2 дескрипторы new и old ссылаются на общую такую структуру и поэтому имеют один и тот же R/Wуказатель. Это означает, что в программе new и old являются синонимами и могут использоваться даже вперемежку:


    dup2(new, old);

    write(new, "a", 1);

    write(old, "b", 1);

    write(new, "c", 1);

запишет в файл1 строку "abc". Программа

    int fd;

    printf( "Hi there\n");

    fd = creat( "newout", 0640 );

    dup2(fd, 1); close(fd);

    printf( "Hey, You!\n");

выдаст первое сообщение на терминал, а второе - в файл newout, поскольку printf выдает данные в канал stdout, связанный с дескриптором 1.

4.33.

Напишите программу, которая будет выдавать подряд в стандартный вывод все файлы, чьи имена указаны в аргументах командной строки. Используйте argc для организации цикла. Добавьте сквозную нумерацию строк и печать номера строки.

4.34.

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


    #include <stdio.h>

    char buf[512], word[] = "#";

    main(){  char *s; int len = strlen(word);

      while((s=fgets(buf, sizeof buf, stdin)) &&

             strncmp(s, word, len));

      fputs(s? s: "Не найдено.\n", stdout);

    }

4.35.

Напишите программу, которая переключает свой стандартный вывод в новый файл имяФайла каждый раз, когда во входном потоке встречается строка вида


            >>>имяФайла

Ответ:

    #include <stdio.h>

    char line[512];

    main(){  FILE *fp = fopen("00", "w");

       while(gets(line) != NULL)

         if( !strncmp(line, ">>>", 3)){

            if( freopen(line+3, "a", fp) == NULL){

              fprintf(stderr, "Can't write to '%s'\n", line+3);

              fp = fopen("00", "a");

            }

         } else fprintf(fp, "%s\n", line);

    }

4.36.

Библиотека буферизованного обмена stdio содержит функции, подобные некоторым системным вызовам. Вот функции - аналоги read и write:

Стандартная функция fread из библиотеки стандартных функций Си предназначена для чтения нетекстовой (как правило) информации из файла:


    int fread(addr, size, count, fp)

       register char *addr; unsigned size, count; FILE *fp;

    {  register c; unsigned ndone=0, sz;

       if(size)

         for( ; ndone < count ; ndone++){

            sz = size;

            do{   if((c = getc(fp)) >= 0 )

                        *addr++ = c;

                  else  return ndone;

            }while( --sz );

         }

       return ndone;

    }

Заметьте, что count - это не количество БАЙТ (как в read), а количество ШТУК размером size байт. Функция выдает число целиком прочитанных ею ШТУК. Существует аналогичная функция fwrite для записи в файл. Пример:

    #include <stdio.h>

    #define MAXPTS 200

    #define N      127

    char filename[] = "pts.dat";

    struct point { int x,y; } pts[MAXPTS], pp= { -1, -2};

    main(){

       int n, i;

       FILE *fp = fopen(filename, "w");

       for(i=0; i < N; i++) /* генерация точек */

          pts[i].x = i, pts[i].y = i * i;

       /* запись массива из N точек в файл */

       fwrite((char *)pts, sizeof(struct point), N, fp);

       fwrite((char *)&pp, sizeof pp,            1, fp);

       fp = freopen(filename, "r", fp);

       /* или fclose(fp); fp=fopen(filename, "r"); */

       /* чтение точек из файла в массив */

       n = fread(pts, sizeof pts[0], MAXPTS, fp);

       for(i=0; i < n; i++)

          printf("Точка #%d(%d,%d)\n",i,pts[i].x,pts[i].y);

    }

Файлы, созданные fwrite, не переносимы на машины другого типа, поскольку в них хранится не текст, а двоичные данные в формате, используемом данным процессором. Такой файл не может быть понят человеком - он не содержит изображений данных в виде текста, а содержит "сырые" байты. Поэтому чаще пользуются функциями работы с текстовыми файлами: fprintf, fscanf, fputs, fgets. Данные, хранимые в виде текста, имеют еще одно преимущество помимо переносимости: их легко при нужде подправить текстовым редактором. Зато они занимают больше места!

Аналогом системного вызова lseek служит функция fseek:


    fseek(fp, offset, whence);

Она полностью аналогична lseek, за исключением возвращаемого ею значения. Она НЕ возвращает новую позицию указателя чтения/записи! Чтобы узнать эту позицию применяется специальная функция


    long ftell(fp);

Она вносит поправку на положение указателя в буфере канала fp. fseek сбрасывает флаг "был достигнут конец файла", который проверяется макросом feof(fp);

4.37.

Найдите ошибку в программе (программа распечатывает корневой каталог в "старом" формате каталогов - с фиксированной длиной имен):

    #include <stdio.h>

    #include <sys/types.h>

    #include <sys/dir.h>

    main(){

      FILE *fp;

      struct direct d;

      char buf[DIRSIZ+1]; buf[DIRSIZ] = '\0';

      fp = fopen( '/', "r" );

      while( fread( &d, sizeof d, 1, fp) == 1 ){

        if( !d.d_ino ) continue;  /* файл стерт */

        strncpy( buf, d.d_name, DIRSIZ);

        printf( "%s\n", buf );

      }

      fclose(fp);

    }

Указание: смотри в fopen(). Внимательнее к строкам и символам! '/' и "/" - это совершенно разные вещи (хотя синтаксической ошибки нет!).

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

4.38.

Функциями

       fputs(    строка, fp);

      printf(    формат, ...);

     fprintf(fp, формат, ...);

невозможно вывести строку формат, содержащую в середине байт '\0', поскольку он служит для них признаком конца строки. Однако такой байт может понадобиться в файле, если мы формируем некоторые нетекстовые данные, например управляющую последовательность переключения шрифтов для принтера. Как быть? Есть много вариантов решения.

Пусть мы хотим выдать в канал fp последовательность из 4х байт "\033e\0\5". Мы можем сделать это посимвольно:


    putc('\033',fp); putc('e',   fp);

    putc('\000',fp); putc('\005',fp);

(можно просто в цикле), либо использовать один из способов:

    fprintf( fp,         "\033e%c\5", '\0');

    write  ( fileno(fp), "\033e\0\5",   4 );

    fwrite ( "\033e\0\5", sizeof(char), 4, fp);

где 4 - количество выводимых байтов.

4.39.

Напишите функции для "быстрого доступа" к строкам файла. Идея такова: сначала прочитать весь файл от начала до конца и смещения начал строк (адреса по файлу) запомнить в массив чисел типа long (точнее, off_t), используя функции fgets() и ftell(). Для быстрого чтения n-ой строки используйте функции fseek() и fgets().


    #include <stdio.h>

    #define MAXLINES 2000  /* Максим. число строк в файле*/

    FILE *fp;              /* Указатель на файл */

    int nlines;            /* Число строк в файле */

    long offsets[MAXLINES];/* Адреса начал строк */

    extern long ftell();/*Выдает смещение от начала файла*/

    char buffer[256];      /* Буфер для чтения строк */

    /* Разметка массива адресов начал строк */

    void getSeeks(){

       int c;

       offsets[0] =0L;

       while((c = getc(fp)) != EOF)

         if(c =='\n') /* Конец строки - начало новой */

            offsets[++nlines] = ftell(fp);

    /* Если последняя строка файла не имеет \n на конце, */

    /* но не пуста, то ее все равно надо посчитать */

       if(ftell(fp) != offsets[nlines])

            nlines++;

       printf( "%d строк в файле\n", nlines);

    }

    char *getLine(n){  /* Прочесть строку номер n */

       fseek(fp, offsets[n], 0);

       return fgets(buffer, sizeof buffer, fp);

    }

    void main(){ /* печать файла задом-наперед */

       int i;

       fp = fopen("INPUT", "r"); getSeeks();

       for( i=nlines-1; i>=0; --i)

            printf( "%3d:%s", i, getLine(i));

    }

* - Функция


   int system(char *команда);

выполняет команду, записанную в строке команда, вызывая для этого интерпретатор команд

   /bin/sh -c "команда"

** dup2 читается как "dup to", в английском жаргоне принято обозначать предлог "to" цифрой 2, поскольку слова "to" и "two" произносятся одинаково: "ту". "From me 2 You". Также 4 читается как "for".

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

Назад | Содержание | Вперед


Используются технологии uCoz