-4

I have this command line argument -

cat file_name | ./a.out 

The problem is not reading from the cat command inside the C program as we can do that with read(), fgets(), fgetc() but the actual problem I am facing is after reading the data from cat I am not able to take input from user using fgets.

Here is my sample code

while(fgets(buffer, BUFSIZ, stdin ) != NULL )
    puts( buffer ); // Here I have tried strtok( buffer, "\n" ) too.
memset( buffer, 0, BUFSIZ );`

The problem is after this line, it is not asking for the input like the below is not working-

puts("Name: ");
fgets( buffer, BUFSIZ, stdin );

Help me with what's wrong happening here?

  • 1
    Seems reasonable, since `stdin` is now connected to the output of the `cat` command, then fgets() will read from its output, it can't read what the user types at the terminal. – nos May 03 '18 at 06:17
  • Do you know the solution? @nos – Devesh Pratap May 03 '18 at 06:18
  • 1
    @Devesh.Pratap The solution is that if you need to read input from the user and from a file, you cannot pipe the content of the file into your program, instead provide an argument to your program and open the file in your own program. You would run it as `./a.out file_name` – nos May 03 '18 at 06:21
  • I know that `./a.out file_name` is definitely going to work but its not the requirement. I need to accept input from `cat` as well as need to wait for user input, its going to happen inside the thread. @nos – Devesh Pratap May 03 '18 at 06:22
  • 1
    Insisting on something that's not possible **by design** isn't helpful. Shouting in the question isn't, either. Better take a step back and **rethink** your requirement -- **why** do you think you need it? Google for "XY problem". –  May 03 '18 at 06:37
  • Sir :) I do not need it. I have the work to do and it's simply the requirement of the work. Secondly, I am not shouting the reason I mentioned those lines which you have already deleted is because I have searched almost every where on google but I am no able to find the solution. For people `stackoverflow` is the first step when they find some difficulty for me tis the last hope. And it is something which is possible that is why it is mentioned in the work. But thanks anyways.@FelixPalmen – Devesh Pratap May 03 '18 at 06:40
  • 2
    That's because **there is no solution**. There's only one `stdin` and if you do a redirect in the shell, the shell wires it to the output of the other command, simple as that. Note "it isn't possible" would be a correct answer here. Therefore, if you want further help, explain what should be achieved by that requirement and we can help you find a different way. (and, just for the records, writing in ALL CAPS **is** "shouting", even if you weren't aware...) –  May 03 '18 at 06:50

2 Answers2

1

When you do cat file_name | ./a.out the standard input of your program is tied to a pipe linking it to the output of cat. Your program will never get to see the user input - the very stream from where it would arrive has been replaced by the aforementioned pipe.

Mind you, I suspect that with some horrible POSIX-specific trickery you may be able to reopen it going straight for the tty device, but it's just bad design. If you need to both read from a file and accept interactive user input just accept the file as a command line argument and use stdin to interact with the user.

Edit

This is an example of the Unix-specific kludges that one can attempt, assuming that the process still has a controlling terminal. After reading all the original stdin, I'm opening /dev/tty (which is the controlling terminal of the process) and re-linking stdin to it.

Disclaimer: this is for entertainment purposes only, don't do this for real.

#include <stdio.h>
#include <stdlib.h>

void die(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    fputs(msg, stderr);
    exit(1);
}

int main() {
    /* Read all of stdin and count the bytes read (just to do something with it) */
    int ch;
    unsigned long count = 0;
    while((ch = getchar())!=EOF) {
        count++;
    }
    printf("Read %lu bytes from stdin\n", count);
    /* Open the controlling terminal and re-link it to the relevant C library FILE *
     * Notice that the UNIX fd for stdin is still the old one (it's
     * surprisingly complex to "reset" stdio stdin to a new UNIX fd) */
    if(freopen("/dev/tty", "r", stdin) == NULL) {
        die("Failed freopen");
    }

    /* Do something with this newly gained console */
    puts("How old are you?");
    fflush(stdout);
    int age = -1;
    if(scanf("%d", &age)!=1) {
        die("Bad input");
    }
    printf("You are %d years old\n", age);
    return 0;
}

(previously I had a solution that checked if stderr or stdout were still consoles, which was even more of a kludge; thanks @rici for reminding me of the fact that POSIX has the concept of "controlling terminal", which is accessible through /dev/tty)

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • I can not do that as its not the requirement. I need to take input from both using `cat` as well as from user inside the thread. – Devesh Pratap May 03 '18 at 06:20
  • Is this an exercise or something? Because that's a surprisingly stupid requirement. Breaking expected behavior of input redirection is horrible design. – Matteo Italia May 03 '18 at 06:24
  • Yeah you can say that it's an exercise kind of thing. Sir, may be its stupid but its kind of challenging. Like I tried several thing, `dup, tcflush, tcdrain` etc. but they all are just simply not working. – Devesh Pratap May 03 '18 at 06:27
  • Yes, because that's not how the system is supposed to work. Once the shell forks to create your process and closes the stdin file descriptor to replace it with the pipe, it's gone for good. When your code starts executing there's nothing to do to make it come back, the file descriptor you have is the one linked to the output of `cat`, no `tc*` call is going to magically make it behave like a console. – Matteo Italia May 03 '18 at 06:42
  • Do you have any solution in your mind to make this thing work? – Devesh Pratap May 03 '18 at 06:45
  • 1
    It's like asking how to recover a deleted file; there are no solutions, only kludges, as you're going against how the system is supposed to work *and* the information you need is already lost. You can go for your parent process under `/proc` and snoop around to see if you can find a pty that may be the same as yours (but what if you were started detached?); you can hope your standard output or standard error hasn't been redirected, and use it to find out what tty you're writing to, and re-open it for input. You can try a lot of kludges, but they'll all be bad solutions in search of a problem. – Matteo Italia May 03 '18 at 06:59
  • What if I read the `cat` inside the child process and ones the input is done, `exit` from the child ? Data is not the problem I can use IPC for that. What do you think, is it going to work ? – Devesh Pratap May 03 '18 at 07:03
  • @DeveshPratap **no**. That is, unless you set up the pipe yourself. If the shell does it, your main process doesn't have access to the input of the controlling terminal any more, this won't magically change by some child process exiting. Now **please** stop insisting this must be possible. It isn't. Yes, as Matteo wrote, finding a **platform-specific** way of identifying the controlling terminal and opening it in your own code *could* work, but it's not robust, not at all portable and just brain dead. –  May 03 '18 at 07:09
  • 1
    I added an example of such a kludge; it kinda-works on Linux, but again, it's mainly for entertainment purposes. I would never release anything using this kind of thing, both because it's fragile, and because it's a horrible interface - what kind of braindead program actively circumvents stdin redirection? – Matteo Italia May 03 '18 at 07:47
  • @matteo: why not just use `/dev/tty`? It's got to be easier... http://tldp.org/HOWTO/Text-Terminal-HOWTO-7.html#ss7.3 – rici May 03 '18 at 08:10
  • I sense a perl programmer here :D @MatteoItalia nice example, I'm entertained :) Kids, don't do this at home ... –  May 03 '18 at 08:25
  • 1
    @rici: agh you are right, I completely forgot about the controlling terminal! That's way better. – Matteo Italia May 03 '18 at 08:31
  • 1
    @FelixPalmen: ahahah, fortunately I never had to do any real Perl, I've been exposed to it just a few times and I was thoroughly traumatized. – Matteo Italia May 03 '18 at 08:32
  • @MatteoItalia *what kind of braindead program actively circumvents stdin redirection?* For security reasons, `ssh` does: https://stackoverflow.com/questions/1340366/how-to-make-ssh-receive-the-password-from-stdin `ssh` does that to make it hard to script passwords. `ssh` tries really, really hard to make sure any password comes from the controlling terminal even when it's part of a command pipeline. I sure hope `ssh` can't be characterized as "brain-dead". – Andrew Henle May 03 '18 at 14:30
0

If you need to use stdin for user interaction, then you need to use a different file descriptor for reading the input stream.

You could use a specific pre-opened file descriptor and document that (e.g. "the input stream should be connected to fd 3"), but the usual approach is to accept a file name as a command-line argument. You can then provide a named pipe as the argument; shells such as Bash provide process substitution to make that easy:

./a.out <(cat file_name)

When that is run interactively like that, stdin is still connected to the terminal, and can be used at the same time as the stream from the connected command.

(Obviously, if the command actually is cat with a single argument, then you could just provide the filename itself as the argument, but I'm assuming that's a placeholder for a more involved pipeline).

Toby Speight
  • 27,591
  • 48
  • 66
  • 103