Unix (в C) пытается написать хвост для XV6

Привет stackru(ers)!

Я изучаю Unix с использованием ОС XV6 (документация находится здесь) и пытаюсь написать функцию хвоста на C. Ожидаемый результат:

  1. хвост, чтобы дать последние 10 строк файла
  2. tail - указать последние строки файла
  3. хвост... это дать последние 10 строк файлов...
  4. хвост - ... это дать последний из строк...
  5. grep | хвост, чтобы дать последние 10 предложений, в которых содержатся

Я написал две версии tail, одна из которых реализована с использованием char* [], а другая - путем записи в файл и последующего чтения из него (обе опубликованы ниже). Моя версия, которая реализует tail с использованием char* [], кажется более точной для фактическая команда. Однако в версии, где я пишу во временный файл, а затем читаю из него, я получаю больше строк в качестве вывода, и я не уверен, почему это происходит. Я предполагаю, что при чтении из одного файла и записи в другое расположение '\n' запутывается. Я очень признателен за помощь в выяснении этого!

Пожалуйста, не сердитесь на меня, если я делаю что-то глупое. Я новичок в C в Unix и только пытаюсь учиться.

tail.c используя char* []

#include "types.h"
#include "stat.h"
#include "user.h"
#include "fcntl.h"

char buf [512];

void tail (int fd, int toSub) {
  int n;
  int numLines = 0;
  int linesToPrint = 0;
  char *buffer;
  buffer = (char*) malloc (500000);
  int buffSize = 0;
  while ((n = read(fd, buf, sizeof(buf))) > 0) {
    for (int i = 0; i<n; i++) {
      buffer[buffSize] = (char)buf[i];
      buffSize++;
      if(buf[i] == '\n')
        numLines++;
    }
  }
  if (n < 0) {
    printf (1, "tail: read error \n");
    exit ();
  }
  if (numLines < toSub)
    linesToPrint = 0;
  linesToPrint = numLines - toSub;

  int counter = 0;
  for (int i = 0; i < buffSize; i++) {
    if (counter >= linesToPrint)
      printf(1,"%c",buffer[i]);
    if (buffer[i] == '\n')
      counter++;
  }
  free (buffer);
}

int main (int argc, char *argv[]) {
  int toSub = 10;
  int fd = -1;

  if (argc <= 1) {
    tail (0, toSub);
    exit();
  }
  else if (argc > 1 && argv[1][0] == '-') {
    char getToSub [10];
    for (int k=1; k<strlen(argv[1]); k++) {
      getToSub[k-1] = argv[1][k];
    }
    toSub = (atoi)(getToSub);
  }
  else {
    if((fd = open (argv[1], toSub)) < 0) {
      printf (1, "tail: cannot open %s\n", argv[1]);
      exit ();
    }
    tail (fd, toSub);
    close (fd);
  }
  if (argc > 2) {
    for (int i=2; i<argc; i++) {
      if((fd = open (argv[i], 0)) < 0) {
        printf (1, "tail: cannot open %s\n", argv[i]);
        exit ();
      }
      else {
        tail (fd, toSub);
        close (fd);
      }
    }
  }
  exit();
}

tail.c используя запись

#include "types.h"
#include "stat.h"
#include "user.h"
#include "fcntl.h"

char buf [512];

void tail (int fd, int toSub) {
  int n;
  int numLines;
  int linesToPrint;
  int ptrDump;
  ptrDump = open ("tailDump", O_CREATE | O_RDWR);
  while ((n = read(fd, buf, sizeof(buf))) > 0) {
    write (ptrDump, buf, sizeof(buf));
    for (int i = 0; i<n; i++) {
      if(buf[i] == '\n')
        numLines++;
    }
  }
  if (n < 0) {
    printf (1, "tail: read error \n");
    exit ();
  }
  if (numLines < toSub)
    linesToPrint = 0;
  linesToPrint = numLines - toSub;

  close (ptrDump);
  ptrDump = open ("tailDump", 0);

  int counter = 0;
  while ((n = read(ptrDump, buf, sizeof(buf))) > 0) {
    for (int i = 0; i<n; i++) {
      if (counter > linesToPrint)
        printf(1,"%c",buf[i]);
      if (buf[i] == '\n')
        counter++;
      }
    }
    close (ptrDump);
    unlink("tailDump");
}

int main (int argc, char *argv[]) {
  int toSub = 10;
  int fd = -1;

  if (argc <= 1) {
    tail (0, toSub);
    exit();
  }
  else if (argc > 1 && argv[1][0] == '-') {
    char getToSub [10];
    for (int k=1; k<strlen(argv[1]); k++) {
      getToSub[k-1] = argv[1][k];
    }
    toSub = (atoi)(getToSub);
  }
  else {
    if((fd = open (argv[1], toSub)) < 0) {
      printf (1, "tail: cannot open %s\n", argv[1]);
      exit ();
    }
    tail (fd, toSub);
    close (fd);
  }
  if (argc > 2) {
    for (int i=2; i<argc; i++) {
      if((fd = open (argv[i], 0)) < 0) {
        printf (1, "tail: cannot open %s\n", argv[i]);
        exit ();
      }
      else {
        tail (fd, toSub);
        close (fd);
      }
    }
  }
  exit();
}

У меня есть код на моем Github (находится здесь), а также в tail_using_str.c и tail_using_file.c

2 ответа

