Написание оболочки командной строки с C;пытаясь использовать ncurses / C в первый раз - PullRequest
0 голосов
/ 05 апреля 2011

Я работаю над проектом класса, в котором я должен написать оболочку командной строки со следующими требованиями:

  • Оболочка должна читать буферизованный ввод
  • Буфер должен быть 64 символа
  • Условия ошибки должны быть обработаны
    • Превышен размер буфера
    • Прерывания (когда поступает сигнал) - см. Справочную страницу для чтения ()
    • Неверный ввод (неразборчивые символы, пустые строки и т. Д.)
    • Любая другая ошибка, с которой можно столкнуться.
  • Оболочка должна иметь историю не менее 20 предметов, и история не должна иметь статический размер. Когда буфер истории заполнен, самый старый элемент удаляется, а самый новый добавляется.
  • Программы должны запускаться на переднем или заднем плане. (используя &)
  • Ctrl-D выйдет из оболочки
  • Ctrl-C напечатает всю историю
  • Команда «history» также напечатает полную историю. Новейшие предметы будут внизу списка.
  • Все остальные сигналы будут перехвачены и отображены пользователю в оболочке
  • Программа будет использовать команду read () для чтения при вводе, если не поддерживаются клавиши со стрелками

Я решил использовать клавиши со стрелками для циклического изменения истории, поэтому я использую ncurses для ввода, а не read (). Я думаю, что я все делаю правильно, используя strtok () для разбора ввода, а также fork () и execvp () для запуска процессов, но я не делаю все правильно, правильно реализуя ncurses. Все, что я получил до сих пор, - это инициализация нового экрана, отображение подсказки, а затем ошибка при любом нажатии клавиши. Не хорошо.

Я считаю, что проблема должна быть в моем дизайне. Я не слишком хорошо оборачиваю голову вокруг медсестер. Какие структуры данных я должен использовать для этого проекта? Как мне справиться с настройкой ncurses, разбором и всем, что между ними? Как обстоят дела с окнами и экранами, и должен ли я иметь одно глобально доступное окно / экран, с которым я работаю? Кроме того, я пытался использовать char * для входного буфера и char ** для истории команд, но у меня нет опыта работы с C, поэтому, несмотря на чтение по malloc, calloc и realloc, я Не уверен, что лучший способ хранить команды в буфере и истории. Любые советы по управлению этими массивами символов?

tl; dr: Как правильно использовать ncurses для создания оболочки командной строки и как управлять управлением памятью команд с помощью C?

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

edit: я уже видел http://www.gnu.org/software/libc/manual/html_node/Implementing-a-Shell.html и http://www.linuxinfor.com/english/NCURSES-Programming/, но документация ncurses на самом деле имеет слишком много накладных расходов. Я просто хочу использовать его способность распознавать клавиши со стрелками.

Ответы [ 3 ]

1 голос
/ 05 апреля 2011

Вот пример кода, который:

  1. Выполняет динамическое выделение памяти.

  2. Считывает данные с консоли в неблокирующем режиме.

  3. Использует коды VT100 для печати буфера кадров на консоль.

Он компилируется в Linux с использованием GCC без предупреждений или ошибок. Это далеко не ошибка, но она должна дать вам некоторые идеи о том, что возможно. Скомпилируйте и запустите его, нажав [вверх] и [вниз], чтобы напечатать сообщения, набрав символы, и нажав [enter], «выполнит» команду.

#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/** VT100 command to clear the screen. Use puts(VT100_CLEAR_SCREEN) to clear
 *  the screen. */
#define VT100_CLEAR_SCREEN "\033[2J"

/** VT100 command to reset the cursor to the top left hand corner of the
 *  screen. */
#define VT100_CURSOR_TO_ORIGIN "\033[H"

struct frame_s
{
    int x;
    int y;
    char *data;
};

static int draw_frame(struct frame_s *frame)
{
    int row;
    char *data;
    int attrib;

    puts(VT100_CLEAR_SCREEN);
    puts(VT100_CURSOR_TO_ORIGIN);

    for (row = 0, data = frame->data; row  < frame->y; row++, data += frame->x)
    {
        /*  0 for normal, 1 for bold, 7 for reverse. */
        attrib = 0;

        /*  The VT100 commands to move the cursor, set the attribute, and the
         *  actual frame line. */
        fprintf(stdout, "\033[%d;%dH\033[0m\033[%dm%.*s", row + 1, 0, attrib, frame->x, data);
        fflush(stdout);
    }

    return (0);
}

