How to compare substrings and parse numbers from a C string - c

I'm new to low level programming and have been stuck with the following problem:
I get a string in the following format:
?cmd=ASET&<hour>&<minute>
where the hour and minute values always consist of 2 decimal numbers.
So an example of the string that can be received is:
"?cmd=ASET&08&30"
I am trying to write an if statement that recognizes that the string starts with "?cmd=ASET" and changes two global variables called minute and hour to the values in the String. I've been trying to do this with strtok(), but have not had any luck so far. So the global layout of my if statement would be:
if (String starts with "?cmd=ASET") {
minute = value found in the string;
hour = value found in the string;
}
I would really appreciate any help.

Try something like this, where cmd is a char * or char [] type variable. Note, strncmp() is safer than strcmp(). In general, in C programming, you want to use the variants of functions that limit the length, to avoid stack overflow attacks and other security risks. And since string to numeric functions can fail if given bad input it is better to use a form where you can check their status, which is why atoi() and atol() are not recommended. sscanf() allows status to be checked as does strtol() so they are both acceptable.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int
main()
{
char *string = "?cmd=ASET&12&30";
#define ASET_CMD "?cmd=ASET"
int hour = 0, minute = 0;
if (strncmp(string, ASET_CMD, strlen(ASET_CMD)) == 0) {
if (sscanf(string + strlen(ASET_CMD), "&%d&%d", &hour, &minute) != 2) {
printf("Couldn't parse input string");
exit(EXIT_FAILURE);
}
}
printf("hour: %d, minute: %d\n", hour, minute);
return(EXIT_SUCCESS);
}
$ cc -o prog prog.c
$ ./prog
hour: 12, minute: 30

If sscanf() is available for OP, consider:
unsigned hour, minute;
int n;
int cnt = sscanf(buffer, "?cmd=ASET&%2u&%2u%n", &hour, &minute, &n);
if (cnt == 2 && buffer[n] == 0) Success();
else Failure();
cnt will have the value of 2 if the prefix matches and 2 numbers where found.
The n detects if any addtional characters exist in the string.

there is a function atoi() which returns integer from string.
You can parse string like that
char string[16];
strcpy(string, "?cmd=ASET&08&30");
if (String starts with "?cmd=ASET") {
hour = atoi(string+10);
minute = atoi(string+13);
}
where +10 is position for hour and +13 for minute.
here is a working example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ()
{
int hour, minute;
char string[16];
strcpy(string, "?cmd=ASET&08&30");
/* Check if string contains ?cmd=ASET */
if (strstr(string, "?cmd=ASET") != NULL)
{
/* Parse minute and hour */
hour = atoi(string+10);
minute = atoi(string+13);
/* Print output */
printf("Minute: %02d \nHour: %02d\n", minute, hour);
}
}

Related

list convertion in C

