Как использовать dup и / или dup2 для перенаправления стандарта в канал, затем в другой канал, а затем обратно в стандартный поток?

Хорошо, ребята, есть миллиард демонстраций, относящихся к dup, dup2, fcntl, pipe и всевозможным вещам, которые прекрасны, когда существует несколько процессов. Однако мне еще предстоит увидеть одну очень простую вещь, которая, как я думаю, поможет объяснить поведение pipe и его отношение к стандарту снаружи и внутри.

Моя цель - просто (в том же процессе) перенаправить стандартный вывод через конвейер обратно на стандартный вывод напрямую. Я уже выполнил это с помощью промежуточных этапов, которые перенаправляют вывод канала в файл или записывают в буфер ... а затем возвращают стандартный вывод туда, где он начался. На этом этапе, конечно, я могу записать буфер обратно в стандартный вывод, но я не хочу этого делать.

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

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

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

int main() {
    int pipeEnds_arr1[2];

    char str1[] = "STRING TO FEED INTO PIPE \n"; // make a string array

    pipe(pipeEnds_arr1);

    printf("File Descriptor for pipe ends from array\nPOSITION out  0 : %d\nPOSITION in 1 : %d\n", pipeEnds_arr1[0], pipeEnds_arr1[1]);


    /* now my goal is to shift the input of the pipe into the position of 
     * standard output, so that the print command feeds the pipe, then I 
     * would like to redirect the other end of the pipe to standard out. 
     */

    int someInt = dup(1); // duplicates stdout to next available file table position

    printf ("Some Int FD: %d\n", someInt); // print out the fd for someInt just for knowing where it is

    /* This is the problem area.  The out end of the pipe never 
     * makes it back to std out, and I see no way to do so.  
     * Stdout should be in the file table position 5, but when 
     * I dup2 the output end of the pipe into this position , 
     * I believe I am actually overwriting std out completely.  
     * But I don't want to overwrite it, i want to feed the output
     * of the pipe into std out. I think I am fundamentally 
     * misunderstanding this issue. 
     */

    dup2(pipeEnds_arr1[1], 1); //put input end of pipe into std out position
    //dup2(pipeEnds_arr1[0], 5); // this will not work
    //and other tests I have conducted do not work


    printf("File Descriptor for pipe ends from array\nPOSITION out  0 : %d\nPOSITION in 1 : %d\n", pipeEnds_arr1[0], pipeEnds_arr1[1]);

    fflush(stdout);

    close(pipeEnds_arr1[0]);
    close(pipeEnds_arr1[1]);

    return 0;
}

РЕДАКТИРОВАТЬ ********* Хорошо, я знаю, что std out каким-то образом берет информацию из таких команд, как printf, а затем направляет ее в буфер, который затем сбрасывается в оболочку.

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

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


int main() {
    /*  Each pipe end array has to have 2 positions in it.  The array
     *  position represents the two pipe ends with the 0 index
     *  position representing the output of the pipe (the place you want
     *  read your data from), and 1 index position representing the
     *  input file descriptor of the pipe (the place you want to write
     *  your data).
     */
    int pipeEnds_arr1[2];

    char str1[] = "Hello, we are feeding this into the pipe that we are through stdout into a pipe and then reading from the pipe and then feeding that output into a file \n"; // make a string array

    /*  Here we want to actually do the pipe command. We feed it the array
     *  with the 2 positions in it which will now hold file descriptors
     *  attached to the current process which allow for input and output
     *  through the new pipe. At this point, we don't know what the
     *  exact file decriptors are, but we can look at them by printing
     */

    pipe(pipeEnds_arr1);
    printf("File Descriptor for pipe ends from array\nPOSITION out  0 : %d\nPOSITION in 1 : %d\n", pipeEnds_arr1[0], pipeEnds_arr1[1]);

    /* now my goal is to shift the input of the pipe into the position of 
     * standard output, so that the print command feeds the pipe, then we
     * will try to read from the pipe and redirect the output to the std 
     * or in this test case out to a file.
     */

    int someInt = dup(1); // we moved what was stdout into someInt;

    /* put the write end of the pipe in the old stdout position by
     * using dup2 so we will print directly into the pipe
     */
    dup2(pipeEnds_arr1[1], 1);

    /* this is where id like to re-rout the pipe back to stdout but
     * im obviously not understanding this correctly
     */
    //dup2(someInt, 3);

    /* since std out has now been replaced by the pipe write end, this
     * printf will print into the pipe
     */
    printf("%s", str1);


    /* now we read from the pipe into a new string we make */
    int n;
    char str2[strlen(str1)];
    n = read(pipeEnds_arr1[0], str2, sizeof(str2)-1);
    str2[n] = 0;

    /* open a file and then write into it from the output of the pipe 
     * that we saved into the str2
     */
    int fd = open("tmp.out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    write(fd, str2, strlen(str2));


    /* not sure about these last commands and their relevance */
    fflush(stdout);
    close(pipeEnds_arr1[0]);
    close(pipeEnds_arr1[1]);
    close(fd);

    return 0;
}

person cmg    schedule 16.09.2016    source источник
comment
Стандартный вывод - всегда файловый дескриптор 1. То, что у вас есть в someInt, - это дескриптор, который отличается от дескриптора 1 после вашего dup2 вызова. Система dup действительно дублирует дескрипторы, она не использует ссылки или ссылки или что-то подобное. Кроме того, вызовы dup не изменяют переданные вами номера дескрипторов. pipeEnds_arr1[1] не изменит значение (номер дескриптора) в любое время после вызова pipe.   -  person Some programmer dude    schedule 16.09.2016
comment
Это ни в коем случае не может работать. Один конец канала предназначен для записи, другой - для чтения. Таким образом, вы можете заменить stdout концом канала для записи, и ваша программа сможет записывать данные в канал (думая, что он пишет в stdout). Но другой конец трубы нельзя напрямую подключить к исходному stdout. Некоторый фрагмент кода должен читать из конвейера и записывать данные в исходный stdout.   -  person user3386109    schedule 16.09.2016
comment
Это не может работать даже теоретически, потому что это создаст бесконечный цикл. Подумайте об этом - вы в конечном итоге вернете вывод из stdout обратно в себя.   -  person kfx    schedule 16.09.2016
comment
@ user3386109 каким-то образом std out берет информацию из таких команд, как printf, а затем направляет ее в буфер, который затем сбрасывается в оболочку. Должен быть способ направить конец канала для чтения или вывода в тот же буфер, который затем попадает в оболочку. Кстати, что вы подразумеваете под каким-то фрагментом кода? Я понял, как перенаправить вывод канала в строку, а затем я могу делать все, что хочу, но в моем понимании все еще чего-то не хватает, потому что не похоже, что необходимо направлять вывод на что-то еще и ТОГДА разошлите это ...? должен быть один шаг.   -  person cmg    schedule 17.09.2016
comment
@ user6840486 Я тот же парень, с которым вы разговариваете по ответу Ричи. Я жду, когда вы опубликуете код, показывающий, как вы можете довольно легко перенаправить мой стандартный вывод в файл через конвейер в том же процессе. Также был бы интересен код, который направляет вывод конвейера в строку.   -  person user3386109    schedule 17.09.2016
comment
@ user3386109 roger, который обновился.   -  person cmg    schedule 17.09.2016
comment
Итак, вы вызываете read, чтобы извлечь сообщение из канала и сохранить его в строке, а затем вы вызываете write, чтобы извлечь сообщение из строки и сохранить его в файле. Вот что я имею в виду под частью кода. Это код, который читает из канала и записывает данные в файл. Итак, если у вас где-то сохранен исходный дескриптор файла stdout, вы можете делать то же самое для чтения из канала и записи в исходный дескриптор файла stdout.   -  person user3386109    schedule 17.09.2016
comment
@ user3386109, значит, нет возможности перейти от канала непосредственно к буферу, который питает консоль? Вероятно, это был бы лучший способ поставить вопрос, чем мой первоначальный способ.   -  person cmg    schedule 17.09.2016
comment
Неа. Думайте о канале как о файле, доступном только для чтения, как если бы вы сделали int fdin=open("tmp.in",O_RDONLY). А консоль - это файл только для записи, как если бы вы сделали int fdout=open("tmp.out",O_WRONLY). Эти два файловых дескриптора не будут взаимодействовать друг с другом. Если вы хотите, чтобы они разговаривали, вам нужен код для read от одного и write для другого.   -  person user3386109    schedule 17.09.2016


Ответы (1)


Каналы не находятся между файловыми дескрипторами. Они находятся между процессами. Так что нет никакого смысла «перенаправлять стандарт через пайп».

Что вы можете сделать, так это изменить таблицу дескрипторов файла процесса так, чтобы его стандартный вывод (fd 1) был записываемой стороной канала. И вы можете изменить таблицу файловых дескрипторов другого процесса так, чтобы какой-нибудь файловый дескриптор, возможно, даже stdin (fd 0), был читаемой стороной того же канала. Это позволяет передавать данные по каналу между двумя процессами. (Вы можете настроить канал между двумя файловыми дескрипторами в одном процессе, если хотите; иногда это полезно, но не забывайте о взаимоблокировках.)

stdout не какая-то магическая сущность. Это просто запись 1 в таблице fd, и она может относиться к любому «файлу» в смысле слова Unix, который включает в себя обычные файлы, устройства (включая консоль и псевдотерминал, с которыми взаимодействует ваша оболочка), сокеты, каналы , FIFO и все остальное, что операционная система считает достойным предоставления потокового доступа.

Обычно, когда оболочка запускает утилиту командной строки, она сначала клонирует fds 0, 1 и 2 (stdin, stdout и stderr) из собственных fd 0, 1 и 2, которые обычно являются одним и тем же устройством: console, или, чаще всего, в наши дни, псевдотерминал, предоставляемый графическим консольным приложением, которое вы используете. Но вы можете изменить эти назначения, например, с помощью операторов перенаправления оболочки, операторов конвейера оболочки и некоторых специальных файлов, предоставляемых оболочкой.

Наконец, у каналов есть небольшие буферы в ядре, но ключевым моментом является слово «маленький» - буфер может содержать всего 4096 байт. Если он заполнится, попытки записи в канал будут зависать до тех пор, пока не станет доступным пространство, что происходит только при чтении данных из другого sude. Вот почему так легко зайти в тупик, если один и тот же процесс использует обе стороны канала: если процесс зависает в ожидании опустошения стопки, он никогда не сможет прочитать канал.

person rici    schedule 16.09.2016
comment
Я не совсем понимаю, почему вы говорите, что каналы могут находиться только между разными процессами. Я могу довольно легко направить свой стандартный вывод в файл через конвейер в том же процессе. Так что насчет подачи канала из стандартного выхода и использования его в качестве буфера, а затем перенаправления выходного конца канала обратно на стандартный выход? Я до сих пор не понимаю, почему этого не может быть. Я почти уверен, что правильно направил оператор печати в канал, и, поскольку канал ведет себя как буфер, я должен каким-то образом отправить этот буфер обратно на консоль? - person cmg; 17.09.2016
comment
Я могу довольно легко перенаправить свой стандартный вывод в файл через конвейер в том же процессе. Было бы полезно, если бы вы добавили этот код в вопрос @ user6840486. - person user3386109; 17.09.2016
comment
Извините, я немного новичок в этом. Как мне добавить этот код? Должен ли я опубликовать его как правку или добавить где-нибудь еще? @ user3386109 - person cmg; 17.09.2016
comment
Должен ли я опубликовать его как правку? Да, добавьте абзац, в котором говорится то, что вы сказали в комментарии выше, а затем добавьте код после этого @ user6840486 - person user3386109; 17.09.2016
comment
@ user6840486: вы упорно думаете, что stdout - это вещь. Это не так; это просто число 1, используемое в качестве индекса в массиве открытых файлов. Труба - это один из возможных файлов. Консоль - еще одна возможность. И если конечно есть файлы на диске. И т.д. Когда вы дублируете канал в стандартный вывод, все, что вы делаете, это, по сути, запрашиваете у библиотеки io задание: fds[1] = fds[pipe]. Канал на самом деле представляет собой два файла, по одному на каждом конце, поэтому вы можете использовать его для туннелирования данных, записывая на один конец и читая другой. На самом деле это не имеет ничего общего с stdout; за исключением того, что printf всегда записывает в fd 1. - person rici; 17.09.2016