LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (https://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   stdin and pipes (https://www.linuxquestions.org/questions/linux-newbie-8/stdin-and-pipes-920547/)

qwerty4061 12-26-2011 06:45 AM

stdin and pipes
 
Hi,
I was trying to learn about pipes and standard input/output streams in linux. So I wrote a simple program.

Source Code:
Code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
  char *str;
  str = (char*)malloc(80*sizeof(char));
  while (1)
  {
    printf("Enter string :");
    fgets(str, 80, stdin);
    printf("%s", str);
    sleep(1);
    fflush(NULL);
  }
  free(str);
  return 0;
}

I tried it out by using "uname | ./a.out".

My output was:


Enter string :Linux
Enter string :Linux
Enter string :Linux
Enter string :Linux
Enter string :Linux
Enter string :Linux
Enter string :Linux
Enter string :Linux
:
:

This goes on forever. What I was expecting was that the program would print Linux once and then wait for next input from stdin. This error probably might be occuring because the word Linux is still on the stdin of a.out and it is reading the same thing every second (just my guess). So I tried removing the infinite loop in the code, but in that case if I do "uname | ./a.out" it works. But if I do some command that prints mulitple lines of text like "ls | ./a.out" only the first file name is printed and a.out exits. Can anyone suggest any method to solve this problem.

Thanks

Doc CPU 12-26-2011 07:15 AM

Hi there,

Quote:

Originally Posted by qwerty4061 (Post 4558516)
Code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
  char *str;
  str = (char*)malloc(80*sizeof(char));
  while (1)
  {
    printf("Enter string :");
    fgets(str, 80, stdin);
    printf("%s", str);
    sleep(1);
    fflush(NULL);
  }
  free(str);
  return 0;
}

I tried it out by using "uname | ./a.out".

My output was:

Enter string :Linux
Enter string :Linux
Enter string :Linux
:
:

This goes on forever.

I would expect that, because your program does not check whether stdin has any more input or not. So it reads one line of text and prints that again. In the second and subsequent loop iteration, however, stdin has reached the end of input (that is, feof(stdin) is true). In that case, fgets() does not modify the input buffer (see the reference manual for that case), and you believe you get the same string over and over again.

Quote:

Originally Posted by qwerty4061 (Post 4558516)
What I was expecting was that the program would print Linux once and then wait for next input from stdin.

It does - if you call it without input redirection (piping). Then stdin is the console input, which doesn't have an end-of-file condition.

Quote:

Originally Posted by qwerty4061 (Post 4558516)
Can anyone suggest any method to solve this problem.

Easy: Instead of making an infinite loop using while (1), check the end-of-file condition there:
Code:

while (!feof(stdin))
  ...

Then your program terminates properly when the input stream ends.

[X] Doc CPU

qwerty4061 12-26-2011 08:31 AM

Thanks for the detailed answer. I have been trying to find the answer for hours :newbie: . I didn't understand some part of it though.


Quote:

It does - if you call it without input redirection (piping). Then stdin is the console input, which doesn't have an end-of-file condition.
What other way is there to call it other than piping?


Quote:

Easy: Instead of making an infinite loop using while (1), check the end-of-file condition there:
Code:

while (!feof(stdin))
  ...

Then your program terminates properly when the input stream ends.
I replace the infinite loop with EOF check, but now instead of printing infinite times it is printing "Linux" twice. Why is that happening.


Also wasn't fflush(NULL) supposed to take care of stale values inside STDIN. STDOUT

MTK358 12-26-2011 09:21 AM

Quote:

Originally Posted by Doc CPU (Post 4558528)
console input, which doesn't have an end-of-file condition.

It does: press Ctrl+D.

MTK358 12-26-2011 09:25 AM

Quote:

Originally Posted by qwerty4061 (Post 4558516)
fgets(str, 80, stdin);
printf("%s", str);

"fgets" gets raw data, printf's "%s" expecta a NULL-terminated string. Try this:

Code:

#include <stdio.h>
int main()
{
  char str[81]; // the 81st element is for the NULL character at the end
  while (! feof(stdin))
  {
    printf("Enter string :");
    size_t bytes_read = fgets(str, 80, stdin);
    str[bytes_read] = '\0';
    printf("%s", str);
    sleep(1);
    fflush(NULL);
  }
  return 0;
}


Doc CPU 12-26-2011 09:27 AM

Hi there,

Quote:

Originally Posted by qwerty4061 (Post 4558566)
What other way is there to call it other than piping?

call your program from the console, just like that - and supply the input via keyboard as usual. :-)

Quote:

Originally Posted by qwerty4061 (Post 4558566)
I replace the infinite loop with EOF check, but now instead of printing infinite times it is printing "Linux" twice. Why is that happening.

Because I didn't finish thinking. Damn.
Well, look: fgets() reads until there is either an end-of-file condition, or a line feed. In your situation, the output of uname that you pipe into your program ends with a line feed. So fgets() reads everything, including the line feed, and then returns that data. At that moment, feof(stdin) doesn't catch yet, because you have read up to the end of the stream, but not beyond. End-of-file is recognized only when you try to read past the end. And that happens in lap 2 of your loop.

That's not nice, because it forces the programmer to produce "spaghetti code".
You can revert to your "while (1)" as the heading of your loop, but now you must supply something that ends the loop as soon as you know it should be ended - and that's after the fgets() call. Insert another line after the fgets() call:
Code:

if (feof(stdin))
  break;

That will break out of the loop and continue with the instructions after it when feof(stdin) catches.

Quote:

Originally Posted by qwerty4061 (Post 4558566)
Also wasn't fflush(NULL) supposed to take care of stale values inside STDIN. STDOUT

Yes. But it neither changes the eof() status of the file, nor touches your program's receive buffer. It only discards the rest of available input data you may not have read yet. But honestly, I stopped short on using NULL as an argument, as I would have expected fflush(stdin) here. Using NULL will flush all currently open files (didn't know that yet, thanks), which may not be what you want in a larger project.

[X] Doc CPU

Doc CPU 12-26-2011 09:32 AM

Hi there,

Quote:

Originally Posted by MTK358 (Post 4558591)
Quote:

Originally Posted by Doc CPU (Post 4558528)
Then stdin is the console input, which doesn't have an end-of-file condition.

It does: press Ctrl+D.

if you force it like that, it does. But not automatically when the input stream is exhausted.

Quote:

Originally Posted by MTK358 (Post 4558592)
"fgets" gets raw data, printf's "%s" expecta a NULL-terminated string.

Check it in the reference: fgets adds a terminating 0-byte.

[X] Doc CPU

qwerty4061 12-26-2011 08:48 PM

Thanks everyone especially Doc CPU for your answers.

Quote:

"fgets" gets raw data, printf's "%s" expecta a NULL-terminated string. Try this:
I think you are wrong here. "man fgets" tells a different story.

Quote:

fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A '\0' is stored after the last character in the buffer.
Quote:

Using NULL will flush all currently open files (didn't know that yet, thanks), which may not be what you want in a larger project.
I was initially using fflush(stdin). I changed it to NULL while tying to figure out why the program is not working.

qwerty4061 12-26-2011 08:55 PM

Oops I didn't see Doc CPU's last reply :)

qwerty4061 01-04-2012 01:26 PM

Better Code:


Code:

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

int main()
{
  char* str;
  str = (char*) (malloc(80 * sizeof(char)));
 
  while(fgets(str, 80, stdin) != NULL)
  {
    printf("%s", str);
    sleep(1);
  }

  free(str);
  return 0;
}


Doc CPU 01-04-2012 03:55 PM

Hi there,

Quote:

Originally Posted by qwerty4061 (Post 4566062)
Better Code:
Code:

...
  str = (char*) (malloc(80 * sizeof(char)));


why so complicated? The return type of malloc() is a void * which is by definition compatible to any other pointer type. So the explicite type cast isn't necessary and just makes it worse to read.

[X] Doc CPU

chrism01 01-04-2012 08:21 PM

The rtn type is 'void *' so that you CAN cast it to the type you require, which you should do for robust code. It also makes it clearer for the next guy ;)

Doc CPU 01-05-2012 03:48 AM

Hi there,

Quote:

Originally Posted by chrism01 (Post 4566405)
The rtn type is 'void *' so that you CAN cast it to the type you require

for a type cast to be possible, it wouldn't have to be void *, it could as well be int - though a pointer type is more obvious, of course. But an explicit type cast is always possible, even between totally incompatible types.

Quote:

Originally Posted by chrism01 (Post 4566405)
which you should do for robust code. It also makes it clearer for the next guy ;)

For me, a type cast on the result of malloc() looks more like an attempt of obfuscation. I avoid it for the sake of clarity.

[X] Doc CPU

MTK358 01-05-2012 07:27 AM

Why use malloc() for a fixed-size string that's only used for the lifetime of a function? use an array:

Code:

char str[80];
It will be allocated on the stack and popped off when the functions returns, no need for free().

qwerty4061 01-05-2012 01:13 PM

I guess I should have been clearer when I wrote "Better Code". I wrote that in response to DocCPU's earlier comment to feof forcing developers to write spaghetti code.

Quote:

Why use malloc() for a fixed-size string that's only used for the lifetime of a function? use an array:
Yes, you are right. There is no point in using malloc in this code. In my defence, this code was doing something else entirely different initially, I was modifying it in my effort to learn about standard streams.


Quote:

For me, a type cast on the result of malloc() looks more like an attempt of obfuscation. I avoid it for the sake of clarity.
I disagree with you on this. I think it improves readability, I don't have enough programming experience/knowledge to argue about the relative merits about using an explicit cast. If you have any reason as to why not to use an explicit cast in malloc, other than a personal preference I am definitely interested in it.


All times are GMT -5. The time now is 01:35 AM.