I am trying to make put command line arguments by the user into an array but I am unsure how to approach it.
For example say I ran my program like this.
./program 1,2,3,4,5
How would I store 1 2 3 4 5 without the commas, and allow it to be passed to other functions to be used. I'm sure this has to do with using argv.
PS: NO space-separated, I want the numbers to parse into integers, I have an array of 200, and I want these numbers to be stored in the array as, arr[0] = 1, arr[1] = 2....
store 1 2 3 4 5 without the commas, and allow it to be passed to other functions to be used.
PS: NO space-separated, I want the numbers to parse into integers
Space or comma-separated doesn't matter. Arguments always come in as strings. You will have to do the work to turn them into integers using atoi (Ascii-TO-Integer).
Using spaces between arguments is the normal convention: ./program 1 2 3 4 5. They come in already separated in argv.
Loop through argv (skipping argv[0], the program name) and run them through atoi.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
for(int i = 1; i < argc; i++) {
int num = atoi(argv[i]);
printf("%d: %d\n", i, num);
}
}
Using commas is going to make that harder. You first have to split the string using the kind of weird strtok (STRing TOKenizer). Then again call atoi on the resulting values.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *token = strtok(argv[1], ",");
while(token) {
int num = atoi(token);
printf("%d\n", num);
token = strtok(NULL, ",");
}
}
This approach is also more fragile than taking them as individual arguments. If the user types ./program 1, 2, 3, 4, 5 only 1 will be read.
One of the main disadvantages to using atoi() is it provides no check on the string it is processing and will happily accept atoi ("my-cow"); and silently fail returning 0 without any indication of a problem. While a bit more involved, using strtol() allows you to determine what failed, and then recover. This can be as simple or as in-depth a recovery as your design calls for.
As mentioned in the comment, strtol() was designed to work through a string, converting sets of digits found in the string to a numeric value. On each call it will update the endptr parameter to point to the next character in the string after the last digit converted (to each ',' in your case -- or the nul-terminating character at the end). man 3 strtol provides the details.
Since strtol() updates endptr to the character after the last digit converted, you check if nptr == endptr to catch the error when no digits were converted. You check errno for a numeric conversion error such as overflow. Lastly, since the return type is long you need to check if the value returned is within the range of an int before assigning to your int array.
Putting it altogether with a very minimal bit of error handling, you could do something like:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#define NELEM 200 /* if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
int arr[NELEM] = {0}, ndx = 0; /* array and index */
char *nptr = argv[1], *endptr = nptr; /* nptr and endptr */
if (argc < 2) { /* if no argument, handle error */
fputs ("error: no argument provided.\n", stderr);
return 1;
}
else if (argc > 2) { /* warn on more than 2 arguments */
fputs ("warning: more than one argument provided.\n", stdout);
}
while (ndx < NELEM) { /* loop until all ints processed or arr full */
int error = 0; /* flag indicating error occured */
long tmp = 0; /* temp var to hold strtol return */
char *onerr = NULL; /* pointer to next comma after error */
errno = 0; /* reset errno */
tmp = strtol (nptr, &endptr, 0); /* attempt conversion to long */
if (nptr == endptr) { /* no digits converted */
fputs ("error: no digits converted.\n", stderr);
error = 1;
onerr = strchr (endptr, ',');
}
else if (errno) { /* overflow in conversion */
perror ("strtol conversion error");
error = 1;
onerr = strchr (endptr, ',');
}
else if (tmp < INT_MIN || INT_MAX < tmp) { /* check in range of int */
fputs ("error: value outside range of int.\n", stderr);
error = 1;
onerr = strchr (endptr, ',');
}
if (!error) { /* error flag not set */
arr[ndx++] = tmp; /* assign integer to arr, advance index */
}
else if (onerr) { /* found next ',' update endptr to next ',' */
endptr = onerr;
}
else { /* no next ',' after error, break */
break;
}
/* if at end of string - done, break loop */
if (!*endptr) {
break;
}
nptr = endptr + 1; /* update nptr to 1-past ',' */
}
for (int i = 0; i < ndx; i++) { /* output array content */
printf (" %d", arr[i]);
}
putchar ('\n'); /* tidy up with newline */
}
Example Use/Output
This will handle your normal case, e.g.
$ ./bin/argv1csvints 1,2,3,4,5
1 2 3 4 5
It will warn on bad arguments in list while saving all good arguments in your array:
$ ./bin/argv1csvints 1,my-cow,3,my-cat,5
error: no digits converted.
error: no digits converted.
1 3 5
As well as handling completely bad input:
$ ./bin/argv1csvints my-cow
error: no digits converted.
Or no argument at all:
$ ./bin/argv1csvints
error: no argument provided.
Or more than the expected 1 argument:
$ ./bin/argv1csvints 1,2,3,4,5 6,7,8
warning: more than one argument provided.
1 2 3 4 5
The point to be made it that with a little extra code, you can make your argument parsing routine as robust as need be. While your use of a single argument with comma-separated values is unusual, it is doable. Either manually tokenizing (splitting) the number on the commas with strtok() (or strchr() or combination of strspn() and strcspn()), looping with sscanf() using something similar to the "%d%n" format string to get a minimal succeed / fail indication with the offset of the next number from the last, or using strtol() and taking advantage of its error reporting. It's up to you.
Look things over and let me know if you have questions.
This is how I'd deal with your requirement using strtol(). This does not damage the input string, unlike solutions using strtok(). It also handles overflows and underflows correctly, unlike solutions using atoi() or its relatives. The code assumes you want to store an array of type long; if you want to use int, you can add testing to see if the value converted is larger than INT_MAX or less than INT_MIN and report an appropriate error if it is not a valid int value.
Note that handling errors from strtol() is a tricky business, not least because every return value (from LONG_MIN up to LONG_MAX) is also a valid result. See also Correct usage of strtol(). This code requires no spaces before the comma; it permits them after the comma (so you could run ./csa43 '1, 2, -3, 4, 5' and it would work). It does not allow spaces before commas. It allows leading spaces, but not trailing spaces. These issues could be fixed with more work — probably mostly in the read_value() function. It may be that the validation work in the main loop should be delegated to the read_value() function — it would give a better separation of duty. OTOH, what's here works within limits. It would be feasible to allow trailing spaces, or spaces before commas, if that's what you choose. It would be equally feasible to prohibit leading spaces and spaces after commas, if that's what you choose.
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
static int read_val(const char *str, char **eov, long *value)
{
errno = 0;
char *eon;
if (*str == '\0')
return -1;
long val = strtol(str, &eon, 0);
if (eon == str || (*eon != '\0' && *eon != ',') ||
((val == LONG_MIN || val == LONG_MAX) && errno == ERANGE))
{
fprintf(stderr, "Could not convert '%s' to an integer "
"(the leftover string is '%s')\n", str, eon);
return -1;
}
*value = val;
*eov = eon;
return 0;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s n1,n2,n3,...\n", argv[0]);
exit(EXIT_FAILURE);
}
enum { NUM_ARRAY = 200 };
long array[NUM_ARRAY];
size_t nvals = 0;
char *str = argv[1];
char *eon;
long val;
while (read_val(str, &eon, &val) == 0 && nvals < NUM_ARRAY)
{
array[nvals++] = val;
str = eon;
if (str[0] == ',' && str[1] == '\0')
{
fprintf(stderr, "%s: trailing comma in number string\n", argv[1]);
exit(EXIT_FAILURE);
}
else if (str[0] == ',')
str++;
}
for (size_t i = 0; i < nvals; i++)
printf("[%zu] = %ld\n", i, array[i]);
return 0;
}
Output (program csa43 compiled from csa43.c):
$ csa43 1,2,3,4,5
[0] = 1
[1] = 2
[2] = 3
[3] = 4
[4] = 5
$

