Используйте strtok() и выполните команду UNIX в фоновом режиме

Я пытаюсь написать программу C, которая создает оболочку UNIX. В этой оболочке при вводе команды UNIX оболочка должна выполнять ее на переднем плане или в фоновом режиме (в фоновом режиме, когда указано &). Я получаю команду для запуска на переднем плане, но не могу запустить ее в фоновом режиме.

Вот мой код:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define MAX_LENGTH 1024
#define DELIMS " \t\r\n"

int main(int argc, char *argv[])
{
    char *cmd, *bg;
    char line[MAX_LENGTH];
    pid_t fpid,bpid;
    int status;
    while (1)
    {
           fpid=10;
           bpid=10;
            printf("myshell > ");
            if (!fgets(line, MAX_LENGTH, stdin))
                    break;
             int j=0;

            if(cmd = strtok(line, DELIMS))
            {

                    bg = strtok(line," ");
                    while(bg!=NULL)
                    {
                            printf("%s",bg);
                            bg = strtok(NULL, " ");
                            if(strcmp(bg, "&") == 0)
                                    break;
                    }

                    printf("%s", bg);
                    if(strcmp(cmd,"exit")==0)
                            break;

                    else if(strcmp(bg,"&")==0)
                    {
                            bpid=fork();
                            //waitpid(bpid,&status,0);
                            system(line);
                            exit(0);
                    }
                    else
                   {
                            //fpid=fork();
                            //if(fpid==0)
                            //{
                                    system(line);
                            //      exit(0);
                            //}
                            //else 
                            //{
                            //      waitpid(fpid,&status,0);
                            //}
                    }
            }
    }

  return(0);
}

Этот код для моего домашнего задания.


person user2201650    schedule 20.09.2013    source источник
comment
Первое правило использования strtok: не используйте strtok.   -  person Eugene    schedule 20.09.2013
comment
@Eugene, чем мне заменить strtok?   -  person user2201650    schedule 20.09.2013
comment
strtok_r — реентерабельная версия   -  person Eugene    schedule 20.09.2013
comment
Можете ли вы объяснить это с моим кодом?   -  person user2201650    schedule 20.09.2013
comment
В этом коде есть несколько серьезных ошибок компиляции, которые вы должны исправить перед отправкой в ​​SO. Если файл называется fork.c, то GCC 4.8.1 выдает такие ошибки, как: fork.c:33:20: error: incompatible types when assigning to type ‘char *[100]’ from type ‘char *’ на if(cmd = strtok(line, DELIMS)) и fork.c:46:21: warning: passing argument 1 of ‘strcmp’ from incompatible pointer type [enabled by default] на if(strcmp(cmd,"exit")==0) с примечанием /usr/include/string.h:87:6: note: expected ‘const char *’ but argument is of type ‘char **’ int strcmp(const char *, const char *);   -  person Jonathan Leffler    schedule 21.09.2013
comment
Обратите внимание, что strtok() и strtok_r() обе уничтожают строку ввода, что является одной из причин, по которой они не подходят для использования в разборе командной строки оболочки. В вашем коде различные символы, такие как фон &, должны быть отделены от слова до (и после) пробелами, что не требуется для обычных оболочек. Это не должно быть непреодолимым для самодельной оболочки, но не будет работать с POSIX-совместимой оболочкой. Поскольку strtok() обрезает строку, вы не можете использовать строку в system() впоследствии, потому что там остается только первое слово (это будет нормально для ls, но не для ls -l).   -  person Jonathan Leffler    schedule 21.09.2013
comment
@JonathanLeffler, я внес изменения в код выше. Можете ли вы увидеть, что случилось сейчас, пожалуйста?   -  person user2201650    schedule 21.09.2013


Ответы (2)


