Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

piped stdin and keyboard same time in C

Tags:

c

stdin

I have read previous questions regarding this problem too. fflush(stdin) does not work in this scenario for me. I want my program to read from a piped stdin and continue from keyboard input in the middle.

int main()
{
    int userin = 3;
    read_input();   
    userin = print_menu();
    userin = parse_input(userin);

    return 0;
}  

I want to read data from a file which is passed to the program as a pipied stding like

program < testing_text

int read_input(){
    char line[200];
    char word[MAX_STRING+1];
    int line_number = 0;

    while(fgets(line, sizeof(line), stdin) != NULL ){
        //do something
        printf("%s",line); 
        line_number++;
    }
}

Now read_input must finish reading from the piped input. 'print_menu' must continue reading from the keyboard.

int print_menu()
{
    int userinput;
    char c;
    char num[4];
    while((c=getchar()) != '\n' && c != EOF && c != '\r');

    printf("\n1. Choice 1 \n");
    printf("2. Choice 2\n");
    printf("3. Exit\n");
    printf("Enter your choice (1-3): ");

   /* scanf("%d", &userinput); */
   /* fgets(num,80,stdin);  */

   scanf("%s", num);
   userinput = atoi(num);   
   return userinput;
}

int parse_input(int userinput)
{
    char num[4];
    while( userinput > 3 || userinput < 1 ){
       printf("Sorry, that is not a valid option\n");
       printf("Enter your choice (1-3): ");
       scanf("%s", num);
       userinput = atoi(num);
    
       /* scanf("%d", &userinput); */
       /* while( (c = getchar()) == '\n'); */
    }
     return userinput;
}

My output is a infinite loop of

Enter your choice (1-3): Sorry, that is not a valid option

When I remove read_input method and piped stdin program works fine. I cannot figure out a get around for this, does someone has a idea..

like image 280
Krv Perera Avatar asked Mar 17 '23 09:03

Krv Perera


2 Answers

Pipes come (controlled by the shell) normally on file-descriptor 0, which is the standard input. You can use dup2() to "move" that to a different file descriptor, and use freopen() to continue reading from that. You can also open the /dev/tty device (or whatever the tty program knows about) and make that your standard input.

The dialog program does this for the --gauge (progress bar) option. The reason why it does this is because dialog is a curses application (which by default uses the standard input and output). The actual pipe file-descriptor is as well optional, so quoting from its manual page gives some hints:

--input-fd fd

Read keyboard input from the given file descriptor. Most dialog scripts read from the standard input, but the gauge widget reads a pipe (which is always standard input). Some configurations do not work properly when dialog tries to reopen the terminal. Use this option (with appropriate juggling of file-descriptors) if your script must work in that type of environment.

The logic which implements this is in the util.c, from the init_dialog function. Here is the main part of that, to give an idea how the functions are used:

    dialog_state.pipe_input = stdin;
    if (fileno(input) != fileno(stdin)) {
        if ((fd1 = dup(fileno(input))) >= 0
            && (fd2 = dup(fileno(stdin))) >= 0) {
            (void) dup2(fileno(input), fileno(stdin));
            dialog_state.pipe_input = fdopen(fd2, "r");
            if (fileno(stdin) != 0)     /* some functions may read fd #0 */
                (void) dup2(fileno(stdin), 0);
        } else {
            dlg_exiterr("cannot open tty-input");
        }
        close(fd1);
    } else if (!isatty(fileno(stdin))) {
        if ((fd1 = open_terminal(&device, O_RDONLY)) >= 0) {
            if ((fd2 = dup(fileno(stdin))) >= 0) {
                dialog_state.pipe_input = fdopen(fd2, "r");
                if (freopen(device, "r", stdin) == 0)
                    dlg_exiterr("cannot open tty-input");
                if (fileno(stdin) != 0)         /* some functions may read fd #0 */
                    (void) dup2(fileno(stdin), 0);
            }
            close(fd1);
        }
        free(device);
    }
like image 137
Thomas Dickey Avatar answered Mar 18 '23 23:03

Thomas Dickey


I had a nearly-identical situation with a program I wrote to read binary data from stdin (piped in from some other program's stdout), but I wanted to add keyboard commands (via raw terminal support). Obviously reading piped data and the keyboard simultaneously led to some weird results.

Here's my code, working and tested:

FILE * fp;
int fd;

fd = dup(fileno(stdin));
fp = fdopen(fd, "r");
(void) freopen("/dev/tty", "r", stdin);

Now, my program reads from fp (a FILE* object) for its binary data while reading stdin (now associated with "/dev/tty") for keystrokes.

Note that I discard the return value from freopen (a FILE* object) since I can refer to it via stdin if needed. My experience is that there is no need to close() or fclose() a standard text stream. I fclose(fp) at the end of reading the binary data.

like image 45
pr1268 Avatar answered Mar 18 '23 22:03

pr1268