Split user inputted string to char array

I'm making an appointment calendar program with c. My program adds an new appointment with user inputted command: "A 'description' 'mm' 'dd' 'hh'"
Where description is a string with a maximum of 20 characters, mm is months, dd is days and hh is hours. Months, days and hours can be 1 or 2 characters long.
I have tried to implement readInput function which splits the input string by spacebar and returns a char array which contains: [description, mm, dd, hh] so I can easily get:
desc = array[1];
month = array[2];
day = array[3];
hour = array[4];
Implementing this function was harder than I thought and I have failed miserably. I don't want pointers, just a basic char array containing strings. How should I implement this? Below is my main function.
int main()
{
struct Calendar c;
c.numOfAppointments = 0;
while (true) {
char str[30];
printf("Command: ");
scanf("%[^\n]%*c", str);
if (str[0] == 'A')
{
char command = readInput(str); /*implement this */
}
else if (str[0] == 'L')
{
printCalendar(c);
}
else if (str[0] == 'Q')
{
printf("Exiting program...");
break;
}
}
}
Say I input: A dentist 4 20 10
the returned array should be: ["dentist","4","20","10"]
Implementing this function was harder than I thought and I have failed miserably.
Don't be so hard on yourself... you're learning. We learn to program mainly through our mistakes :-)
I don't want pointers, just a basic char array containing strings.
Ah, so, there's your problem: Will it be an array of characters, or an array of strings?
A string in C is a pointer to a sequence of characters in memory ending with a \0.
An "array of strings" is an array of pointers!
What you could do is:
Read your line into a proper array of characters.
Place '\0' at appropriate places in this array, to indicate the end of line components.
Have a second array, e.g. const char * array_of_fields[4], whose elements are strings.
or
Have a struct { const char* description, month, day, hour; }
and then
Set the elements of the second array, or the elements of the struct, to point to the appropriate positions within the character array.
Use fgets(str, strlen(str), stdin) to read in your string. The next step is is to parse the string. As your input consist of a variable description followed by a somewhat variable date time format. The way to parse is to start at the end to find the separator sep between description and month which would be the 3rd last space (n = 2). You now know whatever is before sep is "A " prefix and description, and everything after is the date time. You can use strptime(3) to parse the date time:
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int rindexn(const char *s, char c, size_t n) {
int i = strlen(s);
if(!i) return -1;
for(i--; i >= 0; i--) {
if(s[i] == c) {
if(!n) break;
n--;
}
}
return i >= 0 ? i : -1;
}
int main() {
const char *str = "A dentist 4 20 10";
// parse
if(!strncmp(str, "A ", 2)) {
printf("A space prefix missing");
return 1;
}
int sep = rindexn(str, ' ', 2);
if(sep == -1) {
printf("sep not found\n");
return 1;
}
struct tm tm;
char *end = strptime(str + sep + 1, "%m %d %H", &tm);
if(!end) {
pritnf("cannot parse date time\n");
return 1;
} else if(*end) {
printf("extra data after hour\n");
return 1;
}
// convert to whatever format you decide
printf("description = \"%.*s\", month = %d, day = %d, hour = %d",
(int) sep - 2, str + 2, tm.tm_mon, tm.tm_mday, tm.tm_hour);
}
which gives the following output:
description = "dentist", month = 3, day = 20, hour = 10

How to extract a number from a string in C

I am working on a project for school but I can't figure out how I can extract the year from a date in a string "20-02-2015" the date is always of the form XX-XX-XXXX
Is there some way to use some kind of scan function?
char date[]="20-02-2015";
int d,m,y;
sscanf(date,"%d-%d-%d",&d,&m,&y);
Assuming that your string is given as char* str or as char str[], you can try this:
int day,mon,year;
sscanf(str,"%d-%d-%d",&day,&mon,&year);
Or you can try this, for a slightly better performance (by avoiding the call to sscanf):
int year = 1000*(str[6]-'0')+100*(str[7]-'0')+10*(str[8]-'0')+(str[9]-'0');
You can use the strtok() function to split a string (and specify the delimiter to use)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *date = malloc(10);
char *day = NULL;
char *month = NULL;
char *year = NULL;
strcpy(date, "01-03-2014");
day = strtok(date, "-");
printf("%s\n",day);
month = strtok(NULL, "-");
printf("%s\n",month);
year = strtok(NULL, "-");
printf("%s\n",year);
free(date);
return 0;
}
the output :
01
03
2014
Yes, with the %d-%d-%d format.
If reading from STDIN, use scanf, from a file use fscanf and from a string use sscanf
you can divide the string using strtok(date,"-") then can use atoi() to get the date, month and year as numbers. check this link it can help you
As other have suggested "%d-%d-%d".
To add error checking, should code need to insure no trailing garbage and all was there:
char date[80];
fgets(data, sizeof date, stdin);
int d,m,y;
in n;
int cnt = sscanf(date, "%d-%d-%d%n", &d, &m, &y, &n);
if (cnt == 3 && date[n] == '\n') Good();
else Bad();

C - Increment a number within a char

