Проблемы истории для моей собственной оболочки в C

avatar
KILIBIBI
7 апреля 2018 в 22:53
114
1
1

В качестве проекта я должен сделать свою собственную оболочку. Я сделал это, но у меня есть некоторые проблемы с функцией истории.

Вот кусок моего кода:

int main(int argc, char* argv[])
{
    char saisie[300], cwd[1024];
    char* nom = getenv("USER");
    char* backup[MAXCMD];
    int boucle = 1, n = 0, i, u = 0, b = 0;

    for(i = 0; i < MAXCMD; i++) 
    {
        backup[i] = NULL;
    }
    for(i = 0; i < MAX_INPUT_SZ; i++)
    {
        saisie[i] = 0;
    }
    char* cmd[MAXPARAMS];    //MAXPARAMS is 20

    while( boucle == 1)
    {   
        printf("%s@sam:~ %s> ", nom, (getcwd(cwd, sizeof(cwd))));
        fgets(saisie,MAX_INPUT_SZ,stdin);
        printf("\n");
        split_input(saisie, cmd);

        free(backup[u]);
        backup[u] = strdup(saisie);
        u = (u + 1) % MAXCMD;

        b = switchcmd(cmd,backup,b,u);
        start(cmd,b);
        b = 0;                      //débloquage fonction start
    }
    return 0;
}

Я печатаю историю с помощью этой функции:

int historique(char* backup[], int u)
{
    int i = u;
    int place = 1;
    do
    {
        if (backup[i])
        {
            printf("%4d:  %s\n", place, backup[i]);
            place++;
        }
        i = (i + 1) % MAXCMD;
    } while (i != u);
        return 0;   
}

B используется для блокировки функции выполнения (запуска), когда пользователь вводит "cd" или "history", поскольку это вызовет ошибку.

Вот функция, которая срабатывает, когда пользователь вводит "cd", "history" или "exit":

int switchcmd(char** cmd,char** backup, int b,int u)

{
    int i, n = 3, switch_value = 0;
    char* error;
    char* listcmd[n];
    listcmd[0] = "cd";
    listcmd[1] = "exit";
    listcmd[2]  = "history";

for (i = 0; i < n; ++i)
{
    if(strcmp(cmd[0], listcmd[i]) == 0)
    {
        switch_value = i + 1;
        break;
    }
}

switch (switch_value)
{
    case 1:
        chdir(cmd[1]);
        b = 1;
        error = strerror(errno);
        if (*error != 0)
        {
            printf("sam: %s: %s\n", cmd[0], error);
        }
        break;

    case 2:

        printf("Bye bye\n");
        exit(0);

    case 3:
        historique((char**)backup,u);
        b = 1;
        break;
}
return b;
}

Когда я запускаю свою оболочку и последовательно ввожу эти команды, они работают. °i1

> clear    
> ls -a -l
> ls -a
> cd ..
> man chdir

Затем "история" для печати истории, у меня есть это: °i2

   1:  clear
   2:  ls
   3:  ls
   4:  cd
   5:  man
   6:  history

и мне нужен этот вывод со всеми параметрами: °i3

1:  clear
2:  ls -a -l
3:  ls -a
4:  cd ..
5:  man chdir
6:  history`

Я не знаю почему, и я не понимаю, почему strdup не дублирует мой cmd в резервной копии, как это должно быть.

Можете помочь?

Источник
Pablo
7 апреля 2018 в 22:59
0

Здесь char* cmd[strlen(saisie)+1]; — первая проблема: saisie не инициализирован, а strlen(saisie) приводит к неопределенному поведению, поэтому длину определить невозможно, и, следовательно, размер cmd неверен. Из-за этого любой результат, который вы видите, является «мусором».

Charles Duffy
7 апреля 2018 в 23:08
1

См. определение минимального воспроизводимого примера — каждый вопрос должен охватывать только одну проблему и содержать только кратчайший код, необходимый для ответа на эту единственную конкретную проблему. Привлечение чего-либо еще - например, не ожидание фоновых задач - делает ваш вопрос слишком широким, а включение кода, не связанного с тем, что позволяет другим воспроизводить вашу конкретную проблему с историей, выводит ее за пределы определения MCVE.

KILIBIBI
8 апреля 2018 в 00:04
0

@Pablo Я отредактировал программу, но все равно не работает как надо

Pablo
8 апреля 2018 в 00:09
1

Вы обновили свой вопрос, но проблема осталась: char* cmd[strlen(saisie)+1]; приводит к неопределенному поведению, поскольку saisie не инициализирован. Все, что следует далее, является "неправильным" из-за неопределенного поведения.

Austin Stephens
8 апреля 2018 в 00:58
0

Как указывает Пабло, вы не можете использовать strlen() для неинициализированного массива; это просто вернет результаты мусора. Вам придется инициализировать массив чем-то вроде saisie[0] = 0, если вы хотите, чтобы он возвращал 0.

KILIBIBI
8 апреля 2018 в 01:47
0

Я в замешательстве, потому что @Pablo вы указываете на то, что [saisie] не инициализирована, и это причина, почему мой [strlen ()] не работает. Я сделал обновление с циклом для инициализации с 0, но [saisie] все еще не инициализирован. Я понимаю, что мне нужно инициализировать [saisie], но после моего обновления я не вижу, кого я могу снова инициализировать [saisie].

Pablo
8 апреля 2018 в 01:52
1

@KILIBIBI вы инициализируете saisie после того, как вы сделали strlen(saisie). Вы должны сначала инициализировать, затем вы можете сделать char* cmd[strlen(saisie)+1];. Но то, как вы инициализируете saisie, будет пустой строкой, поэтому strlen(saisie) вернет 0, поэтому вы будете выполнять char *cmd[1], и размер не может быть изменен позже. Итак, какой смысл иметь массив размерности 1? У вас тоже проблемы с логикой. Какова цель saisie?

KILIBIBI
8 апреля 2018 в 02:09
0

@Pablo 'saisie' используется для хранения команды, введенной пользователем.

Pablo
8 апреля 2018 в 02:12
0

А в чем смысл cmd? Как я уже сказал, как только вы делаете char* cmd[strlen(saisie)+1];, длина массива фиксируется и больше не может меняться. И в его нынешнем виде (после вашего редактирования) он будет иметь размерность один, поэтому вы можете хранить только указатель 1 на char в cmd. Это не имеет никакого смысла для меня.

KILIBIBI
8 апреля 2018 в 02:28
0

Когда пользовательская команда хранится в 'saisie', эта команда дублируется и разделяется на массив параметров. И я использую «cmd» в функции выполнения с execvp. @Пабло

Jongware
8 апреля 2018 в 10:04
0

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

KILIBIBI
8 апреля 2018 в 17:32
0

@ usr2564301 большое спасибо, теперь у меня есть результат, которого я ожидал!

user3629249
9 апреля 2018 в 16:03
0

для удобства чтения и понимания: 1) имена переменных (и имена параметров) должны указывать usage или content Почти все имена в размещенном коде бессмысленны, даже в текущем контексте 2) следуйте аксиоме: только один оператор в строке и (максимум) одно объявление переменной в операторе. 3) постоянно отступайте код, предлагайте, чтобы каждый уровень отступа был 4 пробела. 4) отдельные кодовые блоки ( for if else while do...while switch case <default) одной пустой строкой

user3629249
9 апреля 2018 в 16:11
0

при вызове strdup() всегда проверяйте (!=NULL) возвращаемое значение, а когда закончите с этой дублированной строкой, передайте указатель на free(), чтобы избежать утечки памяти

Ответы (1)

avatar
Pablo
8 апреля 2018 в 03:02
1

Когда пользовательская команда хранится в 'saisie', эта команда дублируется и разделяется на массив параметров. И я использую 'cmd' в функции выполнения с execvp.

Тогда возникает большая проблема, cmd имеет фиксированную длину 1, если вы используете ее для хранения аргументов команды для execvp, то вы можете сохранить только одну вещь: NULL.

У вас есть два варианта:

  1. Используйте большой фиксированный размер, например char *cmd[100], где вы можете хранить до 99 аргументов и не более. Это самое простое решение, но оно не гибкое. достаточно. Хотя в некоторых системах есть ограничение на количество аргументов, которые вы можете перейти на новый процесс, я не знаю, есть ли ограничение для всех систем, это и это может вам помочь.

  2. Динамически создавать массив указателей char в зависимости от командной строки. Это больше работы, но это также более гибкое решение. При условии, что ваша командная строка не поддерживает каналы (|) и перенаправления (<, <<, >, >>), тогда split_input может выглядеть так:

    char **split_input(const char *cmd)
    {
        if(cmd == NULL)
            return NULL;
    
        char **argv = NULL, **tmp;
    
        char *line = strdup(cmd);
        if(line == NULL)
            return NULL;
    
        const char *delim = " \t\n";
    
        char *token = strtok(line, delim);
        if(token == NULL)
        {
            free(line);
            return NULL;
        }
    
        size_t len = 0;
    
        do {
            char *arg = strdup(token);
            if(arg == NULL)
            {
                free_argv(argv);
                free(line);
                return NULL;
            }
    
            tmp = realloc(argv, (len + 2) * sizeof *argv);
    
            if(tmp == NULL)
            {
                free_argv(argv);
                free(line);
                return NULL;
            }
    
            argv = tmp;
            argv[len++] = arg;
            argv[len] = NULL; // argv must be NULL terminated
        } while(token = strtok(NULL, delim));
    
        free(line);
        return argv;
    }
    
    void free_argv(char **argv)
    {
        if(argv == NULL)
            return;
    
        for(size_t i = 0; argv[i]; ++i)
            free(argv[i]);
        free(argv);
    }
    

    Теперь вы можете использовать его так:

    while( boucle == 1)
    {   
        printf("%s@sam:~ %s> ", nom, (getcwd(cwd, sizeof(cwd))));
        fgets(saisie,MAX_INPUT_SZ,stdin);
        printf("\n");
        char **argv = split_input(saisie);
    
        if(argv == NULL)
        {
            fprintf(stderr, "Cannot split command line, not enough memory\n");
            continue;
        }
    
        free(backup[u]);
        backup[u] = strdup(argv[0]); // <-- passing argv[0], not argv
                                     // but perhaps what you really want
                                     // is strdup(saisie)
        u = (u + 1) % MAXCMD;
    
        b = switchcmd(argv,backup,b,u);
        start(argv,b);
        b = 0;                      //débloquage fonction start
        free_argv(argv);
    }
    

Вы также делаете

backup[u] = strdup(cmd);

но проблема в том, что cmd представляет собой массив указателей char, strdup ожидает const char*, вы передаете неверный тип. Должно быть strdup(cmd[0]) или strdup(saisie), если вы хотите сохранить всю команду.

KILIBIBI
8 апреля 2018 в 09:02
0

Я отредактировал °i1 °i2 °i3. Я попробовал оба варианта, которые вы мне дали, они «работают», но у меня те же проблемы с самого начала. Когда я ввожу те же команды, а затем °i1 с history для печати истории, у меня все еще есть этот вывод °i2 Это моя проблема. Я хочу вывести °i3. С strdup(cmd[0]) и `strdup(saisie)' у меня все еще нет истории со всеми параметрами. @Пабло