1

I'm encountering a limitation of bash input redirection that's preventing me from accomplishing a task, and I haven't been able to find a way to work around it so far. Essentially, I need to be able to redirect two separate files to a program's stdin separately, as opposed to all at once.

Here's an example program:

#include <stdio.h>

int main(void) {
    char myArr[200][100];

    int i = 0;
    while (fgets(myArr[i], 100, stdin)) {
        i++;
    }

    freopen("/dev/tty", "rw", stdin);

    int choice = 0;
    while (choice != 1 && choice != 2) {
        printf("Enter menu input: ");
        scanf("%d", &choice);
    }

    if (choice == 1) {
        for (int j = 0; j < i; j++) {
            printf("%s\n", myArr[j]);
        }
    } else if (choice == 2) {
        exit(-1);
    }
}

This program takes in input from stdin until EOF is reached, and counts the number of successful reads (akin to counting the number of lines in the input). I'm passing a whole file's worth of input using ./a.out < longInput.txt, which causes fgets to stop reading once the end of longInput.txt is reached.

I use freopen() to reset stdin so that I can start inputting menu options again after longInput.txt has reached EOF. However, this doesn't work as intended when trying to use bash redirection for menu inputs in the second part of the program.

My question is: how can I redirect input from a second input file that only contains menu options, after I've redirected from the first file and reset stdin, without hard-coding that second file name in my call to freopen()?

Assume that I cannot modify the source of that program, and I only have access to a compiled executable. Here are some references I've already tried, to no avail:

Thanks!

Owen Sullivan
  • 103
  • 2
  • 9
  • Nothing here is a limitation of bash. _The UNIX process model_ only lets you pass one file descriptor per fd. Can you show me how you'd implement the behavior your want bash to provide in a non-shell language? That would perhaps make it clearer what _actually achievable behavior_ you're asking the shell to provide. – Charles Duffy Mar 05 '23 at 21:16
  • BTW, insofar as you just want to do back and reread the same content again, that's a job for `seek()`. – Charles Duffy Mar 05 '23 at 21:17
  • And if you want to have the patent process pass a second file descriptor in and later rename that fd to make it become stdin, that's a job for `fdup2`. – Charles Duffy Mar 05 '23 at 21:19
  • @CharlesDuffy Not quite sure how to exactly reproduce the behavior in another language, but essentially what I'm looking for is `FILE *in1 = fopen("longInput.txt", "r");` and `FILE *in2 = fopen("menuInput.txt", "r");` where `in1` replaces `stdin` in my call to `fgets`, and `scanf` would be replaced with `fscanf(in2, "%d", &choice);`. The ultimate problem here is that I'm unable to modify the source to do this, and I need to have it all come from `stdin`. Should have mentioned that I'm trying to automate grading of student programs and I don't have control over how they implement. – Owen Sullivan Mar 05 '23 at 21:24
  • When/if a Unix program is reading from a _terminal_ (on any fd, not just stdin) you _can_ enter some data which is read, signal EOF by entering a character configurable with stty or ioctl, conventionally control-D=EOT, and then read more data after clearing the EOF condition (freopen is not needed). But this is a poor design and should be avoided (especially don't teach it to students) and for any other type of file, like a diskfile, pipe, or socket, it is flat impossible. – dave_thompson_085 Mar 05 '23 at 22:47
  • @dave_thompson_085 You hit the nail on the head with it being impossible for pipes, my script uses Python's `subprocess.Popen` module and it would appear what you're describing is, indeed, impossible. I can assure you that we didn't actually end up teaching my above method to students, we just advanced them to FILE pointers instead. Oh well, all good. – Owen Sullivan Mar 06 '23 at 23:10
  • If you only have the compiled binary with no access to the source, how are you calling `freopen`? – Chris Dodd Mar 07 '23 at 00:14

1 Answers1

0

Most commonly, you'd do this sort of thing by providing arguments to your program rather than by messing around with stdin. So you'd have something like:

int main(int ac, char **av) {
    if (ac != 3) {
        fprintf(stderr, "usage: %s <data input> <menu input>\n", av[0]);
        exit(1); }
    FILE *dataInput = fopen(av[1], "r");
    if (!dataInput) {
        fprintf(stderr, "can't read data input from %s\n", av[1]);
        exit(1); }
    // read your data input
    char myArr[200][100];
    int i = 0;
    while (i < 200 && fgets(myArr[i], 100, dataInput)) {
        i++;
    }
    
    FILE *meunInput = fopen(av[2], "r");
    if (!menuInput) {
        fprintf(stderr, "Can't open menu input %s\n", av[2]);
        exit(1); }
    // now read your menu input.

Then in bash, you just invoke your program with two arguments with the file names to read from. If you want to read from pipes (running other commands), you can use program redirects:

myprogram <(command that generates data input) <(command that generates menu input)
Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • I'm aware of how to do this in a perfect world, but, as I said in my question, I'm unable to modify the source program that I'm trying to run. This is an exercise in finding a bash workaround, not common practice for C. It does seem as though what I'm trying to do is not possible with bash on its own. – Owen Sullivan Mar 06 '23 at 23:08
  • What you're asking doesn't make any sense with that constraint -- there's only one stdin input, so if the program is fixed it can only take one stdin input. It comes down to what that fixed program is expecting on its stdin, which is what you need to provide. Concatenation of two files? – Chris Dodd Mar 07 '23 at 00:13