Keyboard in raw-unbufferd mode
As noted by Peter, you can accomplish what you apparently desire, that being stopping input immediately upon the keypress of '$'
without conio.h
, but it requires setting the terminal in raw-unbuffered mode. In essence you are changing the way the terminal responds to a keypress.
Rather than buffering the character in stdin
until a newline is read, in raw-unbuffered mode, each character is immediately available to test which allows you to check each for your sentinel character and continue when it is read. Since you are changing the way the terminal responds to each keypress, you must reset the normal behavior when you are done collecting input.
There are a couple of ways to check for the availability of a character in stdin
. One of the easiest is to use select
to test the file descriptor and return a character when it becomes available. A (relatively short) example would be:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <termios.h>
#include <errno.h> /* for errno */
#include <unistd.h> /* for EINTR */
#define MAXC 256
static struct termios g_old_kbd_mode;
/* kbhit uses select to check for input on stdin. */
static int kbhit (void){
struct timeval timeout;
fd_set read_handles;
int status;
/* check stdin (fd 0) for activity */
FD_ZERO (&read_handles);
FD_SET (0, &read_handles);
timeout.tv_sec = 0; /* timeout 0 forces select to return immediately, if */
timeout.tv_usec = 0; /* NULL, select blocks indefinitely. */
do {
status = select (0 + 1, &read_handles, NULL, NULL, &timeout);
} while (status == -1 && errno == EINTR);
return status;
}
/* restore original keyboard attributes */
static void kbd_old_attr (void){
tcsetattr (0, TCSANOW, &g_old_kbd_mode);
}
int main (void) {
char buf[MAXC] = {0};
char *p = buf;
int c;
static char init;
struct termios new_kbd_mode;
if (init) return 1;
/* put keyboard (stdin, actually) in raw, unbuffered mode */
tcgetattr (0, &g_old_kbd_mode);
memcpy (&new_kbd_mode, &g_old_kbd_mode, sizeof(struct termios));
new_kbd_mode.c_lflag &= ~(ICANON);
new_kbd_mode.c_cc[VTIME] = 0;
new_kbd_mode.c_cc[VMIN] = 1;
tcsetattr (0, TCSANOW, &new_kbd_mode);
printf ("\n enter text ('$' ends): ");
while (!kbhit()) { /* this only works with timeout 0 */
c = getchar(); /* get keypress from user */
if (c == '$') break; /* test kepress for '$' */
*p++ = c; /* assign char to buf */
if (p-buf >= MAXC-1) { /* check against limit */
fprintf (stderr, "warning: character limit reached.\n");
break;
}
}
*p = 0; /* null-terminate */
kbd_old_attr (); /* restore original attributes */
printf ("\n text entered : %s\n\n", buf);
return 0;
}
Use/Output
$ ./bin/keypress_dollar
enter text ('$' ends): abcdefgjik$
text entered : abcdefgjik
Note: In this example while (!kbhit)
requires select to return 0. Using a timeout of 0 forces select to return immediately with a char available in fd0 leaving 0
file descriptors remaining. Since the return value for select is the number of descriptors remaining in the set -- it works in this limited case. To use with a timeout, or NULL, you must modify the caller so it is not within while (...)
.
Original -- User Required to Press Enter
You can use either getchar
or scanf
to read a string up until you reach a sentinel character such as '$'
. One way using getchar
would be:
#include <stdio.h>
#define MAXC 256
int main (void) {
char buf[MAXC] = {0};
int i = 0, c;
printf ("\n enter string : ");
while (i + 1 < MAXC && (c = getchar()) != '$' && c != '\n' && c != EOF)
buf[i++] = c;
buf[i] = 0; /* null-terminate */
printf (" string entered: %s\n\n", buf);
return 0;
}
Example/Output
$ ./bin/getchar_dollar
enter string : a quick brown fox jumps over$ the lazy dog.
string entered: a quick brown fox jumps over
Note: all characters following the '$'
remain in the input buffer (e.g. stdin
), so you would need to empty stdin
before your next input/read operation or your would read the characters that are left as your input.