Функция трубы не выполняется должным образом

Я создал следующую программу, чтобы попытаться подключиться к моей собственной оболочке. StringArray – это просто char** я StringArray . Код работает нормально, но когда я помещаю в cat txt.txt | grep a cat txt.txt | grep a , ничего не возвращается к экрану. При отладке я видел, что код, похоже, останавливается примерно на 152 (где находится команда распечатки), где pid==0 и i==0 .

Для контекста я вызываю эту функцию в другой функции после обнаружения канала.

 void doPipe(StringArray sa) { printf("In 69\n"); int filedes[2]; // pos. 0 output, pos. 1 input of the pipe int filedes2[2]; int num_cmds = 0; char *command[256]; pid_t pid; int err = -1; int end = 0; // Variables used for the different loops int i = 0; int j = 0; int k = 0; int l = 0; // First we calculate the number of commands (they are separated // by '|') while (sa[l] != NULL){ if (strcmp(sa[l],"|") == 0){ num_cmds++; } l++; } num_cmds++; // Main loop of this method. For each command between '|', the // pipes will be configured and standard input and/or output will // be replaced. Then it will be executed while (sa[j] != NULL && end != 1){ k = 0; // We use an auxiliary array of pointers to store the command // that will be executed on each iteration while (strcmp(sa[j],"|") != 0){ command[k] = sa[j]; j++; if (sa[j] == NULL){ // 'end' variable used to keep the program from entering // again in the loop when no more arguments are found end = 1; k++; break; } k++; } // Last position of the command will be NULL to indicate that // it is its end when we pass it to the exec function command[k] = NULL; j++; printf("In 121\n"); // Depending on whether we are in an iteration or another, we // will set different descriptors for the pipes inputs and // output. This way, a pipe will be shared between each two // iterations, enabling us to connect the inputs and outputs of // the two different commands. if (i % 2 != 0){ pipe(filedes); // for odd i }else{ pipe(filedes2); // for even i } pid=fork(); if(pid==-1){ if (i != num_cmds - 1){ if (i % 2 != 0){ close(filedes[1]); // for odd i }else{ close(filedes2[1]); // for even i } } printf("Child process could not be created\n"); return; } if(pid==0){ printf("In 148\n"); // If we are in the first command if (i == 0){ printf("In 152\n"); dup2(filedes2[1], STDOUT_FILENO); } // If we are in the last command, depending on whether it // is placed in an odd or even position, we will replace // the standard input for one pipe or another. The standard // output will be untouched because we want to see the // output in the terminal else if (i == num_cmds - 1){ printf("In 162\n"); if (num_cmds % 2 != 0){ // for odd number of commands dup2(filedes[0],STDIN_FILENO); printf("In 166\n"); }else{ // for even number of commands dup2(filedes2[0],STDIN_FILENO); printf("In 166\n"); } // If we are in a command that is in the middle, we will // have to use two pipes, one for input and another for // output. The position is also important in order to choose // which file descriptor corresponds to each input/output }else{ // for odd i if (i % 2 != 0){ dup2(filedes2[0],STDIN_FILENO); dup2(filedes[1],STDOUT_FILENO); }else{ // for even i dup2(filedes[0],STDIN_FILENO); dup2(filedes2[1],STDOUT_FILENO); } } if (execvp(command[0],command)==err){ kill(getpid(),SIGTERM); } } // CLOSING DESCRIPTORS ON PARENT if (i == 0){ close(filedes2[1]); } else if (i == num_cmds - 1){ if (num_cmds % 2 != 0){ close(filedes[0]); }else{ close(filedes2[0]); } }else{ if (i % 2 != 0){ close(filedes2[0]); close(filedes[1]); }else{ close(filedes[0]); close(filedes2[1]); } } waitpid(pid,NULL,0); i++; } } 

Одной из ваших больших проблем может быть waitpid на каждой итерации строительства трубопровода. Ожидание должно быть выполнено в конце (помня о pids в списке).

Мне было сложно понять ваш код, поэтому я сделал некоторое упрощение и очистку. В частности, if (i % 2 ...) повсюду делались все сложнее.

Я очистил и исправил код. Я добавил структуру, чтобы упростить управление [прошу прощения за бесплатную стильную очистку]:

 #include  #include  #include  #include  #include  #include  typedef struct { int pipe_fildes[2]; } pipectl_t; #define CLOSEME(_fd) \ do { \ close(_fd); \ _fd = -1; \ } while (0) void doPipe(char **sa) { pipectl_t pipes[2]; pipectl_t *pipein; pipectl_t *pipeout; pipectl_t *pipetmp; int num_cmds = 0; char *command[256]; pid_t pidlist[256]; pid_t pid; int err = -1; int end = 0; // Variables used for the different loops int icmd = 0; int j = 0; int k = 0; int l = 0; // First we calculate the number of commands (they are separated // by '|') for (int l = 0; sa[l] != NULL; ++l) { if (strcmp(sa[l], "|") == 0) num_cmds++; } num_cmds++; for (int ipipe = 0; ipipe <= 1; ++ipipe) { pipes[ipipe].pipe_fildes[0] = -1; pipes[ipipe].pipe_fildes[1] = -1; } pipein = &pipes[0]; pipeout = &pipes[1]; // Main loop of this method. For each command between '|', the // pipes will be configured and standard input and/or output will // be replaced. Then it will be executed while (sa[j] != NULL && end != 1) { // We use an auxiliary array of pointers to store the command // that will be executed on each iteration k = 0; while (strcmp(sa[j], "|") != 0) { command[k] = sa[j]; j++; k++; if (sa[j] == NULL) { // 'end' variable used to keep the program from entering // again in the loop when no more arguments are found end = 1; break; } } // Last position of the command will be NULL to indicate that // it is its end when we pass it to the exec function command[k] = NULL; j++; // swap input and output, so previous child's output becomes the new // child's input // NOTE: by doing this here, in one place, we eliminate all the i % 2 // if statements pipetmp = pipein; pipein = pipeout; pipeout = pipetmp; // are we the last command? int lastflg = (icmd == (num_cmds - 1)); // last command does _not_ have an output pipe, so don't create one if (! lastflg) pipe(pipeout->pipe_fildes); pid = fork(); // NOTE: fork failure almost never happens and is fatal if (pid == -1) { printf("Child process could not be created\n"); return; } // process child if (pid == 0) { // NOTE: after we've dup'ed a file descriptor, we close it // first command does _not_ have a pipe for input if (icmd > 0) dup2(pipein->pipe_fildes[0],STDIN_FILENO); CLOSEME(pipein->pipe_fildes[0]); // last command does _not_ have a pipe for output if (! lastflg) dup2(pipeout->pipe_fildes[1],STDOUT_FILENO); CLOSEME(pipeout->pipe_fildes[1]); // close the parent sides of the pipes (in this child) // close previous child's output descriptor (the feed for our input) CLOSEME(pipein->pipe_fildes[1]); // close next child's input descriptor (our feed for its input) CLOSEME(pipeout->pipe_fildes[0]); if (execvp(command[0], command) == err) { #if 0 kill(getpid(), SIGTERM); #else exit(1); #endif } } // close all input descriptors for _this_ child CLOSEME(pipein->pipe_fildes[0]); CLOSEME(pipein->pipe_fildes[1]); // close output side of _this_ child's output pipe [which becomes next // child's input pipe] CLOSEME(pipeout->pipe_fildes[1]); pidlist[icmd] = pid; icmd++; } // wait for all pids _after_ the entire pipeline is constructed for (int icmd = 0; icmd < num_cmds; ++icmd) waitpid(pidlist[icmd], NULL, 0); } // main -- main program int main(int argc,char **argv) { char *cp; char *bp; char buf[1000]; char **av; char *avlist[256]; --argc; ++argv; for (; argc > 0; --argc, ++argv) { cp = *argv; if (*cp != '-') break; switch (cp[1]) { default: break; } } while (1) { printf("> "); fflush(stdout); cp = fgets(buf,sizeof(buf),stdin); if (cp == NULL) break; av = avlist; bp = buf; while (1) { cp = strtok(bp," \t\r\n"); bp = NULL; if (cp == NULL) break; *av++ = cp; } *av = NULL; doPipe(avlist); } return 0; } 

ОБНОВИТЬ:

Когда я запускаю этот код, ту же команду cat txt.txt | grep a cat txt.txt | grep a только кажется, что делает первую команду, а не вторую после трубы. (Он выдает txt-файл, но не grep)

Я тестировал всю программу до того, как я опубликовал ее. Я просто повторил проверку с помощью команды cat/grep . Это сработало, но это была моя программа без изменений.

Любые идеи, почему это может произойти? Я применил ваш метод doPipe в своем коде и передал в моем StringArray sa, который также является символом char **.

Мои предложения:

  1. Убедитесь, что моя неизменная версия работает для вас.
  2. Используя doPipe останова gdb на doPipe и посмотрите на аргументы. Для обеих программ они должны быть одинаковыми.
  3. Если StringArray действительно char ** , замените его в своей версии, чтобы убедиться, что это не имеет никакого значения. Это void doPipe(char **sa) и посмотреть, не компилируется ли ваш код. В gdb точке останова вы должны иметь возможность делать ptype sa в обеих программах
  4. StringArray выглядит немного «Java-esque» для меня 🙂 Я бы избегал этого, особенно здесь, поскольку execvp хочет char **
  5. Убедитесь, что sa правильно NULL завершено. Если это не последняя команда в конвейере, может быть фикцией / мусором, а проверка ошибок для неудачного execvp не является надежной.
  6. Убедитесь, что num_cmds одинаково.
  7. Попробуйте cat txt.txt | grep a | sed -es/a/b/ cat txt.txt | grep a | sed -es/a/b/ cat txt.txt | grep a | sed -es/a/b/ . Если вы получаете cat и grep , но не sed , это означает, что num_cmds
  8. Убедитесь, что синтаксический анализ вызывающего абонента в буфере помещает символ "|" в отдельном токене. То есть, этот код работает с cat txt.txt | grep a cat txt.txt | grep a но это не сработает: cat txt.txt|grep a

ОБНОВЛЕНИЕ # 2:

BTW, если код вашего канала все еще не работает (например, последняя команда не выполняется), проверьте, имеет ли последний токен символ новой строки (т.е. строка новой строки не была удалена правильно).

Я пробовал все это, но до сих пор не могу заставить код перенаправления работать с этим. По сути, я смущен тем, где в этом коде я должен проверять «<» или «>»,

Выполнение общего анализа для поддержки перенаправления (например, < или > ), труб (например, | ), нескольких команд на строку (например ; ), встроенных подclassов (например, (echo the date is ; date) и отдельных заданий (например, ) может потребовать немного заботы, и вам нужен многоуровневый подход.

Я подозреваю, что после того, как вы будете работать с трубами и / или redirectм, вам будет поручено реализовать еще больше синтаксиса оболочки. Я делал это раньше, так что, вместо того, чтобы разобраться в этом по частям, вот что вам нужно сделать ...

Вам нужно будет отсканировать входной буфер char-by-char и сохранить токены в структуру «токенов», которая также имеет тип. Вам понадобится связанный список этих структур. Подробнее об этом ниже.

Когда вы сталкиваетесь с цитируемой строкой, вам нужно снять кавычки: "abc" -> abc , помня о беглых кавычках: "ab\"c -> ab"c .

Кроме того, вы должны быть осторожны с цитируемыми строками, примыкающими [то, что perl вызывает] строки «bareword»: echo abc . Если у нас есть abc"d ef"ghi , это необходимо abcd efghi в один токен строки: abcd efghi

Обратные косые черты на перенаправителях также должны учитываться. echo abc > def - это redirect, которое поместит abc в файл def . Но, echo abc \> def должен просто выводить abc > def буквально в stdout. Обратная косая черта на другой «пунктуации» аналогична.

Вам также придется обрабатывать тот факт, что пунктуация не должна иметь пробелов вокруг нее. То есть, echo abc>def должен обрабатываться так же, как если бы это было echo abc > def .

Кроме того, пунктуация внутри строки с кавычками должна обрабатываться так, как если бы она была экранирована выше. То есть, echo abc ">" def не является redirectм, а [снова] следует рассматривать как простую команду.

Кроме того, если текущая строка заканчивается обратным слэшем (например, \ строка \ ), это означает, что следующая строка является «продолжением». Вы должны удалить обратную косую черту и новую строку. Затем прочитайте еще одну строку и продолжайте создавать список токенов.

Кроме того, while & может быть для отдельных заданий, как в: date & , он также может быть частью перенаправления, как в gcc -o myshell myshell.c 2>&1 >logfile

Хорошо, поэтому для управления всем этим нужны типы для токенов и маркерной структуры:

 // token types typedef enum { TOKEN_NORMAL, // simple token/string TOKEN_QUO1, // quoted string TOKEN_QUO2, // quoted string TOKEN_SEMI, // command separater (eg ;) TOKEN_OREDIR, // output redirector (eg >) TOKEN_IREDIR, // input redirector (eg <) TOKEN_PIPE, // pipe separater (eg |) TOKEN_AMP // an & (can be detach or redirect) } toktype_t; // token control typedef struct token token_t; struct token { token_t *tok_next; // forward link token_t *tok_prev; // backward link toktype_t tok_type; // token type char tok_str[256]; // token value }; // token list typedef struct tlist tlist_t; struct token { tlist_t *tlist_next; // forward link tlist_t *tlist_prev; // backward link token_t *tlist_head; // pointer to list head token_t *tlist_tail; // pointer to list tail }; 

Изначально после parsingа строки ввода (помня о продолжениях) у нас есть один tlist .

Если список имеет ; разделители в нем, мы разделили их на создание подсписков. Затем мы зацикливаем на подсписках и выполняем команды по порядку.

При просмотре подкоманды, если она заканчивается на & , команда должна быть отключена. Мы отмечаем это и вытаскиваем его из задней части списка.

Хорошо, теперь у нас есть список, который может иметь форму:

 cat < /etc/passwd | grep root | sed -es/root/admin/ > /tmp/out 

Теперь мы делаем следующий раскол на | поэтому у нас есть список, состоящий из трех элементов:

 cat < /etc/passwd grep root sed -es/root/admin/ > /tmp/out 

Фактически, каждая из этих «строк» ​​является tlist и это двумерный список списков:

 list_of_tlists: | | tlist[0] --> cat --> < --> /etc/passwd | | tlist[1] --> grep --> root | | tlist[2] --> sed --> -e --> s/root/admin/ --> > /tmp/out 

Когда мы создаем конвейер, мы отмечаем перенаправления и делаем файл open а не pipe мере необходимости.

Хорошо, это реферат.

См. Мой ответ здесь: Реализация перенаправления ввода / вывода в оболочке Linux с использованием C для полной и полной реализации.

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

Этот ОП попросил о помощи в перераспределении и трубах.

Боковое замечание: в то время возникали вопросы, связанные с реализацией оболочки. Итак, я закончил создание полной оболочки, которая делает почти все. Но эта версия слишком велика для публикации на SO. Итак, на этой странице найдите ссылку pastebin, которую я разместил. Он имеет полный исходный код. Его можно загрузить, построить и запустить.

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