The problem is that the fscanf
format string "%c %s %c\n"
contains a trailing whitespace character (in this case \n
). This whitespace character tells fscanf
to read and discard all whitespace characters from the input stream until it encounters a non-whitespace character.
Therefore, when fscanf
reads the lines
i hello l
abc z
it will successfully match l
, hello
and l
, but then it will read and discard the whitespace characters "\n "
. However, you only want it to read and discard "\n"
.
There are several ways of solving this problem:
Instead of using a whitespace character which instructs fscanf
to read as many whitespace characters as possible, you can use the %*c
format specifier to read and discard only a single character. However, this will only work if that character is guaranteed to be the newline character.
A more robust solution would be to first read the line using fgets
instead of fscanf
. In contrast to fscanf
, fgets
will always read exactly one entire line (if it can), so it will not leave any characters of a line on the input stream, and it also will not attempt to read characters of the next line. After reading the line and storing the line in a memory buffer as a string, you can use sscanf
on that memory buffer.
Here is an example of the second solution:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
//forward function declaration
bool get_line_from_stream( char buffer[], int buffer_size, FILE *fp );
int main( void )
{
FILE *fp;
char line[200];
fp = fopen( "input.txt", "r" );
if ( fp == NULL )
{
fprintf( stderr, "Error opening file!\n" );
exit( EXIT_FAILURE );
}
while ( get_line_from_stream( line, sizeof line, fp ) )
{
char one, two[20], three;
if ( sscanf( line, "%c %19s %c", &one, two, &three ) == 3 )
{
printf(
"Successfully read the following record:\n"
" One: %c\n"
" Two: %s\n"
" Three: %c\n",
one, two, three
);
}
else
{
fprintf( stderr, "Parsing error!\n" );
}
}
fclose( fp );
}
//This function will read exactly one line of input and remove the
//newline character, if it exists. On success, it will return true.
//If this function is unable to read any further lines due to
//end-of-file, it returns false. If it fails for any other reason, it
//will not return, but will print an error message and call "exit"
//instead.
bool get_line_from_stream( char buffer[], int buffer_size, FILE *fp )
{
char *p;
//attempt to read one line from the stream
if ( fgets( buffer, buffer_size, fp ) == NULL )
{
if ( !feof(fp) )
{
fprintf( stderr, "Input error!\n" );
exit( EXIT_FAILURE );
}
return false;
}
//make sure that line was not too long for input buffer
p = strchr( buffer, '\n' );
if ( p == NULL )
{
//a missing newline character is ok if the next
//character is a newline character or if we have
//reached end-of-file
if ( !feof(fp) && getc(fp) != '\n' )
{
fprintf( stderr, "Line is too long for memory buffer!\n" );
exit( EXIT_FAILURE );
}
}
else
{
//remove newline character by overwriting it with a null
//character
*p = '\0';
}
return true;
}
For the input
a hi c
i hello l
abc z
2 mystr k
this program has the following output:
Successfully read the following record:
One: a
Two: hi
Three: c
Successfully read the following record:
One: i
Two: hello
Three: l
Successfully read the following record:
One:
Two: abc
Three: z
Successfully read the following record:
One: 2
Two: mystr
Three: k