Я считаю, что проблема в том, что вы ждете и замыкаете внутри того же цикла, который создает детей. На первой итерации дочерний элемент выполнит exec (который уничтожит дочернюю программу, перезаписав ее первой командой), а затем родительский файл закроет все свои файловые дескрипторы и ожидает завершения дочернего процесса, прежде чем он перейдет к созданию следующего дочернего элемента. , В этот момент, поскольку родитель закрыл все свои каналы, дальнейшим дочерним элементам нечего будет писать или читать. Поскольку вы не проверяете успешность ваших вызовов dup2, это остается незамеченным.
Если вы хотите сохранить ту же структуру цикла, вам нужно убедиться, что родительский элемент закрывает только те файловые дескрипторы, которые уже были использованы, но оставляет те, которые не были одинокими. Затем, после того как все дети созданы, ваш родитель может подождать.
РЕДАКТИРОВАТЬ : Я перепутал родителя / потомка в своем ответе, но рассуждения все еще верны: процесс, который переходит к форку снова, закрывает все его копии каналов, поэтому любой процесс после первая ветвь не будет иметь допустимых файловых дескрипторов для чтения / записи.
псевдокод с использованием массива каналов, созданных заранее:
/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
if( pipe(pipefds + i*2) < 0 ){
perror and exit
}
}
commandc = 0
while( command ){
pid = fork()
if( pid == 0 ){
/* child gets input from the previous command,
if it's not the first command */
if( not first command ){
if( dup2(pipefds[(commandc-1)*2], 0) < ){
perror and exit
}
}
/* child outputs to next command, if it's not
the last command */
if( not last command ){
if( dup2(pipefds[commandc*2+1], 1) < 0 ){
perror and exit
}
}
close all pipe-fds
execvp
perror and exit
} else if( pid < 0 ){
perror and exit
}
cmd = cmd->next
commandc++
}
/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
close( pipefds[i] );
}
В этом коде исходный родительский процесс создает дочерний элемент для каждой команды и, следовательно, выдерживает все испытания. Дети проверяют, должны ли они получать свои данные от предыдущей команды и должны ли они отправлять свои выходные данные следующей команде. Затем они закрывают все свои копии дескрипторов файла канала и затем исполняются. Родитель ничего не делает, кроме fork, пока не создаст дочерний элемент для каждой команды. Затем он закрывает все свои копии дескрипторов и может ждать.
Создание всех необходимых вам каналов, а затем управление ими в цикле довольно сложно и требует некоторой арифметики с массивами. Цель, однако, выглядит так:
cmd0 cmd1 cmd2 cmd3 cmd4
pipe0 pipe1 pipe2 pipe3
[0,1] [2,3] [4,5] [6,7]
Понимая, что в любой момент вам понадобятся только два набора каналов (канал для предыдущей команды и канал для следующей команды), что упростит ваш код и сделает его немного более устойчивым. Ephemient дает псевдокод для этого здесь . Его код чище, потому что родитель и потомок не должны делать ненужных циклов, чтобы закрыть ненужные файловые дескрипторы, и потому что родитель может легко закрыть свои копии файловых дескрипторов сразу после разветвления.
В качестве примечания: вы всегда должны проверять возвращаемые значения pipe, dup2, fork и exec.
РЕДАКТИРОВАТЬ 2 : опечатка в псевдокоде. ОП: количество труб будет числом труб. Например, "ls | grep foo | sort -r" будет иметь 2 канала.