c read block of lines and store them [duplicate] - c
I am really new to C, and the reading files thing drives me crazy...
I want read a file including name, born place and phone number, etc. All separated by tab
The format might be like this:
Bob Jason Los Angeles 33333333
Alice Wong Washington DC 111-333-222
So I create a struct to record it.
typedef struct Person{
char name[20];
char address[30];
char phone[20];
} Person;
I tried many ways to read this file into struct but it failed.
I tired fread:
read_file = fopen("read.txt", "r");
Person temp;
fread(&temp, sizeof(Person), 100, read_file);
printf("%s %s %s \n", temp.name, temp.address, temp.phone);
But char string does not recorded into temp separated by tab, it read the whole file into temp.name and get weird output.
Then I tried fscanf and sscanf, those all not working for separating tab
fscanf(read_file, "%s %s %s", temp.name, temp.address, temp.phone);
Or
fscanf(read_file, "%s\t%s\t%s", temp.name, temp.address, temp.phone);
This separates the string by space, so I get Bob and Jason separately, while indeed, I need to get "Bob Jason" as one char string. And I did separate these format by tab when I created the text file.
Same for sscanf, I tried different ways many times...
Please help...
I suggest:
Use fgets to read the text line by line.
Use strtok to separate the contents of the line by using tab as the delimiter.
// Use an appropriate number for LINE_SIZE
#define LINE_SIZE 200
char line[LINE_SIZE];
if ( fgets(line, sizeof(line), read_file) == NULL )
{
// Deal with error.
}
Person temp;
char* token = strtok(line, "\t");
if ( token == NULL )
{
// Deal with error.
}
else
{
// Copy token at most the number of characters
// temp.name can hold. Similar logic applies to address
// and phone number.
temp.name[0] = '\0';
strncat(temp.name, token, sizeof(temp.name)-1);
}
token = strtok(NULL, "\t");
if ( token == NULL )
{
// Deal with error.
}
else
{
temp.address[0] = '\0';
strncat(temp.address, token, sizeof(temp.address)-1);
}
token = strtok(NULL, "\n");
if ( token == NULL )
{
// Deal with error.
}
else
{
temp.phone[0] = '\0';
strncat(temp.phone, token, sizeof(temp.phone)-1);
}
Update
Using a helper function, the code can be reduced in size. (Thanks #chux)
// The helper function.
void copyToken(char* destination,
char* source,
size_t maxLen;
char const* delimiter)
{
char* token = strtok(source, delimiter);
if ( token != NULL )
{
destination[0] = '\0';
strncat(destination, token, maxLen-1);
}
}
// Use an appropriate number for LINE_SIZE
#define LINE_SIZE 200
char line[LINE_SIZE];
if ( fgets(line, sizeof(line), read_file) == NULL )
{
// Deal with error.
}
Person temp;
copyToken(temp.name, line, sizeof(temp.name), "\t");
copyToken(temp.address, NULL, sizeof(temp.address), "\t");
copyToken(temp.phone, NULL, sizeof(temp.phone), "\n");
This is only for demonstration, there are better ways to initialize variables, but to illustrate your main question i.e. reading a file delimited by tabs, you can write a function something like this:
Assuming a strict field definition, and your struct definition you can get tokens using strtok().
//for a file with constant field definitions
void GetFileContents(char *file, PERSON *person)
{
char line[260];
FILE *fp;
char *buf=0;
char temp[80];
int i = -1;
fp = fopen(file, "r");
while(fgets(line, 260, fp))
{
i++;
buf = strtok(line, "\t\n");
if(buf) strcpy(person[i].name, buf);
buf = strtok(NULL, "\t\n");
if(buf) strcpy(person[i].address, buf);
buf = strtok(NULL, "\t\n");
if(buf) strcpy(person[i].phone, buf);
//Note: if you have more fields, add more strtok/strcpy sections
//Note: This method will ONLY work for consistent number of fields.
//If variable number of fields, suggest 2 dimensional string array.
}
fclose(fp);
}
Call it in main() like this:
int main(void)
{
//...
PERSON person[NUM_LINES], *pPerson; //NUM_LINES defined elsewhere
//and there are better ways
//this is just for illustration
pPerson = &person[0];//initialize pointer to person
GetFileContents(filename, pPerson); //call function to populate person.
//...
return 0;
}
First thing,
fread(&temp, sizeof(temp), 100, read_file);
will not work because the fields are not fixed width, so it will always read 20 characters for name 30 for address and so on, which is not always the correct thing to do.
You need to read one line at a time, and then parse the line, you can use any method you like to read a like, a simple one is by using fgets() like this
char line[100];
Person persons[100];
int index;
index = 0;
while (fgets(line, sizeof(line), read_file) != NULL)
{
persons[i++] = parseLineAndExtractPerson(line);
}
Now we need a function to parse the line and store the data in you Person struct instance
char *extractToken(const char *const line, char *buffer, size_t bufferLength)
{
char *pointer;
size_t length;
if ((line == NULL) || (buffer == NULL))
return NULL;
pointer = strpbrk(line, "\t");
if (pointer == NULL)
length = strlen(line);
else
length = pointer - line;
if (length >= bufferLength) /* truncate the string if it was too long */
length = bufferLength - 1;
buffer[length] = '\0';
memcpy(buffer, line, length);
return pointer + 1;
}
Person parseLineAndExtractPerson(const char *line)
{
Person person;
person.name[0] = '\0';
person.address[0] = '\0';
person.phone[0] = '\0';
line = extractToken(line, person.name, sizeof(person.name));
line = extractToken(line, person.address, sizeof(person.address));
line = extractToken(line, person.phone, sizeof(person.phone));
return person;
}
Here is a sample implementation of a loop to read at most 100 records
int main(void)
{
char line[100];
Person persons[100];
int index;
FILE *read_file;
read_file = fopen("/path/to/the/file.type", "r");
if (read_file == NULL)
return -1;
index = 0;
while ((index < 100) && (fgets(line, sizeof(line), read_file) != NULL))
{
size_t length;
/* remove the '\n' left by `fgets()'. */
length = strlen(line);
if ((length > 0) && (line[length - 1] == '\n'))
line[length - 1] = '\0';
persons[index++] = parseLineAndExtractPerson(line);
}
fclose(read_file);
while (--index >= 0)
printf("%s: %s, %s\n", persons[index].name, persons[index].address, persons[index].phone);
return 0;
}
Here is a complete program that does what I think you need
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Person{
char name[20];
char address[30];
char phone[20];
} Person;
char *extractToken(const char *const line, char *buffer, size_t bufferLength)
{
char *pointer;
size_t length;
if ((line == NULL) || (buffer == NULL))
return NULL;
pointer = strpbrk(line, "\t");
if (pointer == NULL)
length = strlen(line);
else
length = pointer - line;
if (length >= bufferLength) /* truncate the string if it was too long */
length = bufferLength - 1;
buffer[length] = '\0';
memcpy(buffer, line, length);
return pointer + 1;
}
Person parseLineAndExtractPerson(const char *line)
{
Person person;
person.name[0] = '\0';
person.address[0] = '\0';
person.phone[0] = '\0';
line = extractToken(line, person.name, sizeof(person.name));
line = extractToken(line, person.address, sizeof(person.address));
line = extractToken(line, person.phone, sizeof(person.phone));
return person;
}
int main(void)
{
char line[100];
Person persons[100];
int index;
FILE *read_file;
read_file = fopen("/home/iharob/data.dat", "r");
if (read_file == NULL)
return -1;
index = 0;
while (fgets(line, sizeof(line), read_file) != NULL)
{
size_t length;
length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
persons[index++] = parseLineAndExtractPerson(line);
}
fclose(read_file);
while (--index >= 0)
printf("%s: %s, %s\n", persons[index].name, persons[index].address, persons[index].phone);
return 0;
}
Parsing strings returned by fgets can be very annoying, especially when input is truncated. In fact, fgets leaves a lot to be desired. Did you get the correct string or was there more? Is there a newline at the end? For that matter, is the end 20 bytes away or 32768 bytes away? It would be nice if you didn't need to count that many bytes twice -- once with fgets and once with strlen, just to remove a newline that you didn't want.
Things like fscanf don't necessarily work as intended in this situation unless you have C99's "scanset" feature available, and then that will automatically add a null terminator, if you have enough room. The return value of any of the scanf family is your friend in determining whether success or failure occurred.
You can avoid the null terminator by using %NNc, where NN is the width, but if there's a \t in those NN bytes, then you need to separate it and move it to the next field, except that means bytes in the next field must be moved to the field after that one, and the 90th field will need its bytes moved to the 91st field... And hopefully you only need to do that once... Obviously that isn't actually a solution either.
Given those reasons, I feel it's easier just to read until you encounter one of the expected delimiters and let you decide the behavior of the function when the size specified is too small for a null terminator, yet large enough to fill your buffer. Anyway, here's the code. I think it's pretty straightforward:
/*
* Read a token.
*
* tok: The buffer used to store the token.
* max: The maximum number of characters to store in the buffer.
* delims: A string containing the individual delimiter bytes.
* fileptr: The file pointer to read the token from.
*
* Return value:
* - max: The buffer is full. In this case, the string _IS NOT_ null terminated.
* This may or may not be a problem: it's your choice.
* - (size_t)-1: An I/O error occurred before the last delimiter
* (just like with `fgets`, use `feof`).
* - any other value: The length of the token as `strlen` would return.
* In this case, the string _IS_ null terminated.
*/
size_t
read_token(char *restrict tok, size_t max, const char *restrict delims,
FILE *restrict fileptr)
{
int c;
size_t n;
for (n = 0; n < max && (c = getchar()) != EOF &&
strchr(delims, c) == NULL; ++n)
*tok++ = c;
if (c == EOF)
return (size_t)-1;
if (n == max)
return max;
*tok = 0;
return n;
}
Usage is pretty straightforward as well:
#include <stdio.h>
#include <stdlib.h>
typedef struct person {
char name[20];
char address[30];
char phone[20];
} Person;
int
main(void)
{
FILE *read_file;
Person temp;
size_t line_num;
size_t len;
int c;
int exit_status = EXIT_SUCCESS;
read_file = fopen("read.txt", "r");
if (read_file == NULL) {
fprintf(stderr, "Error opening read.txt\n");
return 1;
}
for (line_num = 0;; ++line_num) {
/*
* Used for detecting early EOF
* (e.g. the last line contains only a name).
*/
temp.name[0] = temp.phone[0] = 0;
len = read_token(temp.name, sizeof(temp.name), "\t",
read_file);
if (len == (size_t)-1)
break;
if (len == max) {
fprintf(stderr, "Skipping bad line %zu\n", line_num + 1);
while ((c = getchar()) != EOF && c != '\n')
; /* nothing */
continue;
}
len = read_token(temp.address, sizeof(temp.address), "\t",
read_file);
if (len == (size_t)-1)
break;
if (len == max) {
fprintf(stderr, "Skipping bad line %zu\n", line_num + 1);
while ((c = getchar()) != EOF && c != '\n')
; /* nothing */
continue;
}
len = read_token(temp.phone, sizeof(temp.phone), "\t",
read_file);
if (len == (size_t)-1)
break;
if (len == max) {
fprintf(stderr, "Skipping bad line %zu\n", line_num + 1);
while ((c = getchar()) != EOF && c != '\n')
; /* nothing */
continue;
}
// Do something with the input here. Example:
printf("Entry %zu:\n"
"\tName: %.*s\n"
"\tAddress: %.*s\n"
"\tPhone: %.*s\n\n",
line_num + 1,
(int)sizeof(temp.name), temp.name,
(int)sizeof(temp.address), temp.address,
(int)sizeof(temp.phone), temp.phone);
}
if (ferror(read_file)) {
fprintf(stderr, "error reading from file\n");
exit_status = EXIT_FAILURE;
}
else if (feof(read_file) && temp.phone[0] == 0 && temp.name[0] != 0) {
fprintf(stderr, "Unexpected end of file while reading entry %zu\n",
line_num + 1);
exit_status = EXIT_FAILURE;
}
//else feof(read_file) is still true, but we parsed a full entry/record
fclose(read_file);
return exit_status;
}
Notice how the exact same 8 lines of code appear in the read loop to handle the return value of read_token? Because of that, I think there's probably room for another function to call read_token and handle its return value, allowing main to simply call this "read_token handler", but I think the code above gives you the basic idea about how to work with read_token and how it can apply in your situation. You might change the behavior in some way, if you like, but the read_token function above would suit me rather well when working with delimited input like this (things would be a bit more complex when you add quoted fields into the mix, but not much more complex as far as I can tell). You can decide what happens with max being returned. I opted for it being considered an error, but you might think otherwise. You might even add an extra getchar when n == max and consider max being a successful return value and something like (size_t)-2 being the "token too large" error indicator instead.
Related
Can't compare Lines of a file in C
I got this piece of code: void scanLinesforArray(FILE* file, char search[], int* lineNr){ char line[1024]; int line_count = 0; while(fgets(line, sizeof(line),file) !=NULL){ ++line_count; printf("%d",line_count); printf(line); char *temp = malloc(strlen(line)); // strncpy(temp,line,sizeof(line)); // printf("%s\n",temp); free(temp); continue; } } This will print all lines of the file, but as soon as I uncomment the strncpy(), the program just stops without error. Same happens as soon as I use strstr() to compare the line to my search variable. I tried the continue statement and other redundant things, but nothing helps.
Many problems: Do not print a general string as a format Code risks undefined behavior should the string contain a %. // printf(line); // BAD printf("%s", line); // or fputs(line, stdout); Bad size strncpy(temp,line,sizeof(line)); is like strncpy(temp,line, 1024);, yet temp points to less than 1024 allocated bytes. Code attempts to write outside allocated memory. Undefined behavior (UB). Rarely should code use strncpy(). Bad specifier %s expects a match string. temp does not point to a string as it lacks a null character. Instead allocated for the '\0'. // printf("%s\n", temp);`. char *temp = malloc(strlen(line) + 1); // + 1 strcpy(temp,line); printf("<%s>", temp); free(temp); No compare "Can't compare Lines of a file in C" is curious as there is no compare code. Recall fgets() typically retains a '\n' in line[]. Perhaps long scanLinesforArray(FILE* file, const char search[]){ char line[1024*4]; // Suggest wider buffer - should be at least as wide as the search string. long line_count = 0; // Suggest wider type while(fgets(line, sizeof line, file)) { line_count++; line[strcspn(line, "\n")] = 0; // Lop off potential \n if (strcmp(line, search) == 0) { return line_count; } } return 0; // No match } Advanced: Sample better performance code. long scanLinesforArray(FILE *file, const char search[]) { size_t len = strlen(search); size_t sz = len + 1; if (sz < BUFSIZ) sz = BUFSIZ; if (sz > INT_MAX) { return -2; // Too big for fgets() } char *line = malloc(sz); if (line == NULL) { return -1; } long line_count = 0; while (fgets(line, (int) sz, file)) { line_count++; if (memcmp(line, search, len) == 0) { if (line[len] == '\n' || line[len] == 0) { free(line); return line_count; } } } free(line); return 0; // No match }
Dynamically allocated unknown length string reading from file (it has to be protected from reading numbers from the file) in C
My problem is such that I need to read string from file. File example: Example 1 sentence Example sentence number xd 595 xd 49 lol but I have to read only the string part, not numbers. I guess I have to use fscanf() with %s for it but let me know what you guys think about it. The part where my problem begins is how to read the string (it is unknown length) using malloc(), realloc()? I tried it by myself, but I failed (my solution is at bottom of my post). Then I need to show the result on the screen. P.S. I have to use malloc()/calloc(), realloc() <-- it has to be dynamically allocated string :) (char *) Code I've tried: int wordSize = 2; char *word = (char *)malloc(wordSize*sizeof(char)); char ch; FILE* InputWords = NULL; InputWords = fopen(ListOfWords,"r"); /* variable ListOfWords contains name of the file */ if (InputWords == NULL) { printf("Error while opening the file.\n"); return 0; } int index = 0; while((ch = fgetc(InputWords)) != -1) { if(ch == ' ') { printf("%s\n", word); wordSize = 2; index = 0; free(word); char* word = (char *)malloc(wordSize*sizeof(char)); } else { wordSize++; word = (char *)realloc(word, wordSize*sizeof(char)); strcpy(word,ch); index++; } } fclose(InputWords);
For your code, you have something have to improve: fgetc return the int type not char. So change char ch to int ch; As the comment of #pmg use EOF (may be any negative value) instead of -1` strcpy(word,ch); you try to copy character (ch) to character pointer (word). Do not cast malloc or realloc function: Do I cast the result of malloc?. For solving your question, i propose you use the strtok function to split string by space character, then test each word is number or not. If the word is not a number, you can use strcat to concatenate the word to the old sentence. The complete code: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> int is_number(char *str) { if (strlen(str) == 0) return -1; for(int i =0; (i < strlen(str)) && (str[i] != '\n') ; i++) { if(!isdigit(str[i])) return -1; } return 1; } int main() { FILE *fp = fopen("input.txt", "r"); char line[256]; if(!fp) return -1; char **sentence; int i = 0; sentence = malloc(sizeof(char *)); if(!sentence) return -1; while(fgets(line, 256, fp)) { char * token = strtok(line, " "); size_t len = 0; sentence = realloc(sentence, sizeof(char *) * (i+1)); if(!sentence) return -1; while(token != NULL) { if (is_number(token) != 1) { sentence[i] = realloc(sentence[i], len + 2 + strlen(token)); // +2 because 1 for null character and 1 for space character if (!sentence[i]) { printf("cannot realloc\n"); return -1; } strcat(strcat(sentence[i], " "), token); len = strlen(sentence[i]); } token = strtok(NULL, " "); } if(len > 0) i++; } for(int j = 0; j < i; j++) { printf("line[%d]: %s", j, sentence[j]); } for(int j = 0; j < i; j++) { free(sentence[j]); } free(sentence); fclose(fp); return 0; } The input and output: $cat input.txt Example 1 sentence Example sentence number xd 595 xd 49 lol ./test line[0]: Example sentence line[1]: Example sentence number xd xd lol
Reading in a line from file or stdin dynamically
I am posed with a situation where my function does exactly what I want except handle higher amounts of input. I initially thought to process each character one by one but was running into problems doing this. So fscanf not only does what I want it to do but it is essential in reading in only one line. I noticed, I cannot reallocate space for bigger array this way though. I have tried using format specifiers i.e. %*s to include a specific amount of buffer space before hand but this still does not work. I have noticed also, I would have no way of knowing the size of the string I am reading in. Here is my attempt and thoughts: #define LINE_MAX 1000 char* getline(FILE* inputStream) { int capacity = LINE_MAX; char* line = malloc(capacity * sizeof(char)); int ch; /* if (sizeof(capacity) == sizeof(line)) { // Not a valid comparison? Too late? capacity *= 2; line = realloc(line, capacity * sizeof(line)); } */ if (fscanf(stream, "%[^\n]s", line) == 1) { ch = fgetc(inputStream); if (ch != '\n' && ch != EOF) { fscanf(inputStream, "%*[^\n]"); fscanf(inputStream, "%*c"); } free(line); return line; } free(line); return NULL; } I am new to memory allocation in general but I feel as though I had a good idea of what to do here. Turns out I was wrong.
Here is an example to read a line and store it in a Character array. #include <stdio.h> #include <stdlib.h> int main(void) { signed char *str; int c; int i; int size = 10; str = malloc(size*sizeof(char)); for(i=0;(c=getchar()) !='\n' && c != EOF;++i){ if( i == size){ size = 2*size; str = realloc(str, size*sizeof(char)); if(str == NULL){ printf("Error Unable to Grow String! :("); exit(-1); } } str[i] = c; } if(i == size){ str = realloc(str, (size+1)*sizeof(char)); if(str == NULL){ printf("Error Unable to Grow String! :("); exit(-1); } } str[i] = '\0'; printf("My String : %s", str); return 0; } The array is resized to twice it's original size if current array can't hold the characters read from input.
Using strcat to concatenate lines from a file
I'm trying to read a file and a lines next line, to see if it starts with a space, for instance: First line second line Third line fourth line Where when I'm reading the file in, I want to check and see if the following line, has a space, if it does, I want to strcat the two lines (in this case the first and second line). So for the first example: 1.) Read in the first line, read ahead to the second, see that their is a space, and strcat both strings, making "First linesecond line" (Repeat for any other lines that follow this pattern). Here's my go at it: int main(void) { FILE * file = fopen("file.txt","r"); if(file==NULL) { return 0; } char * fileBuffer = malloc(sizeof(char)*100); char * temp = malloc(sizeof(char)*100); while(fgets(fileBuffer,100,file) != NULL) { if(isspace(fileBuffer[0])) { strcpy(temp,fileBuffer); //store line that has a space in static temporary variable } else { if(strcmp(temp,"") != 0) { //if temp is not empty strcat(fileBuffer,temp); } } } free(fileBuffer); free(temp); return 0; } However this doesn't work. What happens is when fgets gets executed, it reads the first line, it sees there is no white space, and then goes to the next line, sees there is white space, stores it, but now fileBuffer doesn't contain the first line anymore, but the second. So when I strcat the next time,I don't get the right result of "First linesecond line". Instead i get the result of the third line mixed with the second, which is not what I want. I'm not exactly sure how to fix my logic here, any ideas?
fix your logic like this: #define LINE_SIZE 100 int main(void) { FILE * file = fopen("file.txt","r"); if(file==NULL) { perror("fopen"); return -1; } char * fileBuffer = malloc(LINE_SIZE); char * temp = malloc(LINE_SIZE * 2);//Binding of the string is only once while(fgets(fileBuffer, LINE_SIZE, file) != NULL) { if(isspace(fileBuffer[0])) { temp[strcspn(temp, "\n")] = 0;//remove newline strcat(temp, &fileBuffer[1]); printf("%s", temp); } else { strcpy(temp, fileBuffer); } } fclose(file); free(fileBuffer); free(temp); return 0; }
You have several additional considerations you will need to deal with. The first being, you need to remove the trailing '\n' include in the read by fgets. To do that, you will need the length of the line read. You can then remove the trialing '\n' by overwriting it with a nul-terminating character. e.g.: while (fgets (buf1, MAXC, fp)) { size_t len1 = strlen (buf1); /* get length */ if (len1 && buf1[len1-1] == '\n') buf1[--len1] = 0; /* remove \n */ Another consideration is how to manage allocation and freeing of the memory you use for the combined line. Since you are reading both the first part and then second part of your final line from relatively fixed length strings, it would make more sense to use 2 static buffers, one for reading the line, and one for holding a copy of the first. You can allocate for the final result. e.g. enum { MAXC = 100 }; /* constant for max characters */ ... int main (int argc, char **argv) { char buf1[MAXC] = {0}; char buf2[MAXC] = {0}; char *both = NULL; Once you have both parts of your line ready to combine, you can allocate exactly the space needed, e.g. if (*buf1 == ' ' && *buf2) { both = malloc (len1 + strlen (buf2) + 1); strcpy (both, buf2); strcat (both, buf1); ... Don't forget to free both after each allocation. Putting the pieces together, and fixing the logic of your comparisons, you could end up with a solution like the following: #include <stdio.h> #include <stdlib.h> #include <string.h> enum { MAXC = 100 }; int main (int argc, char **argv) { char buf1[MAXC] = {0}; char buf2[MAXC] = {0}; char *both = NULL; FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin; if (!fp) { fprintf (stderr, "error: file open failed '%s'.\n,", argv[1]); return 1; } while (fgets (buf1, MAXC, fp)) { size_t len1 = strlen (buf1); if (len1 && buf1[len1-1] == '\n') buf1[--len1] = 0; if (*buf1 == ' ' && *buf2) { both = malloc (len1 + strlen (buf2) + 1); strcpy (both, buf2); strcat (both, buf1); printf ("'%s'\n", both); free (both); *buf2 = 0; } else strcpy (buf2, buf1); } if (fp != stdin) fclose (fp); return 0; } Output $ ./bin/cpcat ../dat/catfile.txt 'First line second line' 'Third line fourth line' Look it over and let me know if you have any questions.
Really a quick and dirty way #include <stdio.h> #include <stdlib.h> #include <windows.h> int main(void) { FILE * file = fopen("file.txt", "r"); if (file == NULL) { return 0; } char fileBuffer[100]; char temp[100]; int first = 1; while (fgets(fileBuffer, 100, file) != NULL) { if (isspace(fileBuffer[0])) { strcat(temp, fileBuffer); //store line that has a space in static temporary variable } else { if (first == 1){ strncpy(temp, fileBuffer, sizeof(temp)); // Remove the end line temp[strlen(temp) - 1] = 0; strcat(temp, " "); first = 0; } else{ if (strcmp(temp, "") != 0) { //if temp is not empty strcat(temp, fileBuffer); // Remove the end line temp[strlen(temp) - 1] = 0; strcat(temp, " "); } } } } printf("%s", temp); free(fileBuffer); free(temp); return 0; } Output:
Read files separated by tab in c
I am really new to C, and the reading files thing drives me crazy... I want read a file including name, born place and phone number, etc. All separated by tab The format might be like this: Bob Jason Los Angeles 33333333 Alice Wong Washington DC 111-333-222 So I create a struct to record it. typedef struct Person{ char name[20]; char address[30]; char phone[20]; } Person; I tried many ways to read this file into struct but it failed. I tired fread: read_file = fopen("read.txt", "r"); Person temp; fread(&temp, sizeof(Person), 100, read_file); printf("%s %s %s \n", temp.name, temp.address, temp.phone); But char string does not recorded into temp separated by tab, it read the whole file into temp.name and get weird output. Then I tried fscanf and sscanf, those all not working for separating tab fscanf(read_file, "%s %s %s", temp.name, temp.address, temp.phone); Or fscanf(read_file, "%s\t%s\t%s", temp.name, temp.address, temp.phone); This separates the string by space, so I get Bob and Jason separately, while indeed, I need to get "Bob Jason" as one char string. And I did separate these format by tab when I created the text file. Same for sscanf, I tried different ways many times... Please help...
I suggest: Use fgets to read the text line by line. Use strtok to separate the contents of the line by using tab as the delimiter. // Use an appropriate number for LINE_SIZE #define LINE_SIZE 200 char line[LINE_SIZE]; if ( fgets(line, sizeof(line), read_file) == NULL ) { // Deal with error. } Person temp; char* token = strtok(line, "\t"); if ( token == NULL ) { // Deal with error. } else { // Copy token at most the number of characters // temp.name can hold. Similar logic applies to address // and phone number. temp.name[0] = '\0'; strncat(temp.name, token, sizeof(temp.name)-1); } token = strtok(NULL, "\t"); if ( token == NULL ) { // Deal with error. } else { temp.address[0] = '\0'; strncat(temp.address, token, sizeof(temp.address)-1); } token = strtok(NULL, "\n"); if ( token == NULL ) { // Deal with error. } else { temp.phone[0] = '\0'; strncat(temp.phone, token, sizeof(temp.phone)-1); } Update Using a helper function, the code can be reduced in size. (Thanks #chux) // The helper function. void copyToken(char* destination, char* source, size_t maxLen; char const* delimiter) { char* token = strtok(source, delimiter); if ( token != NULL ) { destination[0] = '\0'; strncat(destination, token, maxLen-1); } } // Use an appropriate number for LINE_SIZE #define LINE_SIZE 200 char line[LINE_SIZE]; if ( fgets(line, sizeof(line), read_file) == NULL ) { // Deal with error. } Person temp; copyToken(temp.name, line, sizeof(temp.name), "\t"); copyToken(temp.address, NULL, sizeof(temp.address), "\t"); copyToken(temp.phone, NULL, sizeof(temp.phone), "\n");
This is only for demonstration, there are better ways to initialize variables, but to illustrate your main question i.e. reading a file delimited by tabs, you can write a function something like this: Assuming a strict field definition, and your struct definition you can get tokens using strtok(). //for a file with constant field definitions void GetFileContents(char *file, PERSON *person) { char line[260]; FILE *fp; char *buf=0; char temp[80]; int i = -1; fp = fopen(file, "r"); while(fgets(line, 260, fp)) { i++; buf = strtok(line, "\t\n"); if(buf) strcpy(person[i].name, buf); buf = strtok(NULL, "\t\n"); if(buf) strcpy(person[i].address, buf); buf = strtok(NULL, "\t\n"); if(buf) strcpy(person[i].phone, buf); //Note: if you have more fields, add more strtok/strcpy sections //Note: This method will ONLY work for consistent number of fields. //If variable number of fields, suggest 2 dimensional string array. } fclose(fp); } Call it in main() like this: int main(void) { //... PERSON person[NUM_LINES], *pPerson; //NUM_LINES defined elsewhere //and there are better ways //this is just for illustration pPerson = &person[0];//initialize pointer to person GetFileContents(filename, pPerson); //call function to populate person. //... return 0; }
First thing, fread(&temp, sizeof(temp), 100, read_file); will not work because the fields are not fixed width, so it will always read 20 characters for name 30 for address and so on, which is not always the correct thing to do. You need to read one line at a time, and then parse the line, you can use any method you like to read a like, a simple one is by using fgets() like this char line[100]; Person persons[100]; int index; index = 0; while (fgets(line, sizeof(line), read_file) != NULL) { persons[i++] = parseLineAndExtractPerson(line); } Now we need a function to parse the line and store the data in you Person struct instance char *extractToken(const char *const line, char *buffer, size_t bufferLength) { char *pointer; size_t length; if ((line == NULL) || (buffer == NULL)) return NULL; pointer = strpbrk(line, "\t"); if (pointer == NULL) length = strlen(line); else length = pointer - line; if (length >= bufferLength) /* truncate the string if it was too long */ length = bufferLength - 1; buffer[length] = '\0'; memcpy(buffer, line, length); return pointer + 1; } Person parseLineAndExtractPerson(const char *line) { Person person; person.name[0] = '\0'; person.address[0] = '\0'; person.phone[0] = '\0'; line = extractToken(line, person.name, sizeof(person.name)); line = extractToken(line, person.address, sizeof(person.address)); line = extractToken(line, person.phone, sizeof(person.phone)); return person; } Here is a sample implementation of a loop to read at most 100 records int main(void) { char line[100]; Person persons[100]; int index; FILE *read_file; read_file = fopen("/path/to/the/file.type", "r"); if (read_file == NULL) return -1; index = 0; while ((index < 100) && (fgets(line, sizeof(line), read_file) != NULL)) { size_t length; /* remove the '\n' left by `fgets()'. */ length = strlen(line); if ((length > 0) && (line[length - 1] == '\n')) line[length - 1] = '\0'; persons[index++] = parseLineAndExtractPerson(line); } fclose(read_file); while (--index >= 0) printf("%s: %s, %s\n", persons[index].name, persons[index].address, persons[index].phone); return 0; } Here is a complete program that does what I think you need #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct Person{ char name[20]; char address[30]; char phone[20]; } Person; char *extractToken(const char *const line, char *buffer, size_t bufferLength) { char *pointer; size_t length; if ((line == NULL) || (buffer == NULL)) return NULL; pointer = strpbrk(line, "\t"); if (pointer == NULL) length = strlen(line); else length = pointer - line; if (length >= bufferLength) /* truncate the string if it was too long */ length = bufferLength - 1; buffer[length] = '\0'; memcpy(buffer, line, length); return pointer + 1; } Person parseLineAndExtractPerson(const char *line) { Person person; person.name[0] = '\0'; person.address[0] = '\0'; person.phone[0] = '\0'; line = extractToken(line, person.name, sizeof(person.name)); line = extractToken(line, person.address, sizeof(person.address)); line = extractToken(line, person.phone, sizeof(person.phone)); return person; } int main(void) { char line[100]; Person persons[100]; int index; FILE *read_file; read_file = fopen("/home/iharob/data.dat", "r"); if (read_file == NULL) return -1; index = 0; while (fgets(line, sizeof(line), read_file) != NULL) { size_t length; length = strlen(line); if (line[length - 1] == '\n') line[length - 1] = '\0'; persons[index++] = parseLineAndExtractPerson(line); } fclose(read_file); while (--index >= 0) printf("%s: %s, %s\n", persons[index].name, persons[index].address, persons[index].phone); return 0; }
Parsing strings returned by fgets can be very annoying, especially when input is truncated. In fact, fgets leaves a lot to be desired. Did you get the correct string or was there more? Is there a newline at the end? For that matter, is the end 20 bytes away or 32768 bytes away? It would be nice if you didn't need to count that many bytes twice -- once with fgets and once with strlen, just to remove a newline that you didn't want. Things like fscanf don't necessarily work as intended in this situation unless you have C99's "scanset" feature available, and then that will automatically add a null terminator, if you have enough room. The return value of any of the scanf family is your friend in determining whether success or failure occurred. You can avoid the null terminator by using %NNc, where NN is the width, but if there's a \t in those NN bytes, then you need to separate it and move it to the next field, except that means bytes in the next field must be moved to the field after that one, and the 90th field will need its bytes moved to the 91st field... And hopefully you only need to do that once... Obviously that isn't actually a solution either. Given those reasons, I feel it's easier just to read until you encounter one of the expected delimiters and let you decide the behavior of the function when the size specified is too small for a null terminator, yet large enough to fill your buffer. Anyway, here's the code. I think it's pretty straightforward: /* * Read a token. * * tok: The buffer used to store the token. * max: The maximum number of characters to store in the buffer. * delims: A string containing the individual delimiter bytes. * fileptr: The file pointer to read the token from. * * Return value: * - max: The buffer is full. In this case, the string _IS NOT_ null terminated. * This may or may not be a problem: it's your choice. * - (size_t)-1: An I/O error occurred before the last delimiter * (just like with `fgets`, use `feof`). * - any other value: The length of the token as `strlen` would return. * In this case, the string _IS_ null terminated. */ size_t read_token(char *restrict tok, size_t max, const char *restrict delims, FILE *restrict fileptr) { int c; size_t n; for (n = 0; n < max && (c = getchar()) != EOF && strchr(delims, c) == NULL; ++n) *tok++ = c; if (c == EOF) return (size_t)-1; if (n == max) return max; *tok = 0; return n; } Usage is pretty straightforward as well: #include <stdio.h> #include <stdlib.h> typedef struct person { char name[20]; char address[30]; char phone[20]; } Person; int main(void) { FILE *read_file; Person temp; size_t line_num; size_t len; int c; int exit_status = EXIT_SUCCESS; read_file = fopen("read.txt", "r"); if (read_file == NULL) { fprintf(stderr, "Error opening read.txt\n"); return 1; } for (line_num = 0;; ++line_num) { /* * Used for detecting early EOF * (e.g. the last line contains only a name). */ temp.name[0] = temp.phone[0] = 0; len = read_token(temp.name, sizeof(temp.name), "\t", read_file); if (len == (size_t)-1) break; if (len == max) { fprintf(stderr, "Skipping bad line %zu\n", line_num + 1); while ((c = getchar()) != EOF && c != '\n') ; /* nothing */ continue; } len = read_token(temp.address, sizeof(temp.address), "\t", read_file); if (len == (size_t)-1) break; if (len == max) { fprintf(stderr, "Skipping bad line %zu\n", line_num + 1); while ((c = getchar()) != EOF && c != '\n') ; /* nothing */ continue; } len = read_token(temp.phone, sizeof(temp.phone), "\t", read_file); if (len == (size_t)-1) break; if (len == max) { fprintf(stderr, "Skipping bad line %zu\n", line_num + 1); while ((c = getchar()) != EOF && c != '\n') ; /* nothing */ continue; } // Do something with the input here. Example: printf("Entry %zu:\n" "\tName: %.*s\n" "\tAddress: %.*s\n" "\tPhone: %.*s\n\n", line_num + 1, (int)sizeof(temp.name), temp.name, (int)sizeof(temp.address), temp.address, (int)sizeof(temp.phone), temp.phone); } if (ferror(read_file)) { fprintf(stderr, "error reading from file\n"); exit_status = EXIT_FAILURE; } else if (feof(read_file) && temp.phone[0] == 0 && temp.name[0] != 0) { fprintf(stderr, "Unexpected end of file while reading entry %zu\n", line_num + 1); exit_status = EXIT_FAILURE; } //else feof(read_file) is still true, but we parsed a full entry/record fclose(read_file); return exit_status; } Notice how the exact same 8 lines of code appear in the read loop to handle the return value of read_token? Because of that, I think there's probably room for another function to call read_token and handle its return value, allowing main to simply call this "read_token handler", but I think the code above gives you the basic idea about how to work with read_token and how it can apply in your situation. You might change the behavior in some way, if you like, but the read_token function above would suit me rather well when working with delimited input like this (things would be a bit more complex when you add quoted fields into the mix, but not much more complex as far as I can tell). You can decide what happens with max being returned. I opted for it being considered an error, but you might think otherwise. You might even add an extra getchar when n == max and consider max being a successful return value and something like (size_t)-2 being the "token too large" error indicator instead.