Я думаю, что ваша проблема здесь:

  while ((n = read(fd, buf, sizeof(buf))) > 0) {
    write (ptrDump, buf, sizeof(buf));

Вы читаете в n байт, но когда вы пишете, вы пишете sizeof(buf) байт. Другими словами, вы можете написать слишком много байтов.

Может быть, вы хотите это вместо этого:

  while ((n = read(fd, buf, sizeof(buf))) > 0) {
    write (ptrDump, buf, n);
                         ^
                        note

Пожалуйста, не сердитесь на меня, если я делаю что-то глупое. Я новичок в C в Unix и только пытаюсь учиться.

Таким образом, этот ответ, который не является строго необходимым, поскольку на основной вопрос, который вы задали, уже получен ответ. Ваш опубликованный вопрос на самом деле поднимает кучу вопросов, которые не были заданы явно, и я собираюсь ответить здесь.

Ожидаемый результат: ... tail - это дать последнюю из строк файла

По мнению кого? Не в соответствии с POSIX и не в соответствии с UNIX V7, где tail(1) впервые появился.

(А на самом деле tail(1) впервые появился в PWB / UNIX, но это не получило широкого распространения.)

grep | tail чтобы дать последние 10 предложений, в которых содержатся

Вы имеете в виду последние 10 строк, а не предложения. grep не производит предложения.

(За исключением советского Unix, где grep предложений тебе!)

char *buffer;

buffer = (char*) malloc (500000);

Это и следующее exit вызов создать утечку памяти. Вы можете сказать, что это безвредно, так как ОС вернет память при выходе из программы, но она небрежная, и инструменты, такие как Valgrind, будут вызывать вас.

Или free() ваши буферы перед всеми возможными точками выхода из функции, или вместо этого объявите этот буфер в стеке:

char buffer[500000]

Возможно, вы не сможете объявить такой большой буфер в стеке, в зависимости от ограничений xv6. Общий современный предел для размера стека составляет 2 МБ, и это для всего стека, используемого всеми функциями в самой глубокой цепочке вызовов. Это настраивается в современных системах, но может не настраиваться в xv6.

Если вы вынуждены пойти с malloc() вариант, вы можете сделать это в одной строке:

char *buffer = (char*) malloc (500000);

Дополнительно:

  • иметь плохой стиль buf а также buffer, Ленивый. Дайте каждому буферу целевое имя, например lineBuf а также accumBuf

  • buffSize озадаченно назван. Не ясно, к какому буферу он относится, и размер буфера в любом случае не равен. Назовите это как accumBytes решить обе проблемы.

  • Вы скучаете по куче #includes необходимо в современных системах POSIX, и у вас есть некоторые, которые не работают на таких. Я бы посмотрел, есть ли у xv6 stdio.h.h, stdlib.h, string.h а также unistd.h, а также #include их для портативности POSIX. Я также посмотрю, сможете ли вы #includetypes.h с помощью sys/types.h Это необходимо по крайней мере в MacOS и, возможно, в других Unixes. user.h не требуется в современных системах, так что если вам это не нужно на xv6, удалите его.

  • Ваш вариант в памяти считывает весь файл в RAM, а затем пропускает обратно байты в RAM, которые он не хочет печатать. Немного подумав покажет, как вы можете уменьшить размер буфера и не делать два прохода над входными данными. (Подсказка: accumBuf[toSub][sizeof(lineBuf)], Не стесняйтесь умножать второй член на некоторое количество, если вы хотите, чтобы строки больше sizeof(lineBuf) байт.)

if(buf[i] == '\n') numLines++;

Вам, вероятно, следует проверить наличие байта, отличного от '\n', в конце буфера накопления и добавить для него еще одну строку. Строки без LF-терминаторов не совсем кошерны, но пользователь обычно ожидает, что этот завершающий фрагмент рассматривается как строка.

printf (1, "tail: read error \n");

Что это 1, шум? Вы пытаетесь указать stdout? Это верно только для write не printf, printf() уже отправляет stdout, (Действительно, вы должны использовать fprintf() отправлять куда угодно.)

Поскольку это только в ваших случаях ошибок, это означает, что вы не должны проверять наличие ошибок.

Это еще одна причина для написания кода для переносимости POSIX, даже если вы в конечном итоге нацелены xv6: современные компиляторы Unix системы C гораздо строже относятся к коду, который они готовы принять. Современные компиляторы C делают многое из того, что нам полагалось на такие инструменты, как lint в прошлом.

exit()

exit(2) принимает параметр, код состояния выхода, традиционно 0 для чистого выхода и ненулевое значение для ошибки. Единственная причина, по которой ваш компилятор позволяет вам избежать неприятностей, заключается в том, что ранние компиляторы C не строго проверяли список аргументов, приведенный в соответствии с объявленными параметрами функции. Фактически, xv6, вероятно, поставляет компилятор K&R, у которого даже не было прототипов функций для объявления списков параметров. Программист должен был делать правильные вещи без предупреждения.

linesToPrint = numLines - toSub;

Это не "строки для печати", это "строки для пропуска печати". Мне понадобилось 5 минут, чтобы посмотреть на код, чтобы преодолеть это семантическое несоответствие. Компилятору все равно, но имена переменных не для компилятора. Если бы они были только для компилятора, мы бы назвали их всех a, b, так далее.

printf("%c",buffer[i]);

использование putchar() Вот.

int counter = 0;

Опять ленивый. Подсчет чего?

Я только на полпути через первую программу, но достаточно комментариев. Я надеюсь, что вы узнали несколько вещей из этого.

Другие вопросы по тегам