C: Strings are copied from buffer incorrectly - c

I have been working with text files but somehow I have been getting a strange error. When I try to get the 1st, 2nd, 4th, and 5th parts of a string (the ':' is the delimiter), I get a weird response. This is what I am trying to read:
1000:Product:0.75:5:0
And I get this sort of answer:
8 |X |0
75(
Here is the code:
int main(){
char c,buff[100],prod[30],id[8],stock[8],vendas[8];
int i=0,n=0,num=0;
FILE *fp;
fp=fopen("products.txt","r+");
printf("Lista de produtos(Num |Produto |Stock |Vendas)\n");
while(fgets(buff,100,fp)){
for(n=0;n<strlen(buff);n++){
if(buff[n]==':'){
num++;
i=0;
}
else if((buff[n]!=':')&&(num==0)){
id[i]=buff[n];
i++;
}
else if((buff[n]!=':')&&(num==1)){
prod[i]=buff[n];
i++;
}
else if((buff[n]!=':')&&(num==3)){
stock[i]=buff[n];
i++;
}
else if((buff[n]!=':')&&(num=4)){
vendas[i]=buff[n];
i++;
}
}
i=0;
num=0;
printf("%s |%s |%s |%s\n",id,prod,stock,vendas);
memset(id,0,8);
memset(prod,0,30);
memset(stock,0,8);
memset(vendas,0,8);
}
printf("Prima qualquer tecla para sair");
getchar();
return 0;
}
Any help would be appreciated. Sorry if this question or my code isn't the best.
Have a great day!

In case the format string does not start with :, you never initialize i.
In case it does start with : then num==0 will never get executed.
You never null terminate the strings.
if(buff[n]==':') ... else if((buff[n]!=':' is redundant.
num=4 is a bug, enable compiler warnings or get a better compiler.
You never check if the file was opened correctly nor if you reached the end of the file.

The fitst time through invokes undefined behaviour because you have not initialised the content of each field buffer.
You also do not check for buffer overruns when filling the fields in. If a field is as long or longer than the array you are putting it in, there will be a buffer overrun and no terminating \0
Also in each else if you do not need the (buff[n]!=':') condition.
Also, the last else if has an assignment in the conditional n = 4 (although this actually does not affect the result).

At least two errors:
Initialize your arrays first before trying to use them...
while(fgets(buff,100,fp)){
memset(id,0,8);
memset(prod,0,30);
memset(stock,0,8);
memset(vendas,0,8);
for(n=0;n<strlen(buff);n++){
Assignment in test, replace:
if((buff[n]!=':')&&(num=4)){
with
if((buff[n]!=':')&&(num==4)){
^

You may be making the logic much more difficult that it needs to be. When you must break a line of text up into words based on a delimiter, you should be thinking strtok or strsep. strtok is the normal routine used to tokenize a line of text on delimiters, breaking the line up into individual words. (strsep is primarily used when there is a chance of encountering an empty-field, as in a large .csv file).
Your tokens are simple the ':' char and the '\n' at the end. You could simply declare char *delim = ":\n"; and be covered.
While your are free to daisy-chain if, then, else if, .... together, a switch on a counter may make life easier and the code more readable.
Putting those pieces together, you could do something like the following:
#include <stdio.h>
#include <string.h>
enum { ISV = 8, PROD = 30, BUFF = 100 }; /* constants */
int main (int argc, char **argv) {
char buff[BUFF] = "", prod[PROD] = "", id[ISV] = "",
stock[ISV] = "", vendas[ISV] = "", *delim = ":\n";
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed.\n");
return 1;
}
while (fgets (buff, sizeof buff, fp)) { /* for each line of input */
int i = 0, n = 0; /* tokenize buff into words */
for (char *p = strtok (buff, delim); p; p = strtok (NULL, delim)) {
switch (i) {
case 0: strncpy (id, p, ISV); /* handle id */
if (strlen (p) >= ISV)
id[ISV-1] = 0;
i++;
break;
case 1: strncpy (prod, p, PROD); /* handle prod */
if (strlen (p) >= PROD)
prod[PROD-1] = 0;
i++;
break;
case 2: strncpy (stock, p, ISV); /* handle stock */
if (strlen (p) >= ISV)
stock[ISV-1] = 0;
i++;
break;
case 3: strncpy (vendas, p, ISV); /* handle vendas */
if (strlen (p) >= ISV)
vendas[ISV-1] = 0;
i++;
break;
default: break;
}
}
n++;
printf ("id: %s, prod: %s, stock: %s, vendas: %s\n",
id, prod, stock, vendas);
if (fp != stdin) fclose (fp); /* close file */
}
return 0;
}
Example Input File
$ cat dat/prod.txt
1000:Product1:0.75:5:0
1001:Product2:0.90:2:0
1002:Product3:0.55:8:0
Example Use/Output
$ ./bin/prod dat/prod.txt
id: 1000, prod: Product1, stock: 0.75, vendas: 5
id: 1001, prod: Product2, stock: 0.90, vendas: 2
id: 1002, prod: Product3, stock: 0.55, vendas: 8
Look things over and let me know if you have further questions.

Related

Strtok strange behaviour

I'm having some troubles using strtok function.
As an exercise I have to deal with a text file by ruling out white spaces, transforming initials into capital letters and printing no more than 20 characters in a line.
Here is a fragment of my code:
fgets(sentence, SIZE, f1_ptr);
char *tok_ptr = strtok(sentence, " \n"); //tokenazing each line read
tok_ptr[0] = toupper(tok_ptr[0]); //initials to capital letters
int num = 0, i;
while (!feof(f1_ptr)) {
while (tok_ptr != NULL) {
for (i = num; i < strlen(tok_ptr) + num; i++) {
if (i % 20 == 0 && i != 0) //maximum of 20 char per line
fputc('\n', stdout);
fputc(tok_ptr[i - num], stdout);
}
num = i;
tok_ptr = strtok(NULL, " \n");
if (tok_ptr != NULL)
tok_ptr[0] = toupper(tok_ptr[0]);
}
fgets(sentence, SIZE + 1, f1_ptr);
tok_ptr = strtok(sentence, " \n");
if (tok_ptr != NULL)
tok_ptr[0] = toupper(tok_ptr[0]);
}
The text is just a bunch of lines I just show as a reference:
Watch your thoughts ; they become words .
Watch your words ; they become actions .
Watch your actions ; they become habits .
Watch your habits ; they become character .
Watch your character ; it becomes your destiny .
Here is what I obtain in the end:
WatchYourThoughts;Th
eyBecomeWords.WatchY
ourWords;THeyBecomeA
ctions.WatchYourActi
ons;TheyBecomeHabits
.WatchYourHabits;The
yBecomeCharacteR.Wat
chYourCharacter;ItBe
comesYourDEstiny.Lao
-Tze
The final result is mostly correct, but sometimes (for example "they" in they become (and only in that case) or "destiny") words are not correctly tokenized. So for example "they" is split into "t" and "hey" resulting in THey (DEstiny in the other instance) after the manipulations I made.
Is it some bug or am I missing something? Probably my code is not that efficient and some condition may end up being critical...
Thank you for the help, it's not that big of a deal, I just don't understand why such a behaviour is occurring.
You have a large number of errors in your code and you are over-complicating the problem. The most pressing error is Why is while ( !feof (file) ) always wrong? Why? Trace the execution-path within your loop. You attempt to read with fgets(), and then you use sentence without knowing whether EOF was reached calling tok_ptr = strtok(sentence, " \n"); before you ever get around to checking feof(f1_ptr)
What happens when you actually reach EOF? That IS "Why while ( !feof (file) ) is always wrong?" Instead, you always want to control your read-loop with the return of the read function you are using, e.g. while (fgets(sentence, SIZE, f1_ptr) != NULL)
What is it you actually need your code to do?
The larger question is why are you over-complicating the problem with strtok, and arrays (and fgets() for that matter)? Think about what you need to do:
read each character in the file,
if it is whitespace, ignore it, set the in-word flag false,
if a non-whitespace, if 1st char in word, capitalize it, output the char, set the in-word flag true and increment the number of chars output to the current line, and finally
if it is the 20th character output, output a newline and reset the counter zero.
The bare-minimum tools you need from your C-toolbox are fgetc(), isspace() and toupper() from ctype.h, a counter for the number of characters output, and a flag to know if the character is the first non-whitespace character after a whitespace.
Implementing the logic
That makes the problem very simple. Read a character, is it whitespace?, set your in-word flag false, otherwise if your in-word flag is false, capitalize it, output the character, set your in-word flag true, increment your word count. Last thing you need to do is check if your character-count has reached the limit, if so output a '\n' and reset your character-count zero. Repeat until you run out of characters.
You can turn that into a code with something similar to the following:
#include <stdio.h>
#include <ctype.h>
#define CPL 20 /* chars per-line, if you need a constant, #define one (or more) */
int main (int argc, char **argv) {
int c, in = 0, n = 0; /* char, in-word flag, no. of chars output in line */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while ((c = fgetc(fp)) != EOF) { /* read / validate each char in file */
if (isspace(c)) /* char is whitespace? */
in = 0; /* set in-word flag false */
else { /* otherwise, not whitespace */
putchar (in ? c : toupper(c)); /* output char, capitalize 1st in word */
in = 1; /* set in-word flag true */
n++; /* increment character count */
}
if (n == CPL) { /* CPL limit reached? */
putchar ('\n'); /* output newline */
n = 0; /* reset cpl counter */
}
}
putchar ('\n'); /* tidy up with newline */
if (fp != stdin) /* close file if not stdin */
fclose (fp);
}
Example Use/Output
Given your input file stored on my computer in dat/text220.txt, you can produce the output you are looking for with:
$ ./bin/text220 dat/text220.txt
WatchYourThoughts;Th
eyBecomeWords.WatchY
ourWords;TheyBecomeA
ctions.WatchYourActi
ons;TheyBecomeHabits
.WatchYourHabits;The
yBecomeCharacter.Wat
chYourCharacter;ItBe
comesYourDestiny.
(the executable for the code was compiled to bin/text220, I usually keep separate dat, obj, and bin directories for data, object files and executables to keep by source code directory clean)
note: by reading from stdin by default if no filename is provided as the first argument to the program, you can use your program to read input directly, e.g.
$ echo "my dog has fleas - bummer!" | ./bin/text220
MyDogHasFleas-Bummer
!
No fancy string functions required, just a loop, a character, a flag and a counter -- the rest is just arithmetic. It's always worth trying to boils your programming problems down to basic steps and then look around your C-toolbox and find the right tool for each basic step.
Using strtok
Don't get me wrong, there is nothing wrong with using strtok and it makes a fairly simple solution in this case -- the point I was making is that for simple character-oriented string-processing, it's often just a simple to loop over the characters in the line. You don't gain any efficiencies using fgets() with an array and strtok(), the read from the file is already placed into a buffer of BUFSIZ1.
If you did want to use strtok(), you should control you read-loop your with the return from fgets()and then you can tokenize with strtok() also checking its return at each point. A read-loop with fgets() and a tokenization loop with strtok(). Then you handle first-character capitalization and then limiting your output to 20-chars per-line.
You could do something like the following:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define CPL 20 /* chars per-line, if you need a constant, #define one (or more) */
#define MAXC 1024
#define DELIM " \t\r\n"
void putcharCPL (int c, int *n)
{
if (*n == CPL) { /* if n == limit */
putchar ('\n'); /* output '\n' */
*n = 0; /* reset value at mem address 0 */
}
putchar (c); /* output character */
(*n)++; /* increment value at mem address */
}
int main (int argc, char **argv) {
char line[MAXC]; /* buffer to hold each line */
int n = 0; /* no. of chars ouput in line */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (line, MAXC, fp)) /* read each line and tokenize line */
for (char *tok = strtok (line, DELIM); tok; tok = strtok (NULL, DELIM)) {
putcharCPL (toupper(*tok), &n); /* convert 1st char to upper */
for (int i = 1; tok[i]; i++) /* output rest unchanged */
putcharCPL (tok[i], &n);
}
putchar ('\n'); /* tidy up with newline */
if (fp != stdin) /* close file if not stdin */
fclose (fp);
}
(same output)
The putcharCPL() function is just a helper that checks if 20 characters have been output and if so outputs a '\n' and resets the counter. It then outputs the current character and increments the counter by one. A pointer to the counter is passed so it can be updated within the function making the updated value available back in main().
Look things over and let me know if you have further questions.
footnotes:
1. Depending on your version of gcc, the constant in the source setting the read-buffer size may be _IO_BUFSIZ. _IO_BUFSIZ was changed to BUFSIZ here: glibc commit 9964a14579e5eef9 For Linux BUFSIZE is defined as 8192 (512 on Windows).
This is actually a much more interesting OP from a professional point of view than some of the comments may suggest, despite the 'newcomer' aspect of the question, which may sometimes raise fairly deep, underestimated issues.
The fun thing is that on my platform (W10, MSYS2, gcc v.10.2), your code runs fine with correct results:
WatchYourThoughts;Th
eyBecomeWords.WatchY
ourWords;TheyBecomeA
ctions.WatchYourActi
ons;TheyBecomeHabits
.WatchYourHabits;The
yBecomeCharacter.Wat
chYourCharacter;ItBe
comesYourDestiny.
So first, congratulations, newcomer: your coding is not that bad.
This points to how different compilers may or may not protect against limited inappropriate coding or specification misuse, may or may not protect stacks or heaps.
This said, the comment by #Andrew Henle pointing to an illuminating answer about feof is quite relevant.
If you follow it and retrieve your feof test, just moving it down after read checks, not before (as below). Your code should yield better results (note: I will just alter your code minimally, deliberately ignoring lesser issues):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#define SIZE 100 // add some leeway to avoid off-by-one issues
int main()
{
FILE* f1_ptr = fopen("C:\\Users\\Public\\Dev\\test_strtok", "r");
if (! f1_ptr)
{
perror("Open issue");
exit(EXIT_FAILURE);
}
char sentence[SIZE] = {0};
if (NULL == fgets(sentence, SIZE, f1_ptr))
{
perror("fgets issue"); // implementation-dependent
exit(EXIT_FAILURE);
}
errno = 0;
char *tok_ptr = strtok(sentence, " \n"); //tokenizing each line read
if (tok_ptr == NULL || errno)
{
perror("first strtok parse issue");
exit(EXIT_FAILURE);
}
tok_ptr[0] = toupper(tok_ptr[0]); //initials to capital letters
int num = 0;
size_t i = 0;
while (1) {
while (1) {
for (i = num; i < strlen(tok_ptr) + num; i++) {
if (i % 20 == 0 && i != 0) //maximum of 20 char per line
fputc('\n', stdout);
fputc(tok_ptr[i - num], stdout);
}
num = i;
tok_ptr = strtok(NULL, " \n");
if (tok_ptr == NULL) break;
tok_ptr[0] = toupper(tok_ptr[0]);
}
if (NULL == fgets(sentence, SIZE, f1_ptr)) // let's get away whith annoying +1,
// we have enough headroom
{
if (feof(f1_ptr))
{
fprintf(stderr, "\n%s\n", "Found EOF");
break;
}
else
{
perror("Unexpected fgets issue in loop"); // implementation-dependent
exit(EXIT_FAILURE);
}
}
errno = 0;
tok_ptr = strtok(sentence, " \n");
if (tok_ptr == NULL)
{
if (errno)
{
perror("strtok issue in loop");
exit(EXIT_FAILURE);
}
break;
}
tok_ptr[0] = toupper(tok_ptr[0]);
}
return 0;
}
$ ./test
WatchYourThoughts;Th
eyBecomeWords.WatchY
ourWords;TheyBecomeA
ctions.WatchYourActi
ons;TheyBecomeHabits
.WatchYourHabits;The
yBecomeCharacter.Wat
chYourCharacter;ItBe
comesYourDestiny.
Found EOF

How to read CSV file into 2d array of struct with each element having a determining character (C, G, M) to sort into the struct?

I am trying to create a 2d map of array using the csv input
5,4
,,,C 200
,G Vibranium Shield:hands:990,,C 50
M Healing Potion:85,,M Defence Enchanment:360,
,,,
,,G Lighsaber:hands:850,5,4
The first row is the size of the array given.
The problem I am having right now is how to still count the empty list in the csv as a row and column in the array such as ",,,". Plus, how to read the determining character (C, G, M) in order to store the element in the struct. Example, G Vibranium Shield:hands:990, G will be the determining character stored in char type which then i use the switch case to store other element into the appropriate struct.
I tried to use fgets() strtok() but I can't read separately the determining element from other element in the CSV. As from other example it seem it need prior knowledge into which element will be in the line and predetermine the read line and not based on the determining character in the CSV. Thus I used fscanf to read:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct map
{
char types;
char geartypes[100];
int coins;
int values;
char items[100];
}map;
struct map **m;
int main()
{
FILE* mapz;
int i,j,h;
int width,height;
char a;
mapz=fopen("map.csv","r");
if(mapz!=NULL)
{
fscanf(mapz,"%d,%d",&height,&width);
map **m=(map **)malloc(height * sizeof(map *));
for(i=0;i<height;i++)
{
m[i]=(map*)malloc(width * sizeof(map));
}
for(h=0;h<height;h++)
{
for(j=0;j<width;j++)
{
fscanf(mapz,"%c",&a);
switch(a)
{
case('C'):
m[h][j].types=a;
fscanf(mapz,"%d",&m[h][j].coins);
break;
case('G'):
m[h][j].types=a;
fscanf(mapz,"%[^,:]s",m[h][j].items);
fscanf(mapz,"%[^,:]s",m[h][j].geartypes);
fscanf(mapz,"%d",&m[h][j].values);
break;
case('M'):
m[h][j].types=a;
fscanf(mapz,"%[^,:]s",m[h][j].items);
fscanf(mapz,"%d",&m[h][j].values);
break;
}
}
}
for(h=0;h<height;h++)
{
for(j=0;j<width;j++)
{
switch(m[h][j].types)
{
case('C'):
printf("%c",m[h][j].types);
printf("%d\n",m[h][j].coins);
break;
case('G'):
printf("%c",m[h][j].types);
printf("%s%s%d\n",m[h][j].items,m[h][j].geartypes,m[h][j].values);
break;
case('M'):
printf("%c",m[h][j].types);
printf("%s%d\n",m[h][j].items,m[h][j].values);
break;
}
}
}
}
else
{
printf("No such file in directory");
}
fclose(mapz);
return 0;
I tried to use fscanf but it seem to also read the "," which messed up the for count. When i ran the code it come out blank.
Since you are stuck on handling empty fields when you are tokenizing each line, let's look at using strsep to handle that for you. There are a few caveats about using strsep. First note the type of the first parameter. It is char **. That means you cannot read each line into a fixed character array and pass the address of a fixed array (it would not be char**, but instead char (*)[length]). Next, since strsep will update the pointer provided as the first parameter, you cannot simply give it the address of the allocated buffer you are using to store each line you read (you would lose the pointer to the start of the allocated block and be unable to free() the memory or read more than one line.
So, bottom line, you need an allocated buffer to hold the text you are going to pass to strsep, and then your need 2 pointers, one to capture the return from strsep and one to pass the address of to strsep (to allow you to preserve your original buffer pointer).
With that in mind, you can parse your CSV with empty fields similar to:
while (fgets (buf, MAXC, fp)) { /* read each line in file */
size_t i = 0; /* counter */
p = fields = buf; /* initialize pointers to use with strsep */
printf ("\nline %2zu:\n", n++ + 1); /* output heading */
while ((p = strsep (&fields, DELIM))) { /* call strsep */
p[strcspn(p, "\r\n")] = 0; /* trim '\n' (last) */
printf (" field %2zu: '%s'\n", i++ + 1, p); /* output field */
}
}
Putting that together in a full example using your data, you can do something similar to:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define DELIM "," /* (numeric or string) */
int main (int argc, char **argv) {
size_t n = 0, lines, nflds;
char *buf, *fields, *p; /* must use 2 pointers for strsep */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if (!(buf = malloc (MAXC))) { /* allocate storage for buffer */
perror ("malloc-buf"); /* cannot be array with strsep */
return 1;
}
if (!fgets (buf, MAXC, fp)) { /* read/validate 1st line */
fputs ("error: insufficient input line 1.\n", stderr);
return 1;
} /* convert to lines and no. of fields (lines not needed) */
if (sscanf (buf, "%zu,%zu", &lines, &nflds) != 2) {
fputs ("error: invalid format line 1.\n", stderr);
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line in file */
size_t i = 0; /* counter */
p = fields = buf; /* initialize pointers to use with strsep */
printf ("\nline %2zu:\n", n++ + 1); /* output heading */
while ((p = strsep (&fields, DELIM))) { /* call strsep */
p[strcspn(p, "\r\n")] = 0; /* trim '\n' (last) */
printf (" field %2zu: '%s'\n", i++ + 1, p); /* output field */
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
free (buf); /* free allocated memory */
return 0;
}
Example Input File
$ cat dat/emptyflds.csv
5,4
,,,C 200
,G Vibranium Shield:hands:990,,C 50
M Healing Potion:85,,M Defence Enchanment:360,
,,,
,,G Lighsaber:hands:850,5,4
Example Use/Output
The example simply prints the line number and then each separated field on a separate line below it so you can confirm the separation:
$ ./bin/strcspnsepcsv <dat/emptyflds.csv
line 1:
field 1: ''
field 2: ''
field 3: ''
field 4: 'C 200'
line 2:
field 1: ''
field 2: 'G Vibranium Shield:hands:990'
field 3: ''
field 4: 'C 50'
line 3:
field 1: 'M Healing Potion:85'
field 2: ''
field 3: 'M Defence Enchanment:360'
field 4: ''
line 4:
field 1: ''
field 2: ''
field 3: ''
field 4: ''
line 5:
field 1: ''
field 2: ''
field 3: 'G Lighsaber:hands:850'
field 4: '5'
field 5: '4'
(note: line 5 contains a 5th field that exceeds expected no. of fields)
To handle further separation within the fields on ':' or whatever else you need, you are free to call strtok on the pointer p within the field tokenization while loop.
While I have no qualms with David C. Rankin's answer, here's a different approach that uses regular expressions:
#include <assert.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <regex.h>
char line[4096];
int main( int argc, char *argv[] ) {
if( !argv[1] )
errx(EXIT_FAILURE, "missing input");
FILE *input = fopen(argv[1], "r");
if( !input )
err(EXIT_FAILURE, "could not open %s", argv[1]);
if( NULL == fgets(line, sizeof(line), input) )
err(EXIT_FAILURE, "could not read %s", argv[1]);
int nr, nf, nfield;
if( 2 != sscanf(line, "%d,%d", &nr, &nfield) )
err(EXIT_FAILURE, "failed to parse first line");
printf( "reading %d lines of %d fields each\n", nr, nfield );
int erc;
regex_t reg;
const char fmt[] = "([^,\n]*)[,\n]";
char *regex = calloc( nfield, 1 + strlen(fmt) );
for( int i=0; i < nfield; i++ ) {
strcat(regex, fmt);
}
int cflags = REG_EXTENDED;
char errbuf[128];
size_t len = sizeof(errbuf);
const char *truncated = "";
if( (erc = regcomp(&reg, regex, cflags)) != 0 ) {
if( (len = regerror(erc, &reg, errbuf, len)) > sizeof(errbuf) )
truncated = "(truncated)";
errx(EXIT_FAILURE, "%s %s", errbuf, truncated);
}
for( int i=0; i < nr && NULL != fgets(line, sizeof(line), input); i++ ) {
regmatch_t matches[1 + nfield];
const int eflags = 0;
printf("%s", line);
if( (erc = regexec(&reg, line, 1 + nfield, matches, eflags)) != 0 ) {
if( (len = regerror(erc, &reg, errbuf, len)) > sizeof(errbuf) )
truncated = "(truncated)";
errx(EXIT_FAILURE, "regex error: %s %s", errbuf, truncated);
}
for( nf=1; nf < nfield + 1 && matches[nf].rm_so != -1; nf++ ) {
assert(matches[nf].rm_so <= matches[nf].rm_eo);
printf( "%4d: '%.*s'\n",
nf,
(int)(matches[nf].rm_eo - matches[nf].rm_so),
line + matches[nf].rm_so );
}
}
return EXIT_SUCCESS;
}
It's only a little longer (mostly to handle errors). What I like is that once regexec(3) is called, the fields are all set up in the matches array.

How to store strings that end with certain characters?

I want to create a program that opens and writes to 3 different text files, the names a user inputs.
The condition would be that if last names end with certain characters, they would be saved to a specific text file.
For example, if the user inputs a last name that ends with ian it should be saved to the ARMENIA.TXT folder.
Here is my code and the issues I encounter with it:
struct names {
char firstName[20];
char lastName[30];
} person;
int main() {
FILE *arm, *ita, *esp;
char ian[] = "ian";
char ini[] = "ini";
char ez[] = "ez";
char response;
char *ret;
arm = fopen("C:\\Programming\\ARMENIA.TXT", "wt");
ita = fopen("C:\\Programming\\ITALIA.TXT", "wt");
esp = fopen("C:\\Programming\\ESPANIA.TXT", "wt");
if (arm, ita, esp == NULL) {
printf("Error: The archives could not be created.");
return 1;
} else {
printf("Operation Successful: Archives created.\n\n");
}
do {
fflush(stdin);
printf("\n\nPlease input your first name: ");
scanf("%s", &person.firstName);
printf("Please input your last name: ");
scanf("%s", &person.lastName);
if (ret = strstr(person.lastName, ian)) {
fwrite(person.lastName, 1, strlen(person.lastName), arm);
fwrite(person.firstName, 1, strlen(person.firstName), arm);
}
if (ret = strstr(person.lastName, ini)) {
fwrite(person.lastName, 1, strlen(person.lastName), ini);
fwrite(person.firstName, 1, strlen(person.firstName), ini);
}
if (ret = strstr(person.lastName, ez)) {
fwrite(person.lastName, 1, strlen(person.lastName), ez);
fwrite(person.firstName, 1, strlen(person.firstName), ez);
}
printf("\n\nWould you like to enter another person into the archive?: (y) or (n): ");
scanf(" %c", &response);
} while (response == 'y');
printf("\n\nThe operation has finished.\n\n");
fclose(arm);
fclose(ita);
fclose(esp);
return 0;
}
Issue: Will save to folder if last name contains ian (or ini / ez) in ANY part of the last name. How do I make the condition only if it ENDS with these strings?
Issue: Will crash if last name contains ini or ez -- basically, only first If statement works.
Issue: Needs to be saved as Lastname, Firstname -- For now, it saves as LastnameFirstname.
Your program has multiple issues.
Some of the have been addressed by Ray and Stephen Lechner, but here are some more:
The reason for the crashes is you pass string to fwrite instead of FILE* stream pointers: fwrite(person.lastName, 1, strlen(person.lastName), ini); should be written:
fwrite(person.lastName, 1, strlen(person.lastName), ita);
This is an indication that you compile without proper warnings enabled. Let the compiler help avoid such silly mistakes with -Wall for gcc or /W3 for msvc.
Note also that you should use fprintf to properly format the output to your text files. For example:
if (strEndsWith(person.lastName, "ian")) {
fprintf(arm, "%s, %s\n", person.lastName, person.firstName);
}
Here is an improved version:
#include <stdio.h>
#include <string.h>
int strEndsWith(const char *str, const char *ending) {
size_t len1 = strlen(str);
size_t len2 = strlen(ending);
return len1 >= len2 && !strcmp(str + len1 - len2, ending);
}
int main(void) {
FILE *arm = fopen("C:\\Programming\\ARMENIA.TXT", "wt");
FILE *ita = fopen("C:\\Programming\\ITALIA.TXT", "wt");
FILE *esp = fopen("C:\\Programming\\ESPANIA.TXT", "wt");
if (arm == NULL || ita == NULL || esp == NULL) {
printf("Error: The archives could not be created.\n");
return 1;
} else {
printf("Operation Successful: Archives created.\n\n");
}
for (;;) {
char firstName[20];
char lastName[30];
char response;
printf("\n\nPlease input the first name: ");
if (scanf("%19s", firstName) != 1)
break;
printf("Please input the last name: ");
if (scanf("%29s", lastName) != 1)
break;
if (strEndsWith(lastName, "ian")) {
fprintf(arm, "%s, %s\n", lastName, firstName);
}
if (strEndsWith(lastName, "ini")) {
fprintf(ita, "%s, %s\n", lastName, firstName);
}
if (strEndsWith(lastName, "ez")) {
fprintf(esp, "%s, %s\n", lastName, firstName);
}
printf("\n\nWould you like to enter another person into the archive?: (y) or (n): ");
if (scanf(" %c", &response) != 1 || response != 'y')
break;
}
printf("\n\nThe operation has finished.\n\n");
fclose(arm);
fclose(ita);
fclose(esp);
return 0;
}
In addition to the issues Ray pointed out, see the following simple code for a strEndsWith-function. Hope it helps.
int strEndsWith(const char* str, const char* ending) {
size_t strlenStr = strlen(str);
size_t strlenEnding = strlen(ending);
if (strlenStr < strlenEnding)
return 0;
const char* strAtEnding = str + strlenStr - strlenEnding;
return (strcmp(strAtEnding, ending) == 0);
}
There are a number of unrelated issues here.
arm = fopen("C:\\Programming\\ARMENIA.TXT", "wt");
That should be "w+", not "wt". Same for the others. Or just "w", unless you want to both read and write.
if (arm, ita, esp == NULL)
The comma operator evaluates its left operand and discards it, then evaluates its right operand and keeps that value. So this is equivalent to if (esp == NULL)
fflush(stdin);
This is meaningless. When printing, the system is allowed to buffer output and then print it all at once, which can speed things up. fflush forces it to actually print anything that's currently in the buffer. It's useful if, for example, you want to prompt the user for input and need to make sure the prompt shows up.
As such, it only works on an output stream. (This error is actually taught in some terrible references, so I suspect this one isn't your fault. You should stop using whatever reference told you to do this.)
Addendum: As Olaf points out in the comments, this invokes undefined behavior, which is a topic unto itself. The short and imprecise version is that any undefined behavior creates bugs that are really hard to track down.
if (ret = strstr(person.lastName, ian))
This tells you that ian is in person.lastName somewhere, but not that it's at the end. For example, suppose the last name is "ian in ez". It'll match all three of your conditions. As an aside, you don't need to store ian in a variable; you can just do strstr(person.lastName, "ian").

fgetc() can't read float

I got a big problem using fgetc() and i can't figure it out... I try to parse a text file, everything compile but at the execution I got an infinite loop xor a segfault (Code::blocks), my text file is like that: {"USD_EUR": "0.8631364", "EUR_USD": "1.3964719"} with 16 rates change. I try to put all my float in rate[16]...
void read(float change[4][4], char* myFile)
{
FILE* file = NULL;
file = fopen(myFile, "r+");
int value,i;
float rate[16];
char* str = "";
if (file != NULL)
{
do
{
value = fgetc(file);
printf("%c \n",value);
while(value > 48 && value < 57)
{
value = fgetc(file);
strcat(str, value);
//printf("%s \n", str);
}
rate[i] = atof(str);
i++;
str = "";
}while(value != EOF);// 125 = }
change[0][1] = rate[5];
change[0][2] = rate[0];
change[0][3] = rate[15];
change[1][0] = rate[6];
change[1][1] = rate[14];
change[1][2] = rate[7];
change[1][3] = rate[10];
change[2][0] = rate[8];
change[2][1] = rate[2];
change[2][2] = rate[12];
change[2][3] = rate[4];
change[3][0] = rate[3];
change[3][1] = rate[13];
change[3][2] = rate[11];
change[3][3] = rate[9];
fclose(file);
}
else
{
printf("Unable to read the file!\n");
}
}
I also try with EOF but i only have the char before numbers then that goes out of the loop ex: {"USD_EUR": "
I suggest that you simply use fscanf.
E.g
FILE *file;
int i = 0, status;
float value;
float rate[16];
file = fopen(myFile, "r");
if(file == NULL){
printf("Unable to read the file!\n");
return ;
}
while((status=fscanf(file, "%f", &value))!=EOF){
if(status==1){
rate[i++] = value;
if(i==16)//full
break;
} else {
fgetc(file);//one character drop
}
}
fclose(file);
Problem 1:
char* str = "";
Declares str as a pointer to a static string. This creates a literal "" in memory and points str to it, which isn't anything you can safely change. You want something like
char str[30] = "";
Problems 2 and 3:
strcat(str, value);
Attempts to to append to str, which isn't safe or right. Also, as guest notes, you are trying to strcat(char *, int), which isn't the correct usage. strcat(char *, char *) is correct. Note - this doesn't mean that you should strcat(str, (char *) &value); - you need to understand how strings are implemented as char arrays in C, particularly with regard to zero termination.
Problem 4:
str = "";
See user3629249's comment above. Given a proper declaration,
str[0] = '\0';
Would be correct.
Problem 5:
Again, with credit to user3629249,
in 'change', the position change[0][0] is being skipped.
In addition to the solutions provided in the other answers, when faced with a messy line of input to read, it may be easier to use the line-oriented input functions provided by libc (e.g. fgets or getline). Reading the data one line at a time into a buffer, often (not always) allows greater flexibility in parsing the data with the other tools provided by libc (e.g. strtok, strsep, etc..)
With other data, character-oriented input is a better choice. In your case, the lines were interlaced with numerous '"', ':', ' ' and ',''s. This made it difficult to construct a fscanf format string to read both exchange rates in a single call or use any of the string parsing tools like strtok. So this was truly a tough call. I agree, BluePixyes' solution for parsing a single float in a fscanf call is a good solution. The line-oriented alternative is to read a line at a time, and then using strtof to convert the float values found in the line. The only advantage that strtof provides is error checking on the conversion that allows you to verify a good float conversion. This is one approach for a line-oriented solution:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
int main () {
FILE* file = NULL; /* aways initialize variables */
float rate[16] = {0.0}; /* market rates 1st & 2nd */
char myFile[50] = {0}; /* input filename */
char line[128] = {0}; /* input line buffer */
char *p = NULL; /* pointer to parse line */
char *ep = NULL; /* pointer to parse line */
size_t idx = 0; /* index for rate array values */
size_t it = 0; /* general index iterator */
/* prompt for filename */
printf ("\n Please enter filename to read rates from: ");
scanf ("%[^\n]%*c", myFile);
/* open & validate file */
file = fopen (myFile, "r");
if (!file) {
fprintf(stderr, "error: Unable to read the file!\n");
return 1;
}
/* using line-oriented input to read line, then parse */
while (fgets (line, 127, file) != NULL)
{
if (idx == 16) {
fprintf (stderr, "warning: array full.\n");
break;
}
p = line; /* parse line for floats */
while (*p) { /* find first digit or end */
while (*p && (*p < 48 || *p > 57) ) p++;
if (!*p) break; /* validate not null */
rate[idx++] = strtof (p, &ep); /* get float, set end-ptr */
if (errno != 0 || p == ep) /* validate conversion */
fprintf (stderr, "discarding: rate[%zd] invalid read\n", --idx);
p = ep; /* set ptr to end-ptr */
}
}
fclose (file);
printf ("\n The exchange rates read from file:\n\n");
for (it = 0; it < idx; it++)
printf (" rate[%2zd] = %9.7f\n", it, rate[it]);
printf ("\n");
return 0;
}
sample input:
$ cat dat/rates.txt
"USD_EUR": "0.8631364", "EUR_USD": "1.3964719"
"USD_AGT": "0.9175622", "EUR_USD": "1.0975372"
"USD_BRZ": "0.8318743", "EUR_USD": "1.1713074"
"USD_COL": "0.9573478", "EUR_USD": "1.0537964"
"USD_GIA": "0.7904234", "EUR_USD": "1.5393454"
output:
$ ./bin/read_xchgrates
Please enter filename to read rates from: dat/rates.txt
The exchange rates read from file:
rate[ 0] = 0.8631364
rate[ 1] = 1.3964719
rate[ 2] = 0.9175622
rate[ 3] = 1.0975372
rate[ 4] = 0.8318743
rate[ 5] = 1.1713074
rate[ 6] = 0.9573478
rate[ 7] = 1.0537964
rate[ 8] = 0.7904234
rate[ 9] = 1.5393454
Note: check your strtof man page for any additional #define's your compiler may require.
the code has the following sequence:
fgetc results in 'U',
that is not a value inside the range 0...9 exclusive,
so drops through to try and convert str to rate[i]
(where 'i' has not been initialized to a known value)
Since no digits have been saved where str points,
some unknown offset from rate[] gets set to 0
(this is undefined behaviour)
then the unknown value 'i' gets incremented
and the following line: str = "" is executed
which has no effect on string
(unless each literal is at a different location in the .const section)
and the outer loop is repeated.
Eventually, a char in the range 1...8 is input
Then, in the inner loop, that first digit is SKIPPED
and another char is read.
from your example that next char is a '.'
Which could cause the inner loop to be exited
However,
the line: strcat(str, value);
should cause a seg fault event
due to trying to write to the .const section of the executable

Loop crashing in C

I'm very new to C and I'm still learning the basics. I'm creating an application that reads in a text file and breaks down the words individually. My intention will be to count the amount of times each word occurs.
Anyway, the last do-while loop in the code below executes fine, and then crashes. This loop prints memory address to this word (pointer) and then prints the word. It accomplishes this fine, and then crashes on the last iteration. My intention is to push this memory address into a singly linked list, albeit once it's stopped crashing.
Also, just a quick mention regarding the array sizes below; I yet figured out how to set the correct size needed to hold the word character array etc because you must define the size before the array is filled, and I don't know how to do this. Hence why I've set them to 1024.
#include<stdio.h>
#include<string.h>
int main (int argc, char **argv) {
FILE * pFile;
int c;
int n = 0;
char *wp;
char wordArray[1024];
char delims[] = " "; // delims spaces in the word array.
char *result = NULL;
result = strtok(wordArray, delims);
char holder[1024];
pFile=fopen (argv[1],"r");
if (pFile == NULL) perror ("Error opening file");
else {
do {
c = fgetc (pFile);
wordArray[n] = c;
n++;
} while (c != EOF);
n = 0;
fclose (pFile);
do {
result = strtok(NULL, delims);
holder[n] = *result; // holder stores the value of 'result', which should be a word.
wp = &holder[n]; // wp points to the address of 'holder' which holds the 'result'.
n++;
printf("Pointer value = %d\n", wp); // Prints the address of holder.
printf("Result is \"%s\"\n", result); // Prints the 'result' which is a word from the array.
//sl_push_front(&wp); // Push address onto stack.
} while (result != NULL);
}
return 0;
}
Please ignore the bad program structure, as I mentioned, I'm new to this!
Thanks
As others have pointed out, your second loop attempts to dereference result before you check for it being NULL. Restructure your code as follows:
result = strtok( wordArray, delims ); // do this *after* you have read data into
// wordArray
while( result != NULL )
{
holder[n] = *result;
...
result = strtok( NULL, delims );
}
Although...
You're attempting to read the entire contents of the file into memory before breaking it up into words; that's not going to work for files bigger than the size of your buffer (currently 1K). If I may make a suggestion, change your code such that you're reading individual words as you go. Here's an example that breaks the input stream up into words delimited by whitespace (blanks, newlines, tabs, etc.) and punctuation (period, comma, etc.):
#include <stdio.h>
#include <ctype.h>
int main(int argc, char **argv)
{
char buffer[1024];
int c;
size_t n = 0;
FILE *input = stdin;
if( argc > 1 )
{
input = fopen( argv[1], "r");
if (!input)
input = stdin;
}
while(( c = fgetc(input)) != EOF )
{
if (isspace(c) || ispunct(c))
{
if (n > 0)
{
buffer[n] = 0;
printf("read word %s\n", buffer);
n = 0;
}
}
else
{
buffer[n++] = c;
}
}
if (n > 0)
{
buffer[n] = 0;
printf("read word %s\n", buffer);
}
fclose(input);
return 0;
}
No warranties express or implied (having pounded this out before 7:00 a.m.). But it should give you a flavor of how to parse a file as you go. If nothing else, it avoids using strtok, which is not the greatest of tools for parsing input. You should be able to adapt this general structure to your code. For best results, you should abstract that out into its own function:
int getNextWord(FILE *stream, char *buf, size_t bufsize)
{
int c;
size_t n = 0;
while(( c = fgetc(input)) != EOF && n < bufsize)
{
if (isspace(c) || ispunct(c))
{
if (n > 0)
{
buf[n] = 0;
n = 0;
}
}
else
{
buffer[n++] = c;
}
}
if (n > 0)
{
buffer[n] = 0;
printf("read word %s\n", buffer);
}
if (n == 0)
return 0;
else
return 1;
}
and you would call it like
void foo(void)
{
char word[SOME_SIZE];
...
while (getNextWord(inFile, word, sizeof word))
{
do_something_with(word);
}
...
}
If you expect in your do...while code, that result could be null (this is the condition for loop break), how do you think this code-line:
holder[n] = *result;
must work? It seems to me, that it is the reason for crashing in your program.
Change do while loop to while
use
while (condition)
{
}
instead of
do {
}while(condition)
It is crashing because you are trying to derefrance a NULL pointer result in do while loop.
I work mostly with Objective-C and was just looking at your question for fun, but I may have a solution.
Before setting n=0; after your first do-while loop, create another variable called totalWords and set it equal to n, totalWords can be declared anywhere within the file (except within one of the do-while loops), but can be defined at the top to the else block since its lifetime is short:
totalWords = n;
then you can set n back to zero:
n = 0;
Your conditional for the final do-while loop should then say:
...
} while (n <= ++totalWords);
The logic behind the application will thus say, count the words in the file (there are n words, which is the totalWords in the file). When program prints the results to the console, it will run the second do-while loop, which will run until n is one result past the value of totalWords (this ensures that you print the final word).
Alternately, it is better practice and clearer for other programmers to use a loop and a half:
do {
result = strtok(NULL, delims);
holder[n] = *result;
wp = &holder[n];
printf("Pointer value = %d\n", wp);
printf("Result is \"%s\"\n", result);
//sl_push_front(&wp); // Push address onto stack.
if (n == totalWords) break; // This forces the program to exit the do-while after we have printed the last word
n++; // We only need to increment if we have not reached the last word
// if our logic is bad, we will enter an infinite loop, which will tell us while testing that our logic is bad.
} while (true);

Resources