This is the infamous line-buffering status of stdin, which gives trouble to most of the standard input functions.
Characters typed in stdin are not delivered immediately; they are collected in a buffer until either the buffer gets filled or the user types an ENTER ('\n'). scanf() normally ignores any
trailing ENTER but it does not remove it from the line buffer. Moreover, scanf() does
NOT ignore
leading ENTER.
So the 2nd time you invoke scanf() (getchar(), fgets(), etc) via your do-while loop, the first character scanf() founds in stdin is the leftover ENTER ('\n') form the previous user input. Since '\n' equals neither 'Y' nor 'y' your loop stops and your program ends.
An easy work-around is to use a custom input buffer, along with fgets() combined with sscanf(), something along the following lines:
#include <stdio.h> // printf(), fflush(), fgets(), sscanf()
#include <ctype.h> // tolower()
#include <stdlib.h> // exit()
#define MAXINPUT (255+1)
int main( void )
{
char input[ MAXINPUT ] = {'\0'};
char c;
do {
printf( "run again (y/)? " );
fflush( stdout );
fgets( input, MAXINPUT, stdin ); // read input as a string (error checking omitted for brevity)
sscanf( input, "%c", &c ); // extract the info you need using sscanf()
} while ( 'y' == tolower(c) );
exit(0);
}
You can even wrap it up in a utility function, to be used every time you want to prompt for a char from stdin. Something like the following...
#include <stdio.h> // printf(), fflush(), fgets(), sscanf()
#include <ctype.h> // tolower()
#include <stdlib.h> // exit()
#define MAXINPUT (255+1)
/* ------------------------------------------------------------------ */
char prompt_for_char( const char *prompt )
{
char input[ MAXINPUT ] = {'\0'};
if ( prompt ) {
printf( "%s", prompt );
fflush( stdout );
}
return !fgets(input, MAXINPUT, stdin) ? '\0' : *input;
}
/* ------------------------------------------------------------------ */
int main( void )
{
do {
} while( 'y' == prompt_for_char("run again (y/)? ") );
exit(0);
}
As I said above, the whole trick is to first read stdin as a whole in a big string and then use sscanf() on that string to extract the info you need (in my examples, that string is defined as: char input[MAXINPOUT] ).
Note however that if the user types more than MAXINPUT-1 characters in stdin, then the undesirable line-buffer effect will come back. So you may find it more useful to write a small function to read stdin by yourself. You may even have it displaying an optional prompt before reading the string. Something like that...
/* ------------------------------------------------------------------ */
int prompt_for_string( const char *prompt, char *str, const int maxlen )
{
int i, c;
if ( !str )
return 0; /* FALSE */
if ( maxlen < 1 )
return 0; /* FALSE */
if ( prompt ) {
printf( "%s", prompt );
fflush( stdout );
}
for (i=0; i < maxlen-1 && (c=getchar()) != '\n' && c != EOF; i++)
str[i] = c;
/* eat up any extra characters from stdin */
if ( c != '\n' && c != EOF ) {
while ( (c=getchar()) != '\n' && c != EOF )
;
}
/* nil-terminate str */
str[i] = '\0';
return 1; /* TRUE */
}
Actually, the above function is a slightly improved version of the one I used in a small library I wrote a while ago, as a simplified mean of reading the stdin with C. The function inside the library suffers from a small bug, because it treats & compares EOF as a char instead of an int, which is a wrong thing to do. Thankfully it is extremely rare for a user to type the EOF char directly in the console.
The docs and the comments in
that small library are all in Greek, but the code is pretty straight-forward I believe. Also there's a small ReadMe file written in English, including a simple code snippet for its usage.
That library also uses a fixed length for the custom input buffer, equal to 1024 chars, via the
PF_MAXINPUT constant which is defined in the header file: prompt_for.h
If you'd like to read stdin into a dynamically grown custom string, then it would require a bit more work. Here's such a function I quickly wrote, which can hopefully give some insight (please note that I haven't tested extensively)...
/* -----------------------------------------------
* Create a new string (nul-terminated array of chars) by reading the stdin.
* Return the newly created string, or NULL on error.
* Args:
* size_t *len: If non-NULL, then it passes to the caller the length of
* the created string.
*
* size_t *size: If non-NULL, then it passes to the caller the size of
* the created string.
*
* int beExact: If 0 (false). then the size of the created string will
* be an exact multiple of the internally used alloc-ahead
* buffer. Most often it will be larger than: 1 + strlen()
*
* If 1 (true), then the size of the created string will
* be exactly: 1 + strlen()
* Notes:
* The returned string does NOT pretain the '\n' from stdin.
*/
char *s_new_from_stdin( int beExact, size_t *len, size_t *size )
{
const int AHEAD = 16; /* # of bytes for alloc-ahead buffer */
int c, i,j;
char *try = NULL;
char *ret = NULL;
char *retExact = NULL;
/* create allocate ahead buffer */
ret = malloc( AHEAD * sizeof(char) );
if ( NULL == ret )
goto fail;
/* read stdin into ret until '\n' or EOF */
j = 1;
for (i=0; '\n' != (c=getchar()) && EOF != c; i++)
{
/* need another alloc-ahead buffer? */
if ( i == (j * AHEAD)-1 ) /* -1 is for the terminating '\0' */
{
try = realloc(ret, (++j) * AHEAD);
if ( NULL == try ) {
free( ret );
goto fail;
}
ret = try;
}
ret[i] = (char)c;
}
ret[i] = '\0'; /* nul-terminate ret */
/* calc len & size of ret as if beExact == FALSE */
size_t ln = i;
size_t sz = (j * AHEAD);
/* if beExact == TRUE, free unsued mem and update size */
if ( beExact )
{
retExact = malloc( (i+1) * sizeof(char) );
if ( NULL == retExact )
goto fail;
memcpy( retExact, ret, (i+1) );
free(ret);
ret = retExact;
sz = i+1;
}
/* pass len and size to the caller */
if ( len ) *len = ln;
if ( size ) *size = sz;
return ret;
fail:
if (len)
*len = 0;
if (size)
*size = 0;
return NULL;
}
/* -----------------------------------------------
*
*/
char *s_get_ahead( void ) {
return s_new_from_stdin(0, NULL, NULL);
}
char *s_get_exact( void ) {
return s_new_from_stdin(1, NULL, NULL);
}
It uses internally an alloc-ahead buffer, which grows dynamically as needed (by 16-byte chunks every time) as the user types in characters at stdin. A pointer to the
created string is returned by the function (or NULL on error), while the
len and
size pointer arguments are used for passing to the caller the length and the size of the newly created string (or 0 on error).
The
beExact boolean argument can be used for deciding whether the function should create a string with exactly so many characters as the user types in +1 for for the terminating '\0' (TRUE) or if it should leave the allocated memory as an exact multiple of the alloc-ahead buffer (FALSE).
The last 2 functions are just wrappers isolating the
beExact behavior of the normal function, having the additional (dis)advantage that they do not return the length and the size of the created string. They can also be implemented as macros (if for example inlining is of greater importance).
In any case, the allocated memory returned by any of those functions should get
explicitly freed after we are done working with the returned string...
int main( void )
{
size_t len, size;
char *s = s_new_from_stdin(0, &len, &size);
if ( !s )
puts( "<NULL>" );
else {
puts( s );
printf( "len: %zu, size: %zu\n", len, size );
free(s);
}
return 0;
}