'Using "fsgets" in C How can Split string and make a structure

i want to read from the file using fgets and send the information to struct in array so i write this code but the output in incorrect

this is the file i tried to read

1#18042022#14:30#Birzeit#Ramallah#6#15
2#18042022#11:45#Birzeit#Birzeit#6#1
13#19042022#14:30#Birzeit#Atara#6#20
53#20042022#14:00#Birzeit#Nablus#6#7

I have written the following code in attempt to fill an array of struct bus:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct bus
{
    int busnumber;
    char* traveldate;
    char* traveltime;
    char* fromdestination;
    char* todestination;
    float price;
    int capacity;

}bus;

int main()
{
    bus busses[numberofbuses("busses.txt")];
    FILE *fp;
    fp = fopen("busses.txt" , "r");
    char line_data[10240];
    int i = 0;
    char c;
   while(fgets(line_data,10240, fp))
    {
        printf("%s\n",line_data);
    busses[i].busnumber = atoi(strtok(line_data,"#"));
    busses[i].traveldate = strtok(NULL,"#");
    busses[i].traveltime = strtok(NULL,"#");
    busses[i].fromdestination = strtok(NULL,"#");
    busses[i].todestination = strtok(NULL,"#");
    busses[i].price = atof(strtok(NULL,"#"));
    busses[i].capacity = atoi(strtok(NULL,"#"));
    i++;
    }
printf("%s\n",busses[0].traveldate);
    for (int j = 0 ; j < numberofbuses("busses.txt") ; j++)
    {
        printf("%d %s %s %s %s %f %d\n", busses[j].busnumber,busses[j].traveldate,busses[j].traveltime,busses[j].fromdestination,busses[j].todestination , busses[j].price, busses[j].capacity);
    }

   fclose(fp);
   return(0);
}
int numberofbuses (char *filename)
{
    FILE *fp;
    char c;
    int count = 0;
    fp = fopen(filename,"r");
    if(fp == NULL)
    {
        perror("Error opening file");
        return(-1);
    }
    else
    {
        for (c = getc(fp); c != EOF; c = getc(fp))
            if (c == '\n')
                count = count + 1;
        return count;
    }
}
c


Solution 1:[1]

You are making things a bit difficult on yourself and mixing a few concepts. When you declare your struct bus and typedef, you declare pointers for each string you want to read. These pointers are uninitialized and must have storage allocated for each before attempting to copy a string to the memory location. (always answer the question "To what valid memory address does my pointer point?" for every pointer you use)

While allocating for each string can be done relatively simply, it will add unneeded complexity to your code and is unnecessary here. If your values are as shown in your sample data, then you can simply declare fixed arrays instead of pointers to the data you want to store. Looking, the following array sizes will be sufficient for your data (with roughly 50% more storage than required, and room for the nul-terminating character), e.g.

    traveldate        16-bytes
    traveltime         8-bytes
    fromdestination   63-bytes
    todestination     63-bytes

(adjust as needed)

You can declare a few constants to set the size of each array, and provide a single convenient location at the top of your code where you can make a change if required. The constants and your re-written struct could be:

#define MAXC 1024   /* if you need a constant, #define one (or more) */
#define NBUS  128
#define DESTC  64
#define DATEC  16
#define TIMEC   8

typedef struct bus {
  int busnumber;
  char traveldate[DATEC];       /* use fixed arrays or allocate for each */
  char traveltime[TIMEC];
  char fromdestination[DESTC];
  char todestination[DESTC];
  float price;
  int capacity;
} bus;

You are thinking correctly in reading an entire line at a time. MAXC above sets the maximum number of characters for your read-buffer to ensure a line is fully consumed with each call to fgets(). The NBUS constant sets the max number of busses to be read (the array size for your busses array).

Your alternative is to dynamically allocate storage for a block of memory to hold the busses. That also is relatively easy, and it allows you to grow the block of memory to handle however many busses are in your file without needed to know "how many" beforehand. (NEVER make two passes (full-reads) of your data file just to find out how many lines there are -- highly inefficient) However, for the parsing example, we will use a fixed array limited to holding NBUS busses.

With that in mind you can declare your busses array and read each line into a buffer, taking the filename to read as the first command-line argument to your program (or by default reading from stdin if no argument is given) like:

int main (int argc, char **argv) {
  
  char buf[MAXC] = "";                        /* read buffer */
  bus busses[NBUS] = {{ .busnumber = 0 }};    /* array of bus */
  size_t n = 0;                               /* number of busses */
  /* 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 array not full, read line */
  while (n < NBUS && fgets (buf, MAXC, fp)) {
    ...

While there are several alternatives for separating the lines on '#' delimiters, for your fixed-data, by far the simplest is using sscanf() and a carefully crafted format string. You can separate the data while protecting your array bounds with the following:

    "%d#%15[^#]#%7[^#]#%63[^#]#%63[^#]#%f#%d"

The sscanf() format string above will separate each line into:

  • an integer, followed by '#',
  • a string of no more than 15 characters followed by a '#',
  • a string of no more than 7 characters followed by a '#',
  • a string of no more than 63 characters followed by a '#',
  • a string of no more than 63 characters followed by a '#',
  • a float value followed by a '#', and finally
  • an integer.

You count the number of conversions anticipated (7 above) and you validate the return of sscanf() with that value. You can use a counter variable (n here) to track the number of busses read, and only increment n in your read-loop upon a successful separation of all values into your array of struct. You protect the busses array bounds by checking n against NBUS as part of the condition of your read loop (shown above).

Putting it altogether, your read and separation loop to read your file and fill your busses array will look like:

  /* while array not full, read line */
  while (n < NBUS && fgets (buf, MAXC, fp)) {
    /* separate line into stuct variables - VALIDATE return */
    if (sscanf (buf, "%d#%15[^#]#%7[^#]#%63[^#]#%63[^#]#%f#%d", 
                &busses[n].busnumber, busses[n].traveldate,
                busses[n].traveltime, busses[n].fromdestination,
                busses[n].todestination, &busses[n].price,
                &busses[n].capacity) == 7) {
      n++;  /* increment count only on successful separation */
    }
  }

That's all there is to it. You can now do whatever you like with your busses array.

Putting it together into a full example, you would have:

#include <stdio.h>

#define MAXC 1024   /* if you need a constant, #define one (or more) */
#define NBUS  128
#define DESTC  64
#define DATEC  16
#define TIMEC   8

typedef struct bus {
  int busnumber;
  char traveldate[DATEC];       /* use fixed arrays or allocate for each */
  char traveltime[TIMEC];
  char fromdestination[DESTC];
  char todestination[DESTC];
  float price;
  int capacity;
} bus;

int main (int argc, char **argv) {
  
  char buf[MAXC] = "";                        /* read buffer */
  bus busses[NBUS] = {{ .busnumber = 0 }};    /* array of bus */
  size_t n = 0;                               /* number of busses */
  /* 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 array not full, read line */
  while (n < NBUS && fgets (buf, MAXC, fp)) {
    /* separate line into stuct variables - VALIDATE return */
    if (sscanf (buf, "%d#%15[^#]#%7[^#]#%63[^#]#%63[^#]#%f#%d", 
                &busses[n].busnumber, busses[n].traveldate,
                busses[n].traveltime, busses[n].fromdestination,
                busses[n].todestination, &busses[n].price,
                &busses[n].capacity) == 7) {
      n++;  /* increment count only on successful separation */
    }
  }

  if (fp != stdin)   /* close file if not stdin */
      fclose (fp);
  
  for (size_t i = 0; i < n; i++) {  /* output results */
    printf ("%4d  %s  %s  %-12s  %-12s  %6.2f  %3d\n",
            busses[i].busnumber, busses[i].traveldate,
            busses[i].traveltime, busses[i].fromdestination,
            busses[i].todestination, busses[i].price,
            busses[i].capacity);
  }
}

Example Use/Output

With your sample data in the file dat/busses.txt you would get the following upon running the program:

./bin/readbusses dat/busses.txt
   1  18042022  14:30  Birzeit       Ramallah        6.00   15
   2  18042022  11:45  Birzeit       Birzeit         6.00    1
  13  19042022  14:30  Birzeit       Atara           6.00   20
  53  20042022  14:00  Birzeit       Nablus          6.00    7

There are many different ways to approach separating each line, but from a simplicity standpoint, and a robust standpoint (an error in any one line of data only effects data from that line and doesn't break the read from that point forward, such as if you attempted to use fscanf() directly on the file -- without clearing to end of line manually on error.)

Look things over and let me know if you have further questions.

Solution 2:[2]

It is a cool idea to read the entire file as a single string to be tokenized, but it does add a little complexity to reading it. Things work just as nicely with the strdup() option.

User input is always a total pain, but in your case it can be done fairly simply. You have to build the proper tools though:

#include <iso646.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct bus
{
    int    busnumber;
    char * traveldate;
    char * traveltime;
    char * fromdestination;
    char * todestination;
    float  price;
    int    capacity;
} bus;


bool read_string( FILE * f, char ** s )
{
    char buf[100] = {0};
    if (fscanf( f, "%99[^#]", buf ) != 1) return false;
    return (*s = strdup( buf ));
}
bool read_int      ( FILE * f, int   * n      ) { return fscanf( f, "%d", n ) == 1; }
bool read_float    ( FILE * f, float * n      ) { return fscanf( f, "%f", n ) == 1; }
bool read_separator( FILE * f, char separator ) { return fgetc( f ) == separator; }


bool read_bus( FILE * f, bus * bus )
{
    return  read_int   ( f, &bus->busnumber       ) and read_separator( f, '#'  )
        and read_string( f, &bus->traveldate      ) and read_separator( f, '#'  )
        and read_string( f, &bus->traveltime      ) and read_separator( f, '#'  )
        and read_string( f, &bus->fromdestination ) and read_separator( f, '#'  )
        and read_string( f, &bus->todestination   ) and read_separator( f, '#'  )
        and read_float ( f, &bus->price           ) and read_separator( f, '#'  )
        and read_int   ( f, &bus->capacity        ) and read_separator( f, '\n' );
}


bool load_bus_schedule( const char * filename, bus busses[], int * num_busses, int MAX_BUSSES )
{
    FILE * f = fopen( filename, "r" );
    if (!f) return false;

    while ((*num_busses < MAX_BUSSES) and read_bus( f, &busses[ *num_busses ] ))
      *num_busses += 1;

    bool ok = feof( f );
    fclose( f );
    return *num_busses and ok;
}


void free_bus( bus * bus )
{
    free( bus->traveldate      );    bus->traveldate      = NULL;
    free( bus->traveltime      );    bus->traveltime      = NULL;
    free( bus->fromdestination );    bus->fromdestination = NULL;
    free( bus->todestination   );    bus->todestination   = NULL;
}


int main()
{
    enum { MAX_BUSSES = 100 };
    bus busses[ MAX_BUSSES ] = {{0}};
    int num_busses = 0;

    if (!load_bus_schedule( "busses.txt", busses, &num_busses, MAX_BUSSES ))
    {
      fprintf( stderr, "Could not read bus schedule.\n" );
      return EXIT_FAILURE;
    }

    printf( "number of busses = %d\n", num_busses );
    for (int n = 0;  n < num_busses;  n += 1)
        printf(
            "bus %-2d  from %-8s  to %-8s  on %8s  at %5s  price %5g?  has %d seat%s available\n",
            busses[n].busnumber,
            busses[n].fromdestination,
            busses[n].todestination,
            busses[n].traveldate,
            busses[n].traveltime,
            busses[n].price,
            busses[n].capacity,
            busses[n].capacity == 1 ? "" : "s"
        );
        
    while (num_busses) free_bus( &busses[--num_busses] );
    return EXIT_SUCCESS;
}

Oh, from your OP I surmize that the source file is a CSV format with # as field separators and \n as record separators.

1#18042022#14:30#Birzeit#Ramallah#6#15
2#18042022#11:45#Birzeit#Birzeit#7.5#1
13#19042022#14:30#Birzeit#Atara#8#20
53#20042022#14:00#Birzeit#Nablus#10.5#7

Output:

number of busses = 4
bus 1   from Birzeit   to Ramallah  on 18042022  at 14:30  price     6?  has 15 seats available
bus 2   from Birzeit   to Birzeit   on 18042022  at 11:45  price   7.5?  has 1 seat available
bus 13  from Birzeit   to Atara     on 19042022  at 14:30  price     8?  has 20 seats available
bus 53  from Birzeit   to Nablus    on 20042022  at 14:00  price  10.5?  has 7 seats available

A point of note: David C. Rankin’s answer recommends that you first read each line of data and then tokenize it into fields. This is good advice (and my usual recommendation as well).

The approach used here is to simply fail on the first error, for the entire file. It is possible to add stuff that will recover properly, but the choice of whether to do that or not depends on how you intend to deal with the failure. This approach requires input to be correct and does not allow for error. This is a valid approach.

The choice of approach will ultimately depend on human factors (such as business considerations). For a schoolwork I think that rejecting all bad input is a valid requirement.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2