Solved

c problem - pipe vs stdin

Posted on 2002-05-02
20
361 Views
Last Modified: 2010-04-15
Hi,

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");
                exit(1);
        }*/

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

        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"));
}
#####################################
0
Comment
Question by:mafendee
  • 8
  • 7
  • 3
  • +1
20 Comments
 
LVL 2

Expert Comment

by:obg
ID: 6985671
Well, to keep it simple...

FILE *fp

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

Then use getch() to read the key. It's defined within the curses/ncurses library.
0
 

Author Comment

by:mafendee
ID: 6985943
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.
0
 
LVL 2

Expert Comment

by:obg
ID: 6986148
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.
0
 
LVL 2

Expert Comment

by:obg
ID: 6986158
curses needs some initialization before you can use it. I found this in a program I wrote about a year ago:

   initscr();
   nonl();
   cbreak();
   noecho();
   standend();
   clear();
   keypad(stdscr, 1);

Before program termination, you should call:

   erase();
   endwin();
0
 
LVL 5

Expert Comment

by:BlackDiamond
ID: 6986682
you can also use getchar() <stdio.h> to do the same thing if you don't want to use the curses library.
0
 
LVL 2

Expert Comment

by:obg
ID: 6988108
Yeah... I almost fell in that trap myself... Does getchar() not read from stdin?
0
 

Author Comment

by:mafendee
ID: 6991770
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().
0
 
LVL 5

Expert Comment

by:BlackDiamond
ID: 6991944
You are correct.  Try doing an fflush(stdin) before getchar.
0
 
LVL 2

Expert Comment

by:obg
ID: 6993379
Ok, what do you think will happen when you do the following?
more < file
0
 

Author Comment

by:mafendee
ID: 6993543
Have tried fflush(stdin), doesnt work neither!
0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 

Author Comment

by:mafendee
ID: 6993679
obg, I have tried

> more < file

and it didnt wait neither. Just go on till the end.
0
 
LVL 2

Expert Comment

by:obg
ID: 6993736
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).
0
 
LVL 5

Expert Comment

by:ecw
ID: 6996122
open /dev/tty, set tty setting for single har input and away you go.
0
 
LVL 2

Expert Comment

by:obg
ID: 6996141
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...
0
 
LVL 5

Expert Comment

by:ecw
ID: 6996670
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.
0
 
LVL 2

Expert Comment

by:obg
ID: 7000706
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. ;-)
0
 

Author Comment

by:mafendee
ID: 7007889
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


Thanx
0
 

Author Comment

by:mafendee
ID: 7007897
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.
0
 
LVL 5

Accepted Solution

by:
ecw earned 100 total points
ID: 7008687
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.

ie.

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) {
   show_next_page();
   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
0
 

Author Comment

by:mafendee
ID: 7013532
Its worked. Thanx a lot guys!
0

Featured Post

Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

Join & Write a Comment

This tutorial is posted by Aaron Wojnowski, administrator at SDKExpert.net.  To view more iPhone tutorials, visit www.sdkexpert.net. This is a very simple tutorial on finding the user's current location easily. In this tutorial, you will learn ho…
Summary: This tutorial covers some basics of pointer, pointer arithmetic and function pointer. What is a pointer: A pointer is a variable which holds an address. This address might be address of another variable/address of devices/address of fu…
The goal of this video is to provide viewers with basic examples to understand how to use strings and some functions related to them in the C programming language.
The goal of this video is to provide viewers with basic examples to understand and use conditional statements in the C programming language.

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

23 Experts available now in Live!

Get 1:1 Help Now