this is my source code. When I input a string "I am in CSE 2nd year." and replace CSE 2nd(ie loc 9 to 15) by ECE 3rd, I get some garbage vales at the end of string. Also there is a newline at the beggining of rslt2 string. There is something wrong with rslt2. Can anyone please rectify the error?
//splitting a string and replace latter part of string by another string
#include<stdio.h>
#include<string.h>
int main()
{
int i,count=0,loc2,scount=0,rcount=0,loc=0; //scount represents counter for subset and rcount for replacement and loc from where we will split the string
char str[100],sub[100],newss[100],rslt[100],rslt2[100]; //newss=new substr, rslt and rslt2=former and latter part of original string
printf("Enter a String:\n");
fgets(str,100,stdin);
printf("\nString Entered by User:\n");
fflush(stdin);
puts(str);
printf("\nLoc Char\n"); //Creates Colums 'Char' and 'Loc'
for(i=0;str[i]!='\0';i++)
{
count++; //Counts length of String
printf("%d. %c\n",count,str[i]); //Prints Characters with it its Corresponding Location
}
printf("\n\nLength of String: %d\n\n",count);
printf("Enter the locations of Characters from where subset will start and end: \n");
scanf("%d%d",&loc,&loc2); //stores indices of begining and end of substring
printf("\n\nSubset formed from Existing String:\n");
for(i=loc-1;i<loc2;i++)
{
scount++;
sub[i]=str[i]; //stores substring in "sub"
printf("%c",sub[i]);
}
printf("\n\nLength of Subset: %d\n",scount);
for(i=0;i<(loc-1);i++)
{
rslt[i]=str[i]; //Stores former part of string in resultant string
}
for(i=loc2;i<strlen(str);i++)
{
rslt2[i]=str[i]; //Stores latter part of string in resultant string2
}
printf("\n\nEnter a Replacement for Subset(Of Equal Length as that of Subset):\n");
fflush(stdin);
fgets(newss,100,stdin);
for(i=0;newss[i]!='\0';i++)
rcount++;
printf("\n\nLength of New Subset: %d\n",rcount-1); //-1 to subtract length of null char
if(rcount-1!=scount) //to check whether replacement string and substring are of same len
printf("\nSince length of both subsets is not same. \nHence Replacement is Not Possible\n");
else //Concatination of 3 substrings
{
printf("\nResultant String:\n");
for(i=0;i<(loc-1);i++)
printf("%c",rslt[i]);
printf("\n");
for(i=0;newss[i]!='\0';i++)
printf("%c",newss[i]);
for(i=loc2;rslt2[i]!='\0';i++)
printf("%c",rslt2[i]);
}
return 0;
}
Here's an example of how to implement that program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXL 100
int main( void )
{
int count, lengthI, lengthR, start, end;
char initial[MAXL], replacment[MAXL], range[MAXL], result[MAXL];
// get user input
printf( "Initial string: " );
fflush( stdout );
fgets( initial, MAXL, stdin );
printf( "Replacement string: " );
fflush( stdout );
fgets( replacment, MAXL, stdin );
printf( "Start and end: ");
fflush( stdout );
fgets( range, MAXL, stdin );
count = sscanf( range, "%d%d", &start, &end ); // indices of beginning and end of range in initial string
// remove newline character from the input strings, if necessary
lengthI = strlen( initial );
if ( lengthI > 0 && initial[lengthI - 1] == '\n' )
initial[--lengthI] = '\0';
lengthR = strlen( replacment );
if ( lengthR > 0 && replacment[lengthR - 1] == '\n' )
replacment[--lengthR ] = '\0';
// range checking to verify that user inputs are valid and the resulting string will fit into the buffer
if ( count != 2 || start < 0 || start > lengthI || end < start || end > lengthI )
{
fprintf( stderr, "Invalid start and end values\n" );
exit( 1 );
}
if ( lengthI + lengthR - (end - start) + 1 > MAXL )
{
fprintf( stderr, "Resulting string would be too long\n" );
exit( 2 );
}
// create a new string with the substring replaced
if ( start > 0 ) // copy characters from the initial string up to the start index
strncpy( result, initial, start ); // note: this step may leave the result string unterminated
strcpy( &result[start], replacment ); // append the repacement string
// guarantees the result string is terminated
if ( end < lengthI ) // append characters from the initial that are after the end index
strcat( result, &initial[end] ); // terminates the result string (provided that strcat is called)
// print the result
printf( "%s\n", result );
}
Comments:
Don't mix fgets and scanf. Even if you're aware of the issues involved with doing so, it's still easy to get it wrong. Best to just read lines with fgets, and then parse with sscanf as necessary.
fflush(stdin) is non-standard. fflush is only guaranteed to work on stdout. On some systems, fpurge can be used to erase unread input.
When dealing with strings in C (aka arrays of characters), range checking is essential. Buffer overruns are the #1 cause of crashes, unexpected behavior, and security holes in C programs. Always range check user inputs, and always verify that a newly created string will fit into the buffer provided.
Always make sure that a newly created string ends with a null character (aka '\0'), and be sure to include that null character when calculating buffer sizes.
Note that in the sample code, strncpy may leave the string unterminated. The subsequent call to strcpy will terminate the string. The strcat function will also terminate the string. However, the call to strcat is conditional based on user input. Hence, without the call to 'strcpy', we'd have extra work to do to guarantee the the string gets its mandatory null terminator.
use negated scanf ie scanf(" %[^\n]",str_name); instead of gets();
Related
Good afternoon, my question is conceptual. How can I make it generate a "fancy" error when the user incorrectly enters some data that does not correspond to the scanf() function? So as to only allow integers to be entered in the example below (not characters or array of characters or an inappropriate data).
For example:
#include <stdio.h>
int a;
printf("Enter a number\n");
scanf("%d", &a); //the user is supposed to enter a number
printf("Your number is %d ", a);
//but if the user enters something inappropriate, like a character, the program leads to
//undetermined behavior (which as I understand it interprets said character according to its
//value in the ASCII code).
From already thank you very much
In order to determine whether scanf was able to successfully convert the input to an integer, you should check the return value of scanf:
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int num;
printf( "Enter a number: " );
if ( scanf( "%d", &num ) != 1 )
{
printf( "Failed to convert input!\n" );
exit( EXIT_FAILURE );
}
printf( "Conversion successful! The number is %d.\n", num );
}
However, using scanf for line-based user input is generally not recommended, because scanf does not behave in an intuitive manner when dealing with that kind of input. For example, scanf will generally not consume an entire line of input at once. Instead, it will generally only consume the input that matches the argument, but will leave the rest of the line on the input stream, including the newline character.
Leaving the newline character on the input stream can already cause a lot of trouble. For example, see this question.
Also, if the user enters for example 6abc, then scanf will successfully match the 6 and report success, but leave abc on the input stream, so that the next call to scanf will probably immediately fail.
For this reason, it is generally better to always read one line of input at a time, using the function fgets. After successfully reading one line of input as a string, you can use the function strtol to attempt to convert the string to an integer:
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
char line[200], *p;
int num;
//prompt user for input
printf( "Enter a number: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
printf( "Input failure!\n" );
exit( EXIT_FAILURE );
}
//attempt to convert string to integer
num = strtol( line, &p, 10 );
if ( p == line )
{
printf( "Unable to convert to integer!\n" );
exit( EXIT_FAILURE );
}
//print result
printf( "Conversion successful! The number is %d.\n", num );
}
However, this code has the following issues:
It does not check whether the input line was too long to fit into the buffer.
It does not check whether the converted number is representable as an int, for example whether the number is too large to be stored in an int.
It will accept 6abc as valid input for the number 6. This is not as bad as scanf, because scanf will leave abc on the input stream, whereas fgets will not. However, it would probably still be better to reject the input instead of accepting it.
All of these issues can be solved by doing the following:
Issue #1 can be solved by checking
whether the input buffer contains a newline character, or
whether end-of-file has been reached, which can be treated as equivalent to a newline character, because it also indicates the end of the line.
Issue #2 can be solved by checking whether the function strtol set errno to the value of the macro constant ERANGE, to determine whether the converted value is representable as a long. In order to determine whether this value is also representable as an int, the value returned by strtol should be compared against INT_MIN and INT_MAX.
Issue #3 can be solved by checking all remaining characters on the line. Since strtol accepts leading whitespace characters, it would probably also be appropriate to accept trailing whitespace characters. However, if the input contains any other trailing characters, the input should probably be rejected.
Here is an improved version of the code, which solves all of the issues mentioned above and also puts everything into a function named get_int_from_user. This function will automatically reprompt the user for input, until the input is valid.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
int get_int_from_user( const char *prompt )
{
//loop forever until user enters a valid number
for (;;)
{
char buffer[1024], *p;
long l;
//prompt user for input
fputs( prompt, stdout );
//get one line of input from input stream
if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
{
fprintf( stderr, "Unrecoverable input error!\n" );
exit( EXIT_FAILURE );
}
//make sure that entire line was read in (i.e. that
//the buffer was not too small)
if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
{
int c;
printf( "Line input was too long!\n" );
//discard remainder of line
do
{
c = getchar();
if ( c == EOF )
{
fprintf( stderr, "Unrecoverable error reading from input!\n" );
exit( EXIT_FAILURE );
}
} while ( c != '\n' );
continue;
}
//attempt to convert string to number
errno = 0;
l = strtol( buffer, &p, 10 );
if ( p == buffer )
{
printf( "Error converting string to number!\n" );
continue;
}
//make sure that number is representable as an "int"
if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
{
printf( "Number out of range error!\n" );
continue;
}
//make sure that remainder of line contains only whitespace,
//so that input such as "6abc" gets rejected
for ( ; *p != '\0'; p++ )
{
if ( !isspace( (unsigned char)*p ) )
{
printf( "Unexpected input encountered!\n" );
//cannot use `continue` here, because that would go to
//the next iteration of the innermost loop, but we
//want to go to the next iteration of the outer loop
goto continue_outer_loop;
}
}
return l;
continue_outer_loop:
continue;
}
}
int main( void )
{
int number;
number = get_int_from_user( "Enter a number: " );
printf( "Input was valid.\n" );
printf( "The number is: %d\n", number );
return 0;
}
This program has the following behavior:
Enter a number: abc
Error converting string to number!
Enter a number: 6000000000
Number out of range error!
Enter a number: 6 7 8
Unexpected input encountered!
Enter a number: 6abc
Unexpected input encountered!
Enter a number: 6
Input was valid.
The number is: 6
How to get verified user input of a specific type
#1 Get user input as a string
char s[100];
if (!fgets( s, sizeof(s), stdin )) *s = '\0';
char * p = strptok( s, "\r\n" );
if (!p) complain_and_quit();
*p = '\0';
...
Alternately:
#define __STDC_WANT_LIB_EXT2__ 1
#include <stdio.h>
char * s = NULL;
size_t n = 0;
if (getline( &s, &n, stdin ) < 0)
{
free( s );
complain_and_quit();
}
...
free( s );
#2 Get rid of any trailing whitespace
This could easily be put in a trim() function, but here we’ll spell it out:
Can’t believe I forgot this step. Sorry. 😳
p = strchr( s, '\0' );
while (p-- != s) if (!isspace( *p )) break;
p[1] = '\0';
#3 Try to convert that string to the type of thing you want.
char * p;
int user_input = strtol( s, &p, 10 );
if (*p)
{
// Input was not JUST an integer.
// It could be something like "123 xyz", or "not-an-integer".
// Look at the value of p to figure out where the conversion went wrong.
complain();
}
do_something_with_an_integer( user_input );
That’s it!
I attempt to copy the contents of closure in res using strcpy, however i get what i think are soome garbage values before what actually is in closure, any ideas on how to solve this issue, all help is appreciated :) . Please ignore the rest of the scuffed code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int fdcount;
int i, j;
char closure[100];
char temp[20];
printf("nb fd: \n");
scanf("%d", &fdcount);
char left[fdcount][100];
char right[fdcount][100];
char res[fdcount][100];
for (i = 0; i < fdcount; i++) {
printf("left of fd %d: ", i + 1);
scanf(" %c", temp);
strcpy(left[i], temp);
printf("right of fd %d: ", i + 1);
scanf(" %c", temp);
strcpy(right[i], temp);
}
printf("closure ");
scanf(" %c", closure);
//system("cls");
printf("your fds \n");
for (i = 0; i < fdcount; i++) {
printf("%s -> %s \n", left[i], right[i]);
}
strcpy(closure, res[0]);
for (i = 0; i < fdcount; i++) {
printf(" %s", res[i]);
}
return 0;
}
The line
strcpy(closure, res[0]);
will copy the string in res[0] to closure.
You probably want to do the opposite. You want to copy the string in closure to res[0]. In that case, you should swap the two arguments, by changing the line to the following:
strcpy( res[0], closure );
Another problem is that using the function strcpy requires the second argument to actually be a string (i.e. a sequence of characters terminated by a null character). If it is not, then your program will be invoking undefined behavior (i.e. your program may crash).
The content of closure is not a string, because the line
scanf(" %c", closure);
will only write a single character to closure. It won't write a terminating null character afterwards.
If you want to make closure a valid string, then all you have to do is add a terminating null character after the character, like this:
closure[1] = '\0';
On the other hand, if you want to read a whole line of input as a string, then you could use the function fgets instead:
fgets( closure, sizeof closure, stdin );
Note that the function fgets will also read the newline character at the end of the line, and store it as part of the string. If you do not want this, see the following question for different possibilities on how to remove this newline character:
Removing trailing newline character from fgets() input
Another problem in your code is that the following loop is wrong:
for (i = 0; i < fdcount; i++) {
printf(" %s", res[i]);
}
When using printf with the %s conversion format specifier, the argument must be a valid null-terminated string. Since you are calling printf in a loop, the contents of res[0] up to res[fdcount-1] must contain valid strings. However, even after making the fixes mentioned above, only res[0] will be a valid string with a null terminating character. The content of res[1] to res[fdcount-1] will still contain garbage data. Therefore, you should not attempt to treat this garbage data as strings, because doing so will invoke undefined behavior.
Here is a short demonstration program which reads a line of input into closure, copies it, and then prints the copy:
#include <stdio.h>
#include <string.h>
int main( void )
{
int fdcount = 20;
char closure[100];
char res[fdcount][100];
//prompt user for input
printf( "Please enter a line of input: " );
//read one line of user input
fgets( closure, sizeof closure, stdin );
//remove newline character from input
closure[strcspn(closure,"\n")] = '\0';
//copy input to res[0]
strcpy( res[0], closure );
//print copy
printf( "Contents of res[0]: %s\n", res[0] );
}
This program has the following behavior:
Please enter a line of input: This is a test.
Contents of res[0]: This is a test.
Of course, it does not make much sense to create an array of strings, if you are only using one string.
Here is the same program with full error checking:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main( void )
{
int fdcount = 20;
char closure[100];
char res[fdcount][100];
char *p;
//prompt user for input
printf( "Please enter a line of input: " );
//attempt to read one line of user input
if ( fgets( closure, sizeof closure, stdin ) == NULL )
{
fprintf( stderr, "Input error!\n" );
exit( EXIT_FAILURE );
}
//verify that input buffer was large enough to store entire line
p = strchr( closure, '\n' );
if ( p == NULL )
{
if ( !feof( stdin ) )
{
fprintf( stderr, "Line too long to fit into buffer!\n" );
exit( EXIT_FAILURE );
}
}
else
{
//remove newline character from input
*p = '\0';
}
//copy input to res[0]
strcpy( res[0], closure );
//print copy
printf( "Contents of res[0]: %s\n", res[0] );
}
Please ignore the rest of the scuffed code.
The rest of your code is hard to ignore, because it is also invoking undefined behavior, so that the behavior of your entire program is undefined (i.e. your program may crash).
The lines
strcpy(left[i], temp);
and
strcpy(right[i], temp);
require temp to be a string, which it is not, because it is not terminated by a null character.
So I need to read an integer from the stdin where the user may input 1.0, however since this is a double I wouldn't want to accept it. However when I try the method below the 1.0 is converted to 1 and is accepted. I would also like to accept 0001 as a possible integer input as 1.
first_sentence_to_switch = 0;
char buf[15]; // large enough
int number;
wrong_input = 0;
scanf("%14s", buf); // read everything we have in stdin
// printf("buffer: %s", buf);
if (sscanf(buf, "%d", &number) == 1)
{
first_sentence_to_switch = number;
}
else
{
wrong_input = 1;
}
You can use the %n format option to tell how much was matched by an sscanf call to make sure there is no extra cruft on the line:
if (sscanf(buf, "%d %n", &number, &end) == 1 && buf[end] == 0) {
.. ok
} else {
.. not an integer or something else in the input (besides whitespace) after the integer
Note the space between the %d and %n to skip any whitespace that might exist at the end of the buffer (such as a newline if the input was read by fgets or getline)
How to read a whole line of input
The line
scanf("%14s", buf);
will never read a whole line of input. It will only read a single word of input (which can also consist of digits). For example, if the user enters invalid input such as
"39 jdsuoew"
on a single line, then it will only read the word "39" as input, leaving the rest of the line on the input stream. This means that your program will accept the input as valid, although it should probably be rejected in this case.
Even if the user only entered "39", then it will only read this number, but will leave the newline character on the input stream, which can cause trouble.
If you want to ensure that it reads the entire line, I recommend that you use the function fgets instead, as that function will always read a whole line of input (including the newline character), assuming that the size of the provided memory buffer is large enough to store the entire line.
char line[100];
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
fprintf( stderr, "Input error!\n" );
exit( EXIT_FAILURE );
}
//search for newline character, to verify that entire line was read in
if ( strchr( line, '\n' ) == NULL )
{
fprintf( stderr, "Line was too long for input buffer!\n" );
exit( EXIT_FAILURE );
}
Note that the function strchr requires that you #include <string.h>. If, as you state in the comments section, you are not allowed to use that header file, then you will probably have to assume that the memory buffer was large enough for the entire line, without verifying it (which you are also doing in your code). Although it is possible to verify this without using the function strchr, I don't recommend doing this. If the buffer is made large enough, then it is unlikely (but still possible) for the line to not fit into the buffer.
Convert string to integer using strtol
After reading the input line into a memory buffer, you can either use the function sscanf or strtol to attempt to convert the integer to a number. I recommend that you use the function strtol, because the function sscanf has undefined behavior if the user enters a number that is too large to be represented as a long int, whereas the function strtol is able to report such an error condition reliably.
In order to convert the line that you read to an integer, you could simply call strtol like this:
long l;
l = strtol( line, NULL, 10 );
However, calling the function with the second argument set to NULL has the same problem as calling the function atoi: You have no way of knowing whether the input was successfully converted, or if a conversion error occured. And you also have no way of knowing how much of the input was successfully converted, and whether the conversion failed prematurely, for example due to the user entering the decimal point of a floating-point number.
Therefore, it is better to call the function like this:
long l;
char *p;
l = strtol( line, &p, 10 );
Now, the pointer p will point to the first character that was not successfully converted to a number. In the ideal case, it will be pointing to the newline character at the end of the line (or maybe the terminating null character if you are not using fgets). So you could verify that the whole line was converted, and that at least one character was converted, like this:
if ( p == line || *p != '\n' )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
However, this is maybe a bit too strict. For example, if the user enters "39 " (with a space after the number), the input will be rejected. You probably would want to accept the input in this case. Therefore, instead of requiring that p is pointing to the newline character and thereby not accepting any other remaining characters on the line, you may want permit whitespace characters to remain in the line, like this:
if ( p == line )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
while ( *p != '\n' )
{
//verify that remaining character is whitespace character
if ( !isspace( (unsigned char)*p ) )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
p++;
}
Note that you must #include <ctype.h> in order to use the function isspace.
Also, as previously stated, the advantage of using the function strtol over sscanf is that it can reliably report whether the number is too large or too small to be representable as a long int. If such an error condition occurs, it will set errno to ERANGE. Note that you must #include <errno.h> in order to use errno.
long l;
char *p;
errno = 0; //make sure that errno is not already set to ERANGE
l = strtol( line, &p, 10 );
if ( errno == ERANGE )
{
printf( "Number out of range!\n" );
exit( EXIT_FAILURE );
}
Code example of fgets and strtol
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
int main( void )
{
char line[100], *p;
long l;
//prompt user for input
printf( "Please enter an integer: " );
//attempt to read one line of input
if ( fgets( line, sizeof line, stdin ) == NULL )
{
fprintf( stderr, "Input error!\n" );
exit( EXIT_FAILURE );
}
//search for newline character, to verify that entire line was read in
if ( strchr( line, '\n' ) == NULL )
{
fprintf( stderr, "Line was too long for input buffer!\n" );
exit( EXIT_FAILURE );
}
//make sure that errno is not already set to ERANGE
errno = 0;
//attempt to convert input to integer
l = strtol( line, &p, 10 );
//verify that conversion was successful
if ( p == line )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
//check for range error
if ( errno == ERANGE )
{
printf( "Number out of range!\n" );
exit( EXIT_FAILURE );
}
//verify that there are either no remaining characters, or that
//all remaining characters are whitespace characters
while ( *p != '\n' )
{
//verify that remaining character is whitespace character
if ( !isspace( (unsigned char)*p ) )
{
printf( "Error converting number!\n" );
exit( EXIT_FAILURE );
}
p++;
}
//print valid input
printf( "Input is valid.\nYou entered: %ld\n", l );
}
This program has the following output:
Valid input:
Please enter an integer: 39
Input is valid.
You entered: 39
Junk after valid input on same line:
Please enter an integer: 39 jdsuoew
Error converting number!
Attempt to enter floating-point number instead of integer:
Please enter an integer: 1.0
Error converting number!
Attempt to enter number that is so large that it is not representable as a long int:
Please enter an integer: 10000000000000000000000000
Number out of range!
Since there could be a bunch of possible wrong inputs, you should probably look only for right ones: '1' and '0'.
'I would also like to accept 0001 ...'
I only assume from your explanation that you wouldn't want to accept something
like: 0011
I would look from the end of buffer towards beginning.
In another words:
I'd look only for single '1' at the end of buffer and then only for '0' (zeros)
until you reach the beginning of buf.
Everything else is a wrong input.
Since you arbitrarely choose buffer size, you could write something like:
#define BUFF_SZ 15
...
char buf[BUFF_SZ];
...
while (buf[++i]); // <-- to avoid measuring buffer size at runtime.
This is an example of code with a function that returns correct result:
#include <stdio.h>
int check_input (char *buf);
int main()
{
char buf[15]; // large enough
scanf("%14s", buf);
if (check_input(buf) == 0) { printf("Wrong input!"); return(1); };
... input OK ...
return (0);
}
// function returns: 1: on success, 0: on wrong input
int check_input (char *buf)
{
int i=0;
while (buf[++i]); // it will stop counting when NULL char is found ..
// so it's like 'strlen(buff)'
// without unnecessary including <string.h>
// buffer is set at end so check if it ends with '1' ..
if (buf[--i] != '1') return (0);
// now, check all buffer backwards to make sure that ALL of those are '0's..
while ((--i) > 0)
if (buf[i] != '0') return (0);
return (1);
}
I've written most important part as a funtion so it would be more readable.
I hope that could help.
I'm writing a C program that parses user input into a char, and two strings of set length. The user input is stored into a buffer using fgets, and then parsed with sscanf. The trouble is, the three fields have a maximum length. If a string exceeds this length, the remaining characters before the next whitespace should be consumed/discarded.
#include <stdio.h>
#define IN_BUF_SIZE 256
int main(void) {
char inputStr[IN_BUF_SIZE];
char command;
char firstname[6];
char surname[6];
fgets(inputStr, IN_BUF_SIZE, stdin);
sscanf(inputStr, "%c %5s %5s", &command, firstname, surname);
printf("%c %s %s\n", command, firstname, surname);
}
So, with an input of
a bbbbbbbb cc
the desired output would be
a bbbbb cc
but is instead the output is
a bbbbb bbb
Using a format specifier "%c%*s %5s%*s %5s%*s" runs into the opposite problem, where each substring needs to exceed the set length to get to the desired outcome.
Is there way to achieve this by using format specifiers, or is the only way saving the substrings in buffers of their own before cutting them down to the desired length?
In addition to the other answers, never forget when facing string parsing problems, you always have the option of simply walking a pointer down the string to accomplish any type parsing you require. When you read your string into buffer (my buf below), you have an array of characters you are free to analyze manually (either with array indexes, e.g. buffer[i] or by assigning a pointer to the beginning, e.g. char *p = buffer;) With your string, you have the following in buffer with p pointing to the first character in buffer:
--------------------------------
|a| |b|b|b|b|b|b|b|b| |c|c|\n|0| contents
--------------------------------
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 index
|
p
To test the character pointed to by p, you simply dereference the pointer, e.g. *p. So to test whether you have an initial character between a-z followed by a space at the beginning of buffer, you simply need do:
/* validate first char is 'a-z' and followed by ' ' */
if (*p && 'a' <= *p && *p <= 'z' && *(p + 1) == ' ') {
cmd = *p;
p += 2; /* advance pointer to next char following ' ' */
}
note:, you are testing *p first, (which is the shorthand for *p != 0 or the equivalent *p != '\0') to validate the string is not empty (e.g. the first char isn't the nul-byte) before proceeding with further tests. You would also include an else { /* handle error */ } in the event any one of the tests failed (meaning you have no command followed by a space).
When you are done, your are left with p pointing to the third character in buffer, e.g.:
--------------------------------
|a| |b|b|b|b|b|b|b|b| |c|c|\n|0| contents
--------------------------------
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 index
|
p
Now your job is simply, just advance by no more than 5 characters (or until the next space is encountered, assigning the characters to firstname and then nul-terminate following the last character:
/* read up to NLIM chars into fname */
for (n = 0; n < NMLIM && *p && *p != ' ' && *p != '\n'; p++)
fname[n++] = *p;
fname[n] = 0; /* nul terminate */
note: since fgets reads and includes the trailing '\n' in buffer, you should also test for the newline.
When you exit the loop, p is pointing to the seventh character in the buffer as follows:
--------------------------------
|a| |b|b|b|b|b|b|b|b| |c|c|\n|0| contents
--------------------------------
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 index
|
p
You now simply read forward until you encounter the next space and then advance past the space, e.g.:
/* discard remaining chars up to next ' ' */
while (*p && *p != ' ') p++;
p++; /* advance to next char */
note: if you exited the firstname loop pointing at a space, the above code does not execute.
Finally, all you do is repeat the same loop for surname that you did for firstname. Putting all the pieces of the puzzle together, you could do something similar to the following:
#include <stdio.h>
enum { NMLIM = 5, BUFSIZE = 256 };
int main (void) {
char buf[BUFSIZE] = "";
while (fgets (buf, BUFSIZE, stdin)) {
char *p = buf, cmd, /* start & end pointers */
fname[NMLIM+1] = "",
sname[NMLIM+1] = "";
size_t n = 0;
/* validate first char is 'a-z' and followed by ' ' */
if (*p && 'a' <= *p && *p <= 'z' && *(p + 1) == ' ') {
cmd = *p;
p += 2; /* advance pointer to next char following ' ' */
}
else { /* handle error */
fprintf (stderr, "error: no single command followed by space.\n");
return 1;
}
/* read up to NLIM chars into fname */
for (n = 0; n < NMLIM && *p && *p != ' ' && *p != '\n'; p++)
fname[n++] = *p;
fname[n] = 0; /* nul terminate */
/* discard remaining chars up to next ' ' */
while (*p && *p != ' ') p++;
p++; /* advance to next char */
/* read up to NLIM chars into sname */
for (n = 0; n < NMLIM && *p && *p != ' ' && *p != '\n'; p++)
sname[n++] = *p;
sname[n] = 0; /* nul terminate */
printf ("input : %soutput : %c %s %s\n",
buf, cmd, fname, sname);
}
return 0;
}
Example Use/Output
$ echo "a bbbbbbbb cc" | ./bin/walkptr
input : a bbbbbbbb cc
output : a bbbbb cc
Look things over an let me know if you have any questions. No matter how elaborate the string or what you need from it, you can always get what you need by simply walking a pointer (or a pair of pointers) down the length of the string.
One way to split the input buffer as OP desires is to use multiple calls to sscanf(), and to use the %n conversion specifier to keep track of the number of characters read. In the code below, the input string is scanned in three stages.
First, the pointer strPos is assigned to point to the first character of inputStr. Then the input string is scanned with " %c%n%*[^ ]%n". This format string skips over any initial whitespaces that a user might enter before the first character, and stores the first character in command. The %n directive tells sscanf() to store the number of characters read so far in the variable n; then the *[^ ] directive tells sscanf() to read and ignore any characters until a whitespace character is encountered. This effectively skips over any remaining characters that were entered after the initial command character. The %n directive appears again, and overwrites the previous value with the number of characters read until this point. The reason for using %n twice is that, if the user enters a character followed by a whitespace (as expected), the second directive will find no matches, and sscanf() will exit without ever reaching the final %n directive.
The pointer strPos is moved to the beginning of the remaining string by adding n to it, and sscanf() is called a second time, this time with "%5s%n%*[^ ]%n". Here, up to 5 characters are read into the character array firstname[], the number of characters read is saved by the %n directive, any remaining non-whitespace characters are read and ignored, and finally, if the scan made it this far, the number of characters read is saved again.
strPos is increased by n again, and the final scan only needs "%s" to complete the task.
Note that the return value of fgets() is checked to be sure that it was successful. The call to fgets() was changed slightly to:
fgets(inputStr, sizeof inputStr, stdin)
The sizeof operator is used here instead of IN_BUF_SIZE. This way, if the declaration of inputStr is changed later, this line of code will still be correct. Note that the sizeof operator works here because inputStr is an array, and arrays do not decay to pointers in sizeof expressions. But, if inputStr were passed into a function, sizeof could not be used in this way inside the function, because arrays decay to pointers in most expressions, including function calls. Some, #DavidC.Rankin, prefer constants as OP has used. If this seems confusing, I would suggest sticking with the constant IN_BUF_SIZE.
Also note that the return values for each of the calls to sscanf() are checked to be certain that the input matches expectations. For example, if the user enters a command and a first name, but no surname, the program will print an error message and exit. It is worth pointing out that, if the user enters say, a command character and first name only, after the second sscanf() the match may have failed on \n, and strPtr is then incremented to point to the \0 and so is still in bounds. But this relies on the newline being in the string. With no newline, the match might fail on \0, and then strPtr would be incremented out of bounds before the next call to sscanf(). Fortunately, fgets() retains the newline, unless the input line is larger than the specified size of the buffer. Then there is no \n, only the \0 terminator. A more robust program would check the input string for \n, and add one if needed. It would not hurt to increase the size of IN_BUF_SIZE.
#include <stdio.h>
#include <stdlib.h>
#define IN_BUF_SIZE 256
int main(void)
{
char inputStr[IN_BUF_SIZE];
char command;
char firstname[6];
char surname[6];
char *strPos = inputStr; // next scan location
int n = 0; // holds number of characters read
if (fgets(inputStr, sizeof inputStr, stdin) == NULL) {
fprintf(stderr, "Error in fgets()\n");
exit(EXIT_FAILURE);
}
if (sscanf(strPos, " %c%n%*[^ ]%n", &command, &n, &n) < 1) {
fprintf(stderr, "Input formatting error: command\n");
exit(EXIT_FAILURE);
}
strPos += n;
if (sscanf(strPos, "%5s%n%*[^ ]%n", firstname, &n, &n) < 1) {
fprintf(stderr, "Input formatting error: firstname\n");
exit(EXIT_FAILURE);
}
strPos += n;
if (sscanf(strPos, "%5s", surname) < 1) {
fprintf(stderr, "Input formatting error: surname\n");
exit(EXIT_FAILURE);
}
printf("%c %s %s\n", command, firstname, surname);
}
Sample interaction:
a Zaphod Beeblebrox
a Zapho Beebl
The fscanf() functions have a reputation for being subtle and error-prone; the format strings used above may seem a little bit tricky. By writing a function to skip to the next word in the input string, the calls to sscanf() can be simplified. In the code below, skipToNext() takes a pointer to a string as input; if the first character of the string is a \0 terminator, the pointer is returned unchanged. All initial non-whitespace characters are skipped over, then any whitespace characters are skipped, up to the next non-whitespace character (which may be a \0). A pointer is returned to this non-whitespace character.
The resulting program is a little bit longer than the previous program, but it may be easier to understand, and it certainly has simpler format strings. This program does differ from the first in that it no longer accepts leading whitespace in the string. If the user enters whitespace before the command character, this is considered erroneous input.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#define IN_BUF_SIZE 256
char * skipToNext(char *);
int main(void)
{
char inputStr[IN_BUF_SIZE];
char command;
char firstname[6];
char surname[6];
char *strPos = inputStr; // next scan location
if (fgets(inputStr, sizeof inputStr, stdin) == NULL) {
fprintf(stderr, "Error in fgets()\n");
exit(EXIT_FAILURE);
}
if (sscanf(strPos, "%c", &command) != 1 || isspace(command)) {
fprintf(stderr, "Input formatting error: command\n");
exit(EXIT_FAILURE);
}
strPos = skipToNext(strPos);
if (sscanf(strPos, "%5s", firstname) != 1) {
fprintf(stderr, "Input formatting error: firstname\n");
exit(EXIT_FAILURE);
}
strPos = skipToNext(strPos);
if (sscanf(strPos, "%5s", surname) != 1) {
fprintf(stderr, "Input formatting error: surname\n");
exit(EXIT_FAILURE);
}
printf("%c %s %s\n", command, firstname, surname);
}
char * skipToNext(char *c)
{
int inWord = isspace(*c) ? 0 : 1;
if (inWord && *c != '\0') {
while (!isspace(*c)) {
++c;
}
}
inWord = 0;
while (isspace(*c)) {
++c;
}
return c;
}
struct DVDInfo *ReadStruct( void ) {
struct DVDInfo *infoPtr;
int num;
char line[ kMaxLineLength ];
char *result;
infoPtr = malloc( sizeof( struct DVDInfo ) );
if ( NULL == infoPtr ) {
printf( "Out of memory!!! Goodbye!\n" );
exit( 0 );
}
printf( "Enter DVD Title: " );
result = fgets( line, kMaxLineLength, stdin );
line[ strlen( line ) - 1 ] = '\0';
infoPtr->title = MallocAndCopy( line );
printf( "Enter DVD comment: " );
result = fgets( line, kMaxLineLength, stdin );
line[ strlen( line ) - 1 ] = '\0';
infoPtr->comment = MallocAndCopy( line );
do {
printf( "Enter DVD Rating (1-10): " );
scanf( "%d", &num );
Flush();
}
while ( ( num < 1 ) || ( num > 10 ) );
infoPtr->rating = num;
printf( "\n----------\n" );
return( infoPtr );
}
I asked a different question about this code in another thread on stackoverflow but didn't want to double up on that one - why is the terminating zero being added to the end of these files read in by fgets? fgets adds the terminating zero anyway, isn't this overkill?
Generally, you replace the newline character that fgets adds to the string with a NUL character. In all cases, fgets will NUL-terminate.
See: http://www.opengroup.org/onlinepubs/009695399/functions/fgets.html
fgets writes a nul terminator into the buffer you provide (if you specify the buffer size as larger than 0). Otherwise you could not call strlen() on it, strlen() expects a string, and if it isn't nul terminated it is not a string.
You're asking about
line[ strlen( line ) - 1 ] = '\0';
This strips off the last character in line .If you've read a line, it replaces the last character, presumably a \n with a nul terminator.
Consider that fgets just read a line, e.g. your line buffer now contains the string "Hello\n" (the \n is just the escape sequence here, it's actually just 1 character, not 2)
strlen ("Hello\n") is 6, and 6-1 is 5, so the 5. index is replaced by 0
"Hello\n"
^
|
Add 0 terminator
Result:
"Hello"
Just be careful:
you don't want to do line[ strlen(line) - 1 ] = '\0'; on an empty string, in that case you'll end up doing line[-1].
You should check if fgets succeds. You don't want to poke around in line if fgets failed, and didn't write anything to your buffer.
You might want to check whether a whole line actually got read. IF the line you read is larger than
kMaxLineLength ,or e.g. if the last "line" in the file doesn't have a trailing \n , strlen(line) -1 will not be a \n (newline).
Your
result = fgets( line, kMaxLineLength, stdin );
is Ok since the size of line is kMaxLineLength.
fgets reads in at most one less than size characters from stream and stores them into the buffer ...
The line[ strlen( line ) - 1 ] = '\0'; are unnecessary (and insecure — strlen() isn't going to work properly if the string isn't already nul-terminated). fgets() will nul-terminate the buffer. Also, you should be checking that result != NULL before attempting to copy line. fgets() returns NULL at end-of-file or if an error occurs.
Yes, it's overkill.
One suggestion to make it more robust against code rot... change
result = fgets( line, kMaxLineLength, stdin );
to
result = fgets( line, sizeof(line), stdin );