Reputation: 51
I have a program that needs to get an int
from the user from the command line
int main(int argc, char* argv[])
My only problem is that I need to check whether argv
is an int
. If it isn't, I need to return an error. How can I do that? I have to check if the input is an int
before using atoi
. Can someone help me?
Upvotes: 2
Views: 4179
Reputation: 154602
Command line argument validation in C
I need to check whether argv is an int
argv[]
contains a string by checking argc
. for (int a = 1; a < argc; a++) {
int_validation(argv[a]);
}
strtol()
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
void int_validation(const char *s) {
// If leading space not OK
// isspace() only valid in unsigned char range and EOF.
if (isspace(*(unsigned char *)s)) {
puts("Fail - leading spaces");
return;
}
// Convert
int base = 0; // Use 10 for base 10 only input
char *endptr;
errno = 0;
long val = strtol(s, &endptr, base);
if (s == endptr) { // When endptr is same as s, no conversion happened.
puts("Fail - no conversion");
return;
}
// detect overflow
if (errno == ERANGE || val < INT_MIN || val > INT_MAX) {
errno = ERANGE;
puts("Fail - overflow");
return;
}
// If trailing space OK, seek pass them
while (isspace(*(unsigned char *)endptr)) {
endptr++;
}
// If trailing non-numeric text bad
if (*endptr) {
puts("Fail - overflow");
return;
}
printf("Success %d\n", (int) val);
return;
}
Adjust return type and messages as desired.
Typically input like "1e5"
or "123.0"
, although mathematically a whole number, is not consider valid int
input. Additional code needed to allow those.
Upvotes: 4
Reputation: 74385
Try this. I'll let you sort error messaging however you like:
#define TRUE 1
#define FALSE 0
int is_integer( char *s )
{
int i = 0 ;
int is_digit;
int is_sign;
while ( s[i] != '\0' )
{
// this test makes the assumption that the code points for
// decimal digits are contiguous. True for ASCII/UNICODE and EBCDIC.
// If you're using some other bizarro encoding, you're out of luck.
is_digit = s[i] >= '0' && s[i] <= '9' ? TRUE : FALSE ;
is_sign = i == 0 && s[i] == '-' ? TRUE : FALSE ;
if ( !is_digit && !is_sign )
{
return FALSE;
}
++i;
}
return TRUE;
}
int main( int argc, char *argv[] )
{
int i = 0 ;
int cc = 0 ; // assume success;
for ( i = 0 ; i < argc ; ++i )
{
if ( !is_integer(argv[i]) )
{
cc = 1;
}
}
return cc; // exit code 0 is success; non-zero exit code is failure.
}
Upvotes: 0
Reputation: 23832
You can try to convert the argument using strtol()
, it will return 0
if the value is not parseable or the parsed value.
You can also use the second argument for a more detailed verification of the input, you can differenciate between a bad input or a 0
input, since in both cases the returned value is 0
.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc, char *argv[])
{
long parsed_value = 0;
int value = 0;
//for command + 1 argument
if (argc == 2)
{
errno = 0;
char *end_ptr;
parsed_value = strtol(argv[1], &end_ptr, 10);
//argument check, overflow, trailing characters, underflow, errno
if(*end_ptr == argv[1][0] || *end_ptr != '\0' || errno == ERANGE
|| parsed_value < INT_MIN || parsed_value > INT_MAX){
fprintf(stderr, "Invalid argument");
return EXIT_FAILURE;
}
}
else{
fprintf(stderr, "Wrong number of arguments, %d provided, 2 needed", argc);
return EXIT_FAILURE;
}
//otherwise the value was parsed correctly
value = parsed_value;
printf("%d", value);
}
Upvotes: 2
Reputation: 33666
Here's one way, using strtol
and checking the end of the string:
#include <stdio.h>
#include <stdlib.h>
int
main(int argc,char **argv)
{
char *cp;
long lval;
int val;
// skip over program name
--argc;
++argv;
if (argc < 1) {
fprintf(stderr,"main: no argument specified\n");
exit(1);
}
cp = *argv;
if (*cp == 0) {
fprintf(stderr,"main: argument an empty string\n");
exit(1);
}
lval = strtol(cp,&cp,10);
if (*cp != 0) {
fprintf(stderr,"main: argument '%s' is not an integer -- '%s'\n",
*argv,cp);
exit(1);
}
val = (int) lval;
// NOTE: just going for extra credit here ;-)
// ensure number fits in a int (since strtol returns long and that's 64
// bits on a 64 bit machine)
#if 1
if (val != lval) {
fprintf(stderr,"main: argument '%s' (with value %ld) is too large to fit into an integer -- truncated to %d\n",
*argv,lval,val);
exit(1);
}
#endif
printf("val=%d\n",val);
return 0;
}
UPDATE:
Minor: Code does not detect conversion overflow of strtol() Code incorrectly assumes range of long more than int. If same range,
if (val != lval)
is always true. Suggest looking aterrno, INT_MAX,INT_MIN
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
int
main(int argc,char **argv)
{
char *cp;
long lval;
int val;
// skip over program name
--argc;
++argv;
if (argc < 1) {
fprintf(stderr,"main: no argument specified\n");
exit(1);
}
cp = *argv;
if (*cp == 0) {
fprintf(stderr,"main: argument an empty string\n");
exit(1);
}
errno = 0;
lval = strtol(cp,&cp,10);
if (*cp != 0) {
fprintf(stderr,"main: argument '%s' is not an integer -- '%s'\n",
*argv,cp);
exit(1);
}
// on a 32 bit machine, entering 2147483648 will produce a non-zero errno
if (errno) {
fprintf(stderr,"main: argument '%s' parse error -- '%s'\n",
*argv,strerror(errno));
exit(1);
}
// on a 64 bit machine, entering 2147483648 will not produce an error, so
// we should check the range ourselves
if ((lval < INT_MIN) || (lval > INT_MAX)) {
fprintf(stderr,"main: argument '%s' range error -- %ld outside of range (%ld to %ld)\n",
*argv,lval,(long) INT_MIN,(long) INT_MAX);
exit(1);
}
val = (int) lval;
// NOTE: just going for extra credit here ;-)
// ensure number fits in a int (since strtol returns long and that's 64
// bits on a 64 bit machine)
// FIXME -- with above tests this can never be true (i.e. fault), so
// I've nop'ed it -- left in to show prior/original test
#if 0
if (val != lval) {
fprintf(stderr,"main: argument '%s' (with value %ld) is too large to fit into an integer -- truncated to %d\n",
*argv,lval,val);
exit(1);
}
#endif
printf("val=%d\n",val);
return 0;
}
Upvotes: 8
Reputation: 7490
As an alternative to strtol()
(that is the canonical answer) you can perform an hand made validation by using isdigit()
function, and also checking for leading sign characters (+
and -
):
#include <ctype.h>
#include <bool.h>
bool isValidInteger( char * str )
{
bool ret = true;
if( str )
{
char p = str;
for( int i=0; str[i] != 0; i++ )
{
if ( !isdigit( str[i] ) )
{
if( i == 0 && ( str[i] == '+' || *p == '-' ) && str[i+1] )
continue;
ret = false;
break;
}
}
}
else
{
return false;
}
return ret;
}
This implementation relies on the fact that input string is null terminated. But since every argv[N]
is a null terminated string, we are fine.
Usage:
if ( isValidInteger( argv[1] ) )
{
int par = atoi( argv[1] );
}
Note (1): this validator doesn't check against input values that exceed the int
range (from INT_MIN
to INT_MAX
). This is a limitation that might be considered acceptable in many cases.
Note (2): this function doesn't trim leading spaces like strto*
do. If such feature is required, a check like this one can be added at the top of the for-loop
:
bool flag = true;
if( str[i] == ' ' )
continue;
flag = false;
In this way spaces will be tolerated, but only until flag
is set to false
that is when the first non-space character is encountered.
Upvotes: 0
Reputation: 50077
Use isdigit
to test the characters of the argument for "digit-ness", then convert it (or not) based on the results. For example:
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>
#include <stdlib.h>
bool is_all_digits(char *s)
{
bool b = true;
for( ; *s ; ++s)
if(!isdigit(*s))
{
b = false;
break;
}
return b;
}
int main(int argc, char *argv[])
{
for(int i = 0 ; i < argc ; ++i)
{
if(is_all_digits(argv[i]))
printf("argv[%d] is an integer = %d\n", i, atoi(argv[i]));
else
printf("argv[%d] is not an integer \"%s\"\n", i, argv[i]);
}
return 0;
}
When run with the command-line arguments
123 "Not a number" 456.789 "Not another number" 10
the following output is produced:
argv[0] is not an integer "./a.out"
argv[1] is an integer = 123
argv[2] is not an integer "Not a number"
argv[3] is not an integer "456.789"
argv[4] is not an integer "Not another number"
argv[5] is an integer = 10
As others have noted, is_all_digits
doesn't guarantee that a string which represents an integer is parse-able using atoi
or any other routine, but feel free to doctor this to your heart's content. :-)
Upvotes: 1