int main(void)
{
    const struct timespec timeout = { .tv_sec = 1, .tv_nsec = 0 };
    struct frame_s frame;
    struct termios tty_old;
    struct termios tty_new;
    unsigned char line[128];
    unsigned int count = 0;
    int ret;
    struct pollfd fds[1];
    sigset_t sigmask;
    struct tm *tp;
    time_t current_time;

    /*  Set up a little frame. */
    frame.x = 80;
    frame.y = 5;
    frame.data = malloc(frame.x * frame.y);

    if (frame.data == NULL)
    {
        fprintf(stderr, "No memory\n");
        exit (1);
    }

    memset(frame.data, ' ', frame.x * frame.y);

    /*  Get the terminal state. */
    tcgetattr(STDIN_FILENO, &tty_old);
    tty_new = tty_old;

    /*  Turn off "cooked" mode (line buffering) and set minimum characters
     *  to zero (i.e. non-blocking). */
    tty_new.c_lflag &= ~ICANON;
    tty_new.c_cc[VMIN] = 0;

    /*  Set the terminal attributes. */
    tcsetattr(STDIN_FILENO, TCSANOW, &tty_new);

    /*  Un-mask all signals while in ppoll() so any signal will cause
     *  ppoll() to return prematurely. */
    sigemptyset(&sigmask);

    fds[0].events = POLLIN;
    fds[0].fd = STDIN_FILENO;

    /*  Loop forever waiting for key presses. Update the output on every key
     *  press and every 1.0s (when ppoll() times out). */
    do
    {
        fds[0].revents = 0;
        ret = ppoll(fds, sizeof(fds) / sizeof(struct pollfd), &timeout, &sigmask);

        if (fds[0].revents & POLLIN)
        {
            ret = read(STDIN_FILENO, &line[count], sizeof(line) - count);

            if (ret > 0)
            {
                line[count + ret] = '\0';

                if (strcmp(&line[count], "\033[A") == 0)
                {
                    snprintf(frame.data, frame.x, "up");
                    count = 0;
                }
                else if (strcmp(&line[count], "\033[B") == 0)
                {
                    snprintf(frame.data, frame.x, "down");
                    count = 0;
                }
                else if (line[count] == 127) // backspace
                {
                    if (count != 0) { count -= ret;}
                }
                else if (line[count] == '\n')
                {
                    snprintf(frame.data, frame.x, "entered: %s", line);
                    count = 0;
                }
                else
                {
                    count += ret;
                }
            }
        }

        /*  Print the current time to the output buffer. */
        current_time = time(NULL);
        tp = localtime(&current_time);
        strftime(&frame.data[1 * frame.x], frame.x, "%Y/%m/%d %H:%M:%S", tp);

        /*  Print the command line. */
        line[count] = '\0';
        snprintf(&frame.data[(frame.y - 1) * frame.x], frame.x, "$ %s", line);

        draw_frame(&frame);
    }
    while (1);

    /*  Restore terminal and free resources. */
    tcsetattr(STDIN_FILENO, TCSANOW, &tty_old);
    free(frame.data);

    return (0);
}
1 голос
/ 05 апреля 2011

Если ваш входной буфер определен как 64 символа, то я бы рекомендовал использовать массив char вместо char*. Что-то вроде char input_buffer[65]; должно служить вашим целям (добавьте дополнительный символ для конечного '\0').

Что касается истории команд, вы можете использовать для этого двумерный массив. Что-то вроде char command_history[20][65]; должно позволить вам хранить 20 старых команд по 64 символа в каждой.

Статическое распределение этих буферов должно немного облегчить вам задачу, поскольку вам не придется беспокоиться о malloc и друзьях.

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

Обновление после опубликованного кода:

Одна проблема, которую я вижу, состоит в том, что функция takeInput не имеет оператора возврата. Когда вы используете input = takeInput(); внутри своей основной функции, значение input не устанавливается на то, что вы думаете. Вероятно, это недопустимый указатель, из-за которого ваша строка с надписью input[j] выходит на segfault.

Ваше использование cmdHistory также нуждается в пересмотре. Вы выделяете его с помощью cmdHistory = (char**)calloc(21,sizeof(int));, что дает вам достаточно места для хранения 21 целого числа. В функции printHistory вы передаете элементы cmdHistory в printw, как если бы они были строками (они являются только целыми числами). Это определенно не делает то, что вы хотите, чтобы это было сделано. Вместо этого ваша логика распределения для cmdHistory должна больше походить на вашу логику отмены распределения (кроме обратной). Выделите массив char**, затем выполните итерацию по массиву, присваивая каждый указатель вновь выделенному буферу. Так же, как у вас есть один оператор free для каждого элемента в массиве плюс free для массива в целом, у вас должен быть один malloc для каждого элемента плюс один malloc для массива в целом.

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

0 голосов
/ 05 апреля 2011

Вы смотрели библиотеку Readline?Идеально подходит для использования в вашем проекте.

http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html

...