Прочтите справочную страницу для fork(). Код возврата 0 означает, что вы находитесь в дочернем элементе, ненулевой (неотрицательный) означает, что вы являетесь родителем. У вас должна быть другая логика, основанная на этом, и используйте system() (или лучше exec*() в дочерней ветке.

Вот типичная логика, которая у вас должна быть:

tokenize(line)

if (last token is '&') {
    rc = fork();

    if (rc < 0)
        handle error;

    else if (rc > 0) {  /* in parent, rc = child pid */
        do whatever you planned to do in the parent process
    }
    else {  /* in child */
        use exec*() to start the child command
    }
}
else {  /* foreground execution */
    use system() to run command
}
person Alexander L. Belikoff    schedule 20.09.2013
comment
Большое спасибо, что вернулись ко мне. Я не привык много программировать на C. Не могли бы вы доработать/отредактировать мой код? Программа должна проанализировать пользовательский ввод в оболочке, чтобы получить &, а затем выполнить его в фоновом режиме. - person user2201650; 20.09.2013
comment
Смотрите обновленный комментарий - person Alexander L. Belikoff; 20.09.2013
comment
Итак, в какую часть вписывается код фонового процесса? И как мне разобрать последний токен, т.е. &? - person user2201650; 20.09.2013
comment
1. Ваш фоновый процесс запускается в строке use exec*() для запуска дочерней команды. 2. Чтобы разобрать '&', вам нужно токенизировать команду (а не только пробелами, так как foo& также является допустимой командой) и strcmp(tokens[ntoks - 1], &) - person Alexander L. Belikoff; 20.09.2013
comment
Извините, но я все еще не понимаю :( Не могли бы вы привести пример или отредактировать мой код? - person user2201650; 20.09.2013
comment
Напишите функцию токенизации, которая берет строку и заполняет массив строк. Посмотрите на библиотечную функцию strtok() и запустите ее в цикле. Помните, что он сохраняет состояние (или используйте strtok_r(), который не является таковым). GДля простоты предположим, что каждая лексема отделена пробелом (т. е. вы не будете использовать mycommand&, но вам нужно будет поставить пробел перед амперсандом). Как только вы заполните массив токенов, проверьте, какой последний токен. Вы действительно хотите токенизировать, поскольку семейство вызовов exec() ожидает команду и аргументы в виде массива, а не в виде строки. Если вы все еще не понимаете, вам обязательно нужно прочитать книгу K&R. - person Alexander L. Belikoff; 20.09.2013
comment
Думаю, я понял тебя. Я внес изменения в свой код. Но когда я набираю «ls&», оболочка сама выполняет код на переднем плане. Когда я набираю «ls&», я хочу, чтобы оболочка отображала оболочку › путем выполнения команды в фоновом режиме. Можете ли вы помочь мне здесь, пожалуйста? - person user2201650; 20.09.2013
comment
Убедитесь, что ваша логика закодирована правильно (используйте отладку или printf), чтобы убедиться, что вы пытаетесь создать ls из CHILD. На самом деле, не используйте ls — вместо этого создайте сценарий оболочки со значением sleep 120 и запустите его, чтобы четко видеть, когда он выполняется. - person Alexander L. Belikoff; 21.09.2013
comment
Я попытался сделать printf. в переменной «bg» он принимает «ls» в качестве токена. Я хочу, чтобы он принимал «&». Как я могу это сделать? - person user2201650; 21.09.2013
comment
Извините, если я нажимаю, но может ли кто-нибудь помочь мне здесь? - person user2201650; 21.09.2013
comment
Можете ли вы опубликовать свой код? - person Alexander L. Belikoff; 21.09.2013
comment
Я разместил код выше в первом сообщении. Я внес в него изменения. - person user2201650; 21.09.2013
comment
Я использую код, указанный во втором ответе по этой ссылке: stackoverflow.com/questions/4788374/writing -a-базовая-оболочка. Я хотел бы знать, как выполнить команду UNIX в фоновом режиме в этой программе. - person user2201650; 21.09.2013
comment
Может кто-нибудь помочь мне здесь? - person user2201650; 21.09.2013
comment
Похоже, я столкнулся с новой проблемой. Я получаю сообщение об ошибке сегментации (сброс ядра), когда я набираю ls в командной строке. - person user2201650; 21.09.2013

Вот код, полученный из кода в вопросе, который выдает подсказку, получает строку ввода, разбивает ее на токены, определяет, что последний токен — &, и определяет, что первое слово — exit, и выходит из цикла. Он аккуратно распечатывает то, что нашел. И теперь вам нужно обрабатывать код fork, exec, wait и т. д.

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAX_LENGTH 1024
#define DELIMS " \t\r\n"

int main(void)
{
    char line[MAX_LENGTH];
    char *ps1 = "toysh> ";

    while (fputs(ps1, stdout) > 0 && fgets(line, sizeof(line), stdin) != NULL)
    {
        char *cmd[100];
        char *bg = NULL;
        int j = 0;
        char *tokens = line;

        while ((cmd[j++] = strtok(tokens, DELIMS)) != NULL)
            tokens = NULL;

        assert(j < 100);
        /* The line has been tokenized into j-1 tokens */
        /* Print the tokens found */
        for (int i = 0; i < j; i++)
        {
            if (cmd[i] != 0)
                printf("%d: <<%s>>\n", i, cmd[i]);
            else
                printf("%d: NULL pointer\n", i);
        }

        assert(j > 0);
        if (j == 1)
            continue;   // No command

        j--;

        assert(j > 0);
        if (strcmp(cmd[j-1], "&") == 0)
        {
            printf("== Found &\n");
            bg = cmd[j-1];
            cmd[--j] = 0;
            if (j == 0)
            {
                puts("Syntax error: cannot have & on its own");
                continue;
            }
        }

        if (strcmp(cmd[0], "exit") == 0)
        {
            printf("== Found exit command\n");
            if (bg != NULL)
            {
                puts("Can't run exit in background");
                continue;
            }
            break;
        }

        /*
        ** Now you can do your fork, exec, waitpid work.  Note that the
        ** command is already split into words with the null pointer at
        ** the end.  This is what execv(), execve() and execvp() want
        */

    }
    putchar('\n');

    return(0);
}

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

Запрос на дальнейшую помощь

Я очень новичок в работе с форком и ожиданием. Вы можете помочь мне здесь?

Вам дали хороший совет в другом ответе.

Добавлять:

#include <sys/wait.h>

Добавлять:

static void run_command(char **argv, int bg_flag);

Добавлять:

        /*
        ** Now you can do your fork, exec, waitpid work.  Note that the
        ** command is already split into words with the null pointer at
        ** the end.  This is what execv(), execve() and execvp() want
        */
        run_command(cmd, (bg != NULL));

Новая функция:

static void run_command(char **argv, int bg_flag)
{
    pid_t pid;

    fflush(0);   // Flush pending output

    if ((pid = fork()) < 0)
        printf("Fork failed\n");
    else if (pid > 0)
    {
        /* Parent shell */
        if (bg_flag == 0)
        {
            int status;
            int corpse;
            while ((corpse = waitpid(-1, &status, WNOHANG)) >= 0)
            {
                if (corpse != 0)
                    printf("Process %d exited with status 0x%.4X\n",
                           corpse, status);
                if (corpse == 0 || corpse == pid)
                    break;
            }
        }
        else
            printf("%d: %s running in background\n", pid, argv[0]);
    }
    else
    {
        /* Child process */
        execvp(argv[0], argv);
        fprintf(stderr, "%d: failed to execute %s (%d: %s)", (int)getpid(), argv[0], errno, strerror(errno));
        exit(1);
    }
}

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

Кроме того, все сообщения об ошибках должны отправляться на stderr; Я оставил приличную сумму на stdout.

person Jonathan Leffler    schedule 21.09.2013
comment
Я очень новичок в работе с форком и ожиданием. Вы можете помочь мне здесь? По сути, если введена ls, команда должна выполняться на переднем плане, отображая список файлов, присутствующих в каталоге. Если введено ls &, то команда должна выполняться в фоновом режиме и должна немедленно отображать подсказку myshell › - person user2201650; 21.09.2013
comment
Я предпочел имя toysh (имена, начинающиеся с «мой», меня раздражают, будь то папки с документами, СУБД или оболочки). Смотрите обновления. - person Jonathan Leffler; 22.09.2013
comment
Большое спасибо @JonathanLeffler. Ваш обновленный код отлично работает в фоновом режиме. Но когда я набираю ls и он печатает, ls выполняется в фоновом режиме, а также печатает вывод ls. Я не хочу, чтобы он печатал вывод ls. Как я могу это сделать? - person user2201650; 22.09.2013
comment
Вы должны предоставить стандартный вывод для команды, поэтому после разветвления вы можете закрыть стандартный вывод, а затем open() нулевое устройство, /dev/null, и использовать dup2(), чтобы убедиться, что оно подключено к STDOUT_FILENO (также известному как файловый дескриптор 1) (и close() к файловый дескриптор, если он изначально не был открыт 1), так что дочерний процесс будет записывать в /dev/null. Конечно, если вы выполняете перенаправление ввода/вывода (> some.file), вы открываете этот файл вместо /dev/null. - person Jonathan Leffler; 22.09.2013
comment
Извините, но я много чего не понял. :( потому что я новичок - person user2201650; 23.09.2013
comment
Ничего страшного, если вы их пока не понимаете; все когда-то были новичками. Тем не менее, настало время для вас, чтобы сделать небольшое исследование и обучение. Вы можете использовать материалы POSIX в качестве очень хорошего руководства (хотя и не всегда пояснительного). — но посмотрите информацию внизу страниц). Я дал вам указатели на имена функций, о которых вам нужно узнать. Теперь пришло время для вас, чтобы сделать обучение. Вы также можете просмотреть множество вопросов на SO о реализации оболочки в домашних условиях или использовании каналов. Поиск [c] dup2 должен помочь. - person Jonathan Leffler; 23.09.2013
comment
Как мне получить ваш код для запуска команды jobs? Всякий раз, когда я печатаю задания, он говорит, что не удалось выполнить. Можете ли вы помочь мне здесь, пожалуйста? - person user2201650; 26.09.2013
comment
Команда jobs является встроенной оболочкой; это не внешний исполняемый файл. Со временем вы найдете довольно много таких, в том числе (известно) cd. Вам придется распознать и реализовать jobs самостоятельно. Помимо прочего, это означает, что вам придется вести учет (список) процессов, которые вы запускаете в фоновом режиме (подумайте о необходимых структурах данных), и поддерживать этот список в актуальном состоянии, когда вы признаете, что процессы умерли. - person Jonathan Leffler; 26.09.2013
comment
Значит, я не могу выполнять задания по коду, который вы дали? - person user2201650; 26.09.2013
comment
Если вы можете найти двоичный файл с именем jobs, вы можете его выполнить. Вы, вероятно, не найдете двоичный файл с именем jobs, поэтому не сможете его выполнить. Вам придется встроить его в свою оболочку, как exit встроен в вашу оболочку. - person Jonathan Leffler; 26.09.2013