c problem - pipe vs stdin


I'm trying to create a UNIX more-like program (a simple one) and having some problems here.  The program should list 20 lines everytimes a key is pressed.

It should run both way whether by specifying a file name as well as piping from other source :
> more file
> cat file | more.

But for the time being its only work for (i guess)
> more file

If i do
>cat file | more
it doesnt stop waiting for key to be pressed by me. I have specified pipe as /dev/stdin, i guess its just read whatever character from pipe and proceed listing the next 20 lines....till the end.

My question:
1. How to tell the different between pipe output and char from keyboard and of course then to solve the problem.

This is my short program

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

int main(int argc, char** argv)
        /*if (1 == argc) {
                printf("Usage: more [<filename>]\n");

        FILE* fp;
        if (2 == argc) {
                printf("Opening file\n");
                if ((fp = fopen(argv[1], "rt")) == NULL) {
                        perror(" ");
        } else if (1 == argc) {
                printf("Opening stdin\n");
                if ((fp = fopen("/dev/stdin", "rt")) == NULL) {
                        //printf("Usage: more [<filename>]\n");
                        perror(" ");

        char rtn[20];
        char line[20][256];

        do {
                for (int i=0; i<20; i++) {
                        if (fgets(line[i], 256, fp) != NULL) {
                                printf("%s", line[i]);
                        } else {
                                return 0;
                /* wait for any key(s) and return are pressed */
                scanf("%s", rtn);
        } while (strcmp (rtn, "q"));
Who is Participating?
ecwConnect With a Mentor Commented:
if you want single char input (ie. hit spcae for next page), stdio will not do.  You have to use termios to change the tty settings to return from read when a single char has been entered.


int ttyfd;
int quit = 0;
char c;
struct termios termios_old;
struct termios termios_new;

  tty_fd = open("/dev/tty", O_RDONLY);
  tcgetattr(tty_fd, &termios_old);
  termios_new = termios_old;
  termios_new.c_lflag &= ~ICANON;
  termios_new.c_cc[VMIN] = 1;
  termios_new.c_cc[VTIME] = 0;
  /* before doing this, you should set up a signal */
  /* signal handler to reset tty setting to */
  /* termios_old.  Or disable SIG{INT,QUIT,TERM} */
  tcsetattr(tty_fd, TCASNOW, &termios_new);
  /* now drop to pseudo code.... */
  while (! quit) {
   printf("More:"); fflush(stdout);
   if (read(tty_fd, c, 1) > 0) {
    /* do what you is expect if char is space, newline, */
    /* q, h, ...., etc. */
   } else {
     quit = 1;
  tcsetattr(tty_fd, TCASNOW, &termios_old);

See the man pages for termios and tcgetattr
Well, to keep it simple...

FILE *fp

if (argc == 1)
  fp = stdin;
  fp = fopen(argv[1], "r");

Then use getch() to read the key. It's defined within the curses/ncurses library.
mafendeeAuthor Commented:
I am not sure whether getch() works on Solaris or not. I got an error message. Anyway, I think getch() will only read one character and might have trouble there to handle input string from user.
Live webcast with Pinal Dave

Pinal Dave will teach you tricks to help identify the real root cause of database problems rather than red herrings. Attendees will learn scripts that they can use in their environment to immediately figure out their performance Blame Shifters and fix them quickly.

You'll have to link in the curses library as well, specifying -lcurses... To read a string, use getstr. Look up man curses or man ncurses to see if it's installed or not.
curses needs some initialization before you can use it. I found this in a program I wrote about a year ago:

   keypad(stdscr, 1);

Before program termination, you should call:

you can also use getchar() <stdio.h> to do the same thing if you don't want to use the curses library.
Yeah... I almost fell in that trap myself... Does getchar() not read from stdin?
mafendeeAuthor Commented:
I did use getchar() but the problem is it didn't stop for every 20 lines waiting for the next key pressed. Just go on till the end. Most probably, it can tell no different between input from keyboard and input from pipe. Both are from stdin, i guess.
I would need some time to read materials on 'curses.h' unless you have a better idea with getchar().
You are correct.  Try doing an fflush(stdin) before getchar.
Ok, what do you think will happen when you do the following?
more < file
mafendeeAuthor Commented:
Have tried fflush(stdin), doesnt work neither!
mafendeeAuthor Commented:
obg, I have tried

> more < file

and it didnt wait neither. Just go on till the end.
Whenever you do more < file or prog | more or something like that, stdin gets redirected and will no longer be connected to the keyboard. That's why you can't use stdin for keyboard input. Once again, curses supply good functionality for direct keyboard access (and a lot of useful stuff regarding text screen manipulation).
open /dev/tty, set tty setting for single har input and away you go.
Say what...?

Maybe I'm stupid, but I don't understand anything of what you just said. The problem as I see it is that regular reading of the file data might come from stdin, and so does the keyboard input (with the regular getc/getchar). Changing the /dev/tty settings will not change that in any way I can see.

Maybe you are suggesting a way to make keyboard input non-buffered, so that you don't need to press return? Well, that would be one step, but on a dead end...
When you say you are writing a "UNIX more-like" program, do you mean you a writing a UNIX program to imitate more, or you are writing non-UNIX (maybe DOS) program that imitates UNIX's more.

If it is an interactive UNIX program, stdin will by default be /dev/tty, a pseudo device that maps to your current tty.  Any program that opens /dev/tty will automagically open it's own tty.  This is used by things like more that act as filters AND require keyboard input.  With more, if argc==1, read from stdin, but whether reading from stdin or not, always get keyboard input from /dev/tty.

You don't ned to use stdio for simple unbuffer3ed single char input, read(2) is fine, use ioctl or tc{get,set}* or derivitives of, for setting the tty raw, min chars to 1, timeout to indefinite.

I'd include and example but haven't access to my Unix boxes today.

A warning though, if you mess with terminal settings, you MUST reset them at program exit, be it by a signal or intended.
Ah... Now I see what you mean. Of course... Why didn't I think of that? Well, you don't need to answer that question. ;-)
mafendeeAuthor Commented:
I managed to get it work with ecw advice. The way I did was, if the number of argument is
a. 1 then
   (fp = fopen("/dev/stdin", "rt")) == NULL) || ((fret = fopen("/dev/tty", "r") = NULL)

// reading input stdin and wait for return from tty

b  2 then
   (fp = fopen(argv[1], "rt")) == NULL) || ((fret = fopen("/dev/tty", "r") == NULL)

// reading input from a file and wait for return from tty

mafendeeAuthor Commented:
But I do not understand from "ecw" message

"You don't ned to use stdio for simple unbuffer3ed single char input, read(2) is fine, use ioctl or tc{get,set}*
or derivitives of, for setting the tty raw, min chars to 1, timeout to indefinite."

What do you mean? and what do I have to do here with tty?
any example would be good.
mafendeeAuthor Commented:
Its worked. Thanx a lot guys!
All Courses

From novice to tech pro — start learning today.