Is it possible to increment a number alone within a string?
So let's say I have:
char someString = "A0001";
Is there a way to increment the number '0001'? To make it A0002, A0003 etc?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
char *strinc(const char *str, int d, int min_width){
char wk[12];//12:max length of sizeof(int)=4
char *p;
int len, d_len, c;
c = len = strlen(str);
while(isdigit(str[--c]));
++c;
d += strtol(&str[c], NULL, 10);
if(d<0) d = 0;
d_len = sprintf(wk, "%0*d", min_width, d);
p = malloc((c+d_len+1)*sizeof(char));
strncpy(p, str, c);
p[c]='\0';
return strcat(p, wk);
}
int main(void){
char *someString = "A0001";
char *label_x2, *label_x3;
label_x2 = strinc(someString, +1, 4);
printf("%s\n", label_x2);//A0002
label_x3 = strinc(label_x2, +1, 4);
printf("%s\n", label_x3);//A0003
free(label_x2);
label_x2 = strinc("A0008", +5, 4);
printf("%s\n", label_x2);//A0013
free(label_x3);
label_x3 = strinc(label_x2, -8, 4);
printf("%s\n", label_x3);//A0005
free(label_x2);
free(label_x3);
return 0;
}
no u cannot do it because it is a constant
The simple answer is that there is no "easy" way to do what you're asking. You would have to parse the string, extract the numerical portion and parse into a number. Increment the number and then print that number back into your string.
You could try the following simple example to base something on...
EDIT: Just read BLUEPIXY's answer... he presents a nice function that will do it for you, return you a new string, which doesn't have the width restriction of my simple answer...
There are some points worth noting...
Use char someString[] = "A0001"; and not char *someString = "A0001";. The reason is that the former allocates memory on the stack for the string, the latter is a pointer to a string in memory. The memory location decided upon by the compiler in the latter case and is not always guaranteed to be writable.
Crappy #define for snprintf on Windows... not sure that's a good thing. The point is really use a safe buffer writing function that won't overflow the bounds of your array.
The snprintf format string "%0*u" Formats an unsigned integer with a minimum width specified by the argument to the left of the actual integer and the zero tells it to left pad with zeros if necessary.
If your number increases to a width greater than, in this case, 4 digits, the buffer won't overflow, but your answers will look wrong (I haven't put in any logic to increment the buffer size)
I am assuming the the format of the string is always a set of non-numerical-digits, followed by a set of numerical digits and then a null terminator.
Now the code...
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#ifdef WIN32
#define snprintf sprintf_s
#endif
int main(int argc, char* argv[])
{
/* Assume that the string format is letters followed by numbers */
/* Note use someString[] and NOT someString* */
char someString[] = "A0001";
char *start = someString;
char *end = start + strlen(someString); /* End points to the NULL terminator */
char *endOfParse;
char c;
unsigned long num;
ptrdiff_t numDigits;
/* Find first numeric value (start will point to first numeric
* value or NULL if none found */
while( true )
{
c = *start;
if( c == '\0' || isdigit(c) )
break;
++start;
}
if( c == '\0' )
{
printf("Error: didn't find any numerical characters\n");
exit(EXIT_FAILURE);
}
/* Parse the number pointed to by "start" */
num = strtoul(start, &endOfParse, 0);
if(endOfParse < end )
{
printf("Error: Failed to parse the numerical portion of the string\n");
exit(EXIT_FAILURE);
}
/* Figure out how many digits we parsed, so that we can be sure
* not to overflow the buffer when writing in the new number */
numDigits = end - start;
num = num + 1;
snprintf(start, numDigits+1, "%0*u", numDigits, num); /* numDigits+1 for buffer size to include the null terminator */
printf("Result is %s\n", someString);
return EXIT_SUCCESS;
}
You can't do it simply because its not as simple to machine as it looks to you. There are a lot of things you need to understand about what you are trying to do first. For example, What part of string are you taking as a number which is to be incremented?
Last digit only?
A number which will be followed by SINGLE alphabet?
A number which may be followed by any number of alphabets?
LAST number in a string, for example A33B43 would mean to increment 33 or 43?
When you have answers to all such questions, you can implement them in a function. One of the many possible approaches thereafter can be to make a new substring which will represent the number to be incremented(this substring is to be taken out from your someString). Then use atoi() to convert that string into number, increment the number and replace this incremented number as a string in someString.(someString needs to be String or char * btw).

Find int in a string (char*) in pure c

There is a string with a line of text. Let's say:
char * line = "Foo|bar|Baz|23|25|27";
I would have to find the numbers.
I was thinking of something like this:
If the given char is a number, let's put it into a temporary char array. (buffer)
If the next character is NOT a number, let's make the buffer a new int.
The problem is... how do I find numbers in a string like this?
(I'm not familiar with C99/gcc that much.)
Compiler used: gcc 4.3 (Environment is a Debian Linux stable.)
I would approach as the following:
Considering '|' as the separator, tokenize the line of text, i.e. split the line into multiple fields.
For each token:
If the token is numeric:
Convert the token to a number
Some library functions that might be useful are strtok, isdigit, atoi.
One possible implementation for the approach suggested in this answer, based on sscanf.
#include <stdio.h>
#include <string.h>
void find_integers(const char* p) {
size_t s = strlen(p)+1;
char buf[s];
const char * p_end = p+s;
int n;
/* tokenize string */
for (; p < p_end && sscanf(p, "%[^|]%n", &buf, &n); p += (n+1))
{
int x;
/* try to parse an integer */
if (sscanf(buf, "%d", &x)) {
printf("got int :) %d\n", x);
}
else {
printf("got str :( %s\n", buf);
}
}
}
int main() {
const char * line = "Foo|bar|Baz|23|25|27";
find_integers(line);
}
Output:
$ gcc test.c && ./a.out
got str :( Foo
got str :( bar
got str :( Baz
got int :) 23
got int :) 25
got int :) 27

Resources