launch command with input and output

Hello, i have a project that im working on for some time now and i encountered a problem. Simply said, i have the main process and i want to launch multiple cmd commands (in threads) (i mean a command that takes an unknown amount of time, gives output time to time and can take input anytime) but, so far, i can get the output of it and also send input, but only one at a time. The problem is that i need to do both at the same time.

What i got so far with pipes:
Getting the output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::string theCommand = "a long time taking command";

std::array<char, 128> theBuffer;
std::unique_ptr<FILE, decltype(&pclose)> thePipe(popen(theCommand.c_str(), "r"), pclose);

if(!thePipe) {
    return 0;
}

while (fgets(theBuffer.data(), theBuffer.size(), thePipe.get()) != nullptr || closeThread == false) { //behaves perfectly!
    std::cout<<theBuffer.data();

    if(closeThread)
       break;
}


Sending input:
1
2
std::unique_ptr<FILE, decltype(&pclose)> thePipe(popen(theCommand.c_str(), "w"), pclose);
fputs(command.c_str(), thePipe.get());

Last edited on
Those are processes, not threads.

What exactly is the problem? A small complete program that demonstrates the problem would clarify things.

However, a standard pipe cannot (portably) be opened for both reading and writing. In particular, the popen command says you can open for reading or writing, but not both.

And if you are trying to open the same command with two different popen invocations, well, that's not going to work since it's two separate commands.

If you handle your own pipes you can do it.
Last edited on
Partial example of handling your own pipes (and forking and exec'ing your own process).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <unistd.h>

int main() {
    int inp[2], outp[2];
    pipe(inp);
    pipe(outp);

    switch (fork()) {
    case -1:
        perror("fork");
        exit(EXIT_FAILURE);

    case 0:
        close( inp[0]); close(1); dup( inp[1]); close( inp[1]);
        close(outp[1]); close(0); dup(outp[0]); close(outp[0]);

        execlp("program", "program", (const char*)NULL);

        perror("exec");
        exit(EXIT_FAILURE);
    }
        
    // parent ------------------------------------------------
    
    // write to outp[1]; read from inp[0]
    FILE* fin  = fdopen(inp[0], "r");
    FILE* fout = fdopen(outp[1], "w");

    // ...
}
Last edited on
I'm going to illustrate something that comes from a memory perhaps 30 years old, and I'm going to limit this to the scope of a suggestion, it will be up to you, @CosmimPerRam, to flesh this out.

I've referenced a few doc pages to be sure I'm heading in a reasonable direction. The last time I did this was in the very early 90's, when text based applications on UNIX, using actual TTY devices (AKA dumb terminals via RS232), was still a thing.

The objective, back then, was to execute another program, sending data to it as if that program received stdin keystrokes, and reading data back from that program's stdout.

The basic idea is to fashion a short, generic program which opens named pipes. The type of pipe you're using here are, as I recall, anonymous pipes. Named pipes appear as files in the file system (which is common to *NIX operation), but are not files themselves - the type of the entry is not a typical block storage (applicable to files), but of a pipe.

Your program launches this generic pipe substitution utility (which you write). It substitutes stdin and stdout (and perhaps stderr) to 2 (or 3 ) named pipes. Then, it uses execvp (or one from the family of functions) to run the program of interest, but now the stdin and stdout of that process has been tied to the named pipes, leaving the pipe substitutions intact.

Your application opens these 2 (or 3) named pipes to gain the kind of access you want.

First, look up the mkfifo function. An example is here: https://www.geeksforgeeks.org/named-pipe-fifo-example-c-program/

mkfifo creates a named pipe.

1
2
mkfifo( "PipeStdIn", 0666 );
mkfifo( "PipeStdOut", 0666 );


These are now two named pipes in a directory (the path can specify where).

Then, open the pipes with open....

1
2
int pipe_stdin = open( "PipeStdIn", O_WRONLY );
int pipe_stdout = open( "PipeStdOut", O_WRONLY );


This pipe can then be substituted for stdin or stdout using dup2 in the proposed generic utility

1
2
3
4
5
dup2( pipe_stdin, 0); // stdin is zero

or

dup2( pipe_stdout, 1); // stdout is 1 - stderr is 2 



This proposed utility executes the program of interest (probably execvp), whatever that is, but that process's stdin and stdout have been tied to these named pipes.

Your application opens these two pipes using open, writing to one ( the pipe stdin), reading from the other (pipe stdout).

Good luck. I had this working, back in '91/'92, and it was used for at least 15 years when the *NIX box was, finally, replaced in that company.

I remember some tinkering was required, but alas I'm not building a working example to see what that was.



Last edited on
Registered users can post here. Sign in or register to post.