Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem using 2-level pipe when the first program doesn't exit in BASH

Tags:

linux

bash

pipe

Consider this :

xinput  --test 11 | grep "button press   1"

*11 is my Optical mouse index (could be anything else) and "button press 1" means left click.

When I click somewhere in the screen, that shows me this :

button press   1

No problem so far . But when I wanted to use the output of that as the input to my C program , I noticed that the stdin of the program after another level of pipe is always empty :

xinput  --test 11 | grep "button press   1" | ./poll_test

Here's my poll_test code:

/*This is just a program to test polling functionality on stdin.
I've used other programs instead of this as well.
None of them were able to read the stdin */

#include <fcntl.h>
#include <stdio.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <unistd.h>

void main(int argc, char ** argv) {
    int fd;
    char buf[1024];
    int bytes_read;
    struct pollfd pfds[1];

while (1) {
        pfds[0].fd = 0;
        pfds[0].events = POLLIN;
        poll(pfds, 1, -1);
        if (pfds[0].revents & POLLIN) {
            bytes_read = read(0, buf, 1024);
            printf("%s\n" , buf);
            if (!bytes_read) {
                printf("stdin closed\n");
                return;
            }
            write(1, buf, bytes_read);
        }
    }
}

It prints nothing despite of the clicks.

That is confusing.This is not a normal behavior. When I run this for example:

ls | grep a | grep b

It shows me the results successfully. The only difference is that the ls here exits after it prints out to the stdout but that's not the case in the xinput version.

I spend a lot of time to write a script to play a beep in the event of a mouse click but that didn't work. So I wanted to use a C program because there's no polling functionality in bash.

As far as I know , working of the pipes in bash is something like this :

The second program (the right one in the pipe statement) gets executed until it wants to READ from its stdin and it stops going further until there's something to read from the STDIN.

With that in mind, the third program in the command I posted should be able to read the output.As it's the case when the first programs exits.

The next step would be to use libxcb directly instead of the command xinput if pipe problem doesn't work.

I'm totally confused. Any help would be much appreciated.


EDIT: I also tried using an intermediate file descriptor:

exec 3<&1
xinput --test 11 | grep -i "button press   1" >&3  | ./poll_test 3>&1

but didn't help.And also flushing the stdout forcibly doesn't work either :

xinput --test 11 | grep -i "button press   1" ; stdbuf -oL  | ./poll_test
like image 914
Parsa Mousavi Avatar asked Jun 21 '20 19:06

Parsa Mousavi


1 Answers

It seems grep is changing its buffering behaviour depending on whether the output is a terminal or not. I don't know exactly why this happens, but --line-buffered forces it to use line buffering (evaluating the expression as soon as the line ends):

xinput --test 11 | grep "button press   1" --line-buffered | ./poll_test
like image 146
Leonardo Dagnino Avatar answered Nov 15 '22 08:11

Leonardo Dagnino