I have always been of the mindset that writing a general input routine to handle numeric input regardless of the format makes more sense that writing 5 different routines to handle 5 different formats, etc. strtol
is tailor made to handle conversion of numeric input contained in a string regardless of the format. The declaration is:
long int strtol(const char *nptr, char **endptr, int base);
The fact that it takes a pointer as input, and updates an endptr
to the next character following the number converted in nptr
allows you to start at the beginning of a line and work to its end, converting each number found -- regardless of the format the input is in. This allows creation of a flexible input parsing function with robust error checking.
A short example below will illustrate this approach. The example will read from stdin
by default or from any filename provided as the first argument. The strtol
call is wrapped in a short xstrtol
function that simply provides the normal error checking for each conversion. Placing it in a function prevents cluttering the main logic of your code with the repetitive error checking. Example:
#include <stdio.h>
#include <stdlib.h> /* for strtol */
#include <limits.h> /* for INT_MIN/INT_MAX */
#include <errno.h> /* for errno */
#define MAXL 256
long xstrtol (char *p, char **ep, int base);
int main (int argc, char **argv)
{
int values[MAXL] = {0};
char line[MAXL] = {0};
size_t i, idx = 0;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open */
fprintf (stderr, "error: file open failen '%s'.\n", argv[1]);
return 1;
}
/* read each line in file (up to MAXL chars per-line) */
while (fgets (line, MAXL, fp)) {
char *p = line;
char *ep = p;
errno = 0;
/* convert each string of digits into number */
while (errno == 0) {
/* skip any non-digit characters */
while (*p && ((*p != '-' && (*p < '0' || *p > '9')) ||
(*p == '-' && (*(p+1) < '0' || *(p+1) > '9')))) p++;
if (!*p) break;
/* convert string to number */
values[idx++] = (int)xstrtol (p, &ep, 10);
if (errno) break; /* check for error */
if (idx == MAXL) { /* check if array full */
fprintf (stderr, "warning: MAXL values read.\n");
break;
}
/* skip delimiters/move pointer to next digit */
while (*ep && *ep != '-' && (*ep < '0' || *ep > '9')) ep++;
if (*ep)
p = ep;
else /* break if end of string */
break;
}
}
if (fp != stdin) fclose (fp);
printf ("\nvalues read from '%s'\n\n", argc > 1 ? argv[1] : "stdin");
for (i = 0; i < idx; i++)
printf (" values[%3zu] : %d\n", i, values[i]);
return 0;
}
/** a simple strtol implementation with error checking.
* any failed conversion will cause program exit. Adjust
* response to failed conversion as required.
*/
long xstrtol (char *p, char **ep, int base)
{
errno = 0;
long tmp = strtol (p, ep, base);
/* Check for various possible errors */
if ((errno == ERANGE && (tmp == LONG_MIN || tmp == LONG_MAX)) ||
(errno != 0 && tmp == 0)) {
perror ("strtol");
exit (EXIT_FAILURE);
}
if (*ep == p) {
fprintf (stderr, "No digits were found\n");
exit (EXIT_FAILURE);
}
return tmp;
}
Input Files - 5 Different Formats
$ cat dat/10int_braces.txt
{ 8572, -2213, 6434, 16330, 3034, 12346, 4855, 16985, 11250, 1495 }
$ cat dat/10int.csv
8572, -2213, 6434, 16330, 3034
12346, 4855, 16985, 11250, 1495
$ cat dat/10int_space.txt
8572 -2213 6434 16330 3034 12346 4855 16985 11250 1495
$ cat dat/10int_nl.txt
8572
-2213
6434
16330
3034
12346
4855
16985
11250
1495
$ cat dat/10int_5x2.txt
[[ 8572 -2213 ]
[ 6434 16330 ]
[ 3034 12346 ]
[ 4855 16985 ]
[ 11250 1495 ]]
$ cat dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a
- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495
Use/Output
All formats provide the same output, e.g.:
$ ./bin/fgets_xstrtol_simple <dat/10int_braces.txt
values read from 'stdin'
values[ 0] : 8572
values[ 1] : -2213
values[ 2] : 6434
values[ 3] : 16330
values[ 4] : 3034
values[ 5] : 12346
values[ 6] : 4855
values[ 7] : 16985
values[ 8] : 11250
values[ 9] : 1495
You can easily add dynamic allocation/reallocation as needed. Let me know if you have any questions.