Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process: Add combined mode with TTY for input and PTY for output #40037

Closed
momocode-de opened this issue Jan 29, 2021 · 6 comments
Closed

Process: Add combined mode with TTY for input and PTY for output #40037

momocode-de opened this issue Jan 29, 2021 · 6 comments

Comments

@momocode-de
Copy link

Description

I am not very deep in this topic, but I played around with the Symfony Process and Console components. My idea was to create a command that executes a bash script and then reads the output of the bash script and writes it in a better style to the console again. That was not even hard to achieve. Here is an example where the output is written with the "text" function to the console:

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $io = new SymfonyStyle($input, $output);

    $process = new Process(['/path/to/script.sh']);
    $process->start();

    foreach ($process as $type => $buffer) {
        $io->text($buffer);
    }

    return Command::SUCCESS;
}

The next thing I tried was to also make that work with interactive scripts. The first problem was that the "read" within my bash script was skipped by the process. I found out that if I use $process->setPty(true) the "read" is not skipped and that the command waits for an input. But now the problem was that after typing something to the console nothing happened. Then I found out, that if I use $process->setTty(true) the command waits for an input and when I type something to the console that was also applied to the script and the script continued with execution. But now the problem was that I could not change the style of the output anymore, because you cant access the output in TTY mode.

So I needed a combination of both modes. TTY for input and PTY for output. So I just tried to return the following array in the getDescriptors function of the Symfony\Component\Process\Pipes\UnixPipes class:

return [
    ['file', '/dev/tty', 'r'],
    ['pty'],
    ['pty'],
];

And voila, now my command was actually working as expected. The output was printed in the "text" style and once the script came to the "read" part, the script waited for user input and after I typed it to the console, the script continued the execution with my user input.

Like I said, I'm not really deep in this topic. "PTY" and "TTY" are terms I have never heard before. So I don't know if that makes sense, but if so, is it possible to implement this combined method for the Process component?

@momocode-de
Copy link
Author

momocode-de commented Jan 29, 2021

Ah, I forgot to mention an alternative approach. Another idea was to use the PTY mode and to pass an input stream to the process and once the bash script expects an user input I use the $io->ask() function and the input of that is then written to the input stream of the process. But the problem is, that I dont know how to detect if the script waits for an user input. Here is my approach:

protected function execute(InputInterface $input, OutputInterface $output): int
{
    $io = new SymfonyStyle($input, $output);

    $processInput = new InputStream();
    $process = new Process(['/path/to/script.sh'], null, null, $processInput, null);
    $process->setPty(true);
    $process->start();

    foreach ($process as $type => $buffer) {
        // I found no possibility to detect when a user input is expected
        if (false) {
            // Once an interaction appears
            $userInput = $io->ask($buffer);
            $processInput->write($userInput . PHP_EOL);
        }
    }

    $processInput->close();

    return Command::SUCCESS;
}

Maybe this is working somehow but I did not find a solution for it?

@jkufner
Copy link

jkufner commented Jan 31, 2021

Yes, it is wrong :)

Each process has three file descriptors open by default: stdin, stdout, and stderr.

When you run a program from a terminal and you wish to spawn another process and let it read&write from the terminal to communicate with the user, you need to pass the three file descriptors to it. The parent process gets the file descriptors connected to the terminal and the child process inherits the same file descriptors so that it can access the terminal too.

In your solution, you open /dev/tty again; therefore, you establish a new connection to the terminal rather than using the original input. If you try to redirect input to your program, it will still wait for the user input, e.g., yes | ./your-script-that-expect-a-yes-no-answer-from-the-user.

The allocation of PTY allows programs to perform more advanced oprations with the terminal. Not sure about the details here, but you should not need it as long as you only use some colors and other basic stuff.

The correct solution should be to pass STDIN, STDOUT, and STDERR file descriptors to your new process and do nothing else. I'm not sure about exact Symfony's behavior here; the plain C fork() inherits the file descriptors without a change.

Take a look at http://docs.php.net/manual/en/function.posix-isatty.php. Try make a little script that shows three booleans (one the three file descriptors) so that you know that you connected them correctly. All three should be true if you simply run the command from a terminal; they should be false when redirected to/from a file.

@momocode-de
Copy link
Author

@jkufner Thanks for your reply! You are right, passing \STDIN, \STDOUT and \STDERR of the PHP process to the new process does also work. I tried it by changing the symfony code. But I think right now there is no official way to do that.

@carsonbot
Copy link

Thank you for this issue.
There has not been a lot of activity here for a while. Has this been resolved?

@carsonbot
Copy link

Just a quick reminder to make a comment on this. If I don't hear anything I'll close this.

@carsonbot
Copy link

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants