Is using getline() in a while loop bad practice? - c

If I have a file which contains a '\n' on virtually every line, and I use getline() in a while loop to read the content, is this bad practice?
My understanding is that getline() will be called numerous times for each '\n' char reached, and so realloc() will be called each time also. Which seems inefficient?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
perror("Unable to open file: ");
exit(1);
}
char *buffer = NULL;
size_t len = 0;
ssize_t characters;
while ((characters = getline(&buffer, &len, fp)) != -1) {
fputs(buffer, stdout);
}
fclose(fp);
free(buffer);
buffer = NULL;
return 0;
}

There is no problem calling getline in a loop as you do. As a matter of fact, this exactly the intended use case and used in the example in the man page.
realloc is only called if the array allocated so far is too short for the current line, thus it will be called very little and you can even lower the number of calls by allocating an initial array and set its length in the len variable.
Since getline returns the number of characters read, you could write the line using fwrite instead of fputs. Note however that the behavior is subtly different: fwrite will output lines read from the file containing embedded null bytes, whereas fputs would stop on the first null byte. This is usually not an issue as text files usually do not contain null bytes.
Here is a modified version:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
fprintf(stderr, "Unable to open file %s: %s\n",
"file.txt", strerror(errno));
return 1;
}
char *buffer = NULL;
size_t len = 0;
ssize_t characters;
#if 1
// optional code to show how to start with a preallocated buffer
if ((buffer = malloc(256)) != NULL)
len = 256;
#endif
while ((characters = getline(&buffer, &len, fp)) != -1) {
fwrite(buffer, 1, characters, stdout);
}
fclose(fp);
free(buffer);
return 0;
}

Related

opening and closing a text file many times and read from the continuation

I have a text file, and I open it and read one line of it, and close the text file. I'm calling my function under a for loop, but each time this function reads the first line of a text file, how can I fix it to read from the continuation
You can use fseek to reposition yourself in the file after closing and reopening, but it is very unusual to do so. So unusual, in fact, that I would suggest it is completely wrong. Here's some sample code that demonstrates how to do that, as well as a more typical loop. Each loop here reads the first 2 lines of the file, assuming each line is sufficiently small; handling long lines is beyond the scope of this question.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE * xfopen(const char *path, const char *mode);
void xfseek(FILE *stream, long offset, int whence, const char *);
long xftell(FILE *stream, const char *);
void xfclose(FILE *stream, const char *);
int
main(int argc, char *argv[])
{
const char *path = argc > 1 ? argv[1] : "input";
/* Read the first two lines of the file, closing the file on each
* iteration. This is ** not ** the usual way to do this, and is
* included here for demonstration
* purposes only. DO NOT DO THIS.
* It is very unusual to close and re-open the file on each iteration.
*/
long position = 0;
for( int line = 1; line < 3; line++ ){
FILE *ifp = xfopen(path, "r");
char buf[1024];
xfseek(ifp, position, SEEK_SET, path);
fgets(buf, sizeof buf, ifp); /* (1) */
printf("line %d: %s", line, buf);
position = xftell(ifp, path);
xfclose(ifp, path); /* !! */
}
/* The more usual way to read each line of a file is to simply
* read it with repeated calls to the appropriate read method
* (fgets, fread, fgetc, etc.) Each subsequent read starts
* where the previous read finished.
*/
FILE *ifp = xfopen(path, "r");
for( int line = 1; line < 3; line++ ){
char buf[1024];
fgets(buf, sizeof buf, ifp); /* (1) */
printf("line %d: %s", line, buf);
}
xfclose(ifp, path);
return 0;
}
FILE *
xfopen(const char *path, const char *mode)
{
FILE *fp = path[0] != '-' || path[1] != '\0' ? fopen(path, mode) :
*mode == 'r' ? stdin : stdout;
if( fp == NULL ){
perror(path);
exit(EXIT_FAILURE);
}
return fp;
}
void
xfseek(FILE *stream, long offset, int whence, const char *name)
{
if( fseek(stream, offset, whence) == -1){
perror(name);
exit(EXIT_FAILURE);
}
}
long
xftell(FILE *stream, const char *name)
{
long ret = ftell(stream);
if( ret == -1 ){
perror(name);
exit(EXIT_FAILURE);
}
return ret;
}
void
xfclose(FILE *stream, const char *name)
{
if( fclose(stream) ){
perror(name);
exit(EXIT_FAILURE);
}
}
Notes: (1) It is left as an exercise for the reader how best to handle short reads (eg, when fgets returns NULL) or long lines (eg, when fgets completely fills the buffer but fails to read an entire line). Perhaps it is a bit of a cop-out to leave that as an exercise, but the annoyance of dealing with those issues points strongly towards reasons for using the standard idiom. If you want to print the first two lines of the file, use some variation of for( int count = 0; (c = fgetc(fp)) != NULL && count < 2; ) { if( c == '\n' ) count += 1; putchar(c); }. Putting the read function as a condition of the loop is (almost) always the best choice.
The comments have already made suggestions on other alternatives for what you are attempting. But regardless whether it is the right approach or not, it seems pretty clear that your stated ask is clear about wanting to use fseek() et. al to view successive lines when opening and closing a file.
To open and close a file, and each time access and display a successive line, you must first know where each of the locations to be viewed are located within that file. Indeed, as you have tagged, fseek(), (as well as ftell()) can be used to do this. The following pseudo code steps illustrate one possibility:
//store file pointer locations of each line in file:
FILE *fp = fopen(fn, "r");
if(fp)
{
for(int i = 0; i < l_cnt; i++)
{
pos[i] = ftell(fp);
fgets(line, sizeof line, fp);
}
}
fclose(fp);
Then...
//alternately open and close file to view successive lines at stored positions
for(int i = 0; i < line_cnt; i++)
{
FILE *fp = fopen(fn, "r");
if(fp)
{
fseek(fp, pos[i], 0);
fgets(line, sizeof line, fp);
printf("line %d: %s\n", i, line);
fclose(fp);
}
}
There is a more complete source and run-time example here

Using fgets to read through file in C

I am trying to read through the file given then tokenize it. The only problem im having is fgets.The file open recieves no errors. I have seen this elsewhere on the site however no matter how i set this up including setting fileLine to a set amount like (char fileline [200]) i get a segmentation fault. Thanks in advance for any help.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
char *fileName = "0";
char *tokenize, *savePtr;
struct Record *database= malloc(sizeof(database[0]));
int recordNum =0;
char *fileLine = malloc(sizeof(char *));//have replaced with fileline[200] still didnt work
FILE *fd = open(fileName,O_RDWR);
if(fd< 0){
perror("ERROR OPENING FILE");
}
while(fgets(fileLine,200,fd) !=NULL){
printf("%s\n", fileLine);
tokenize = strtok_r(fileLine,",",&savePtr);
while(tokenize != NULL){
//TOKENIZING into a struct
}
}
Why use open() with FILE? Use fopen() instead.
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *fileName = "test.txt";
char *tokenize, *savePtr;
char fileLine[200] = {0}; // init this to be NULL terminated
FILE *fd = fopen(fileName, "r");
if (fd == 0) { // error check, equal to 0 as iharob said, not less than 0
perror("ERROR OPENING FILE");
return -1;
}
while (fgets(fileLine, 200, fd) != NULL) {
printf("%s\n", fileLine);
tokenize = strtok_r(fileLine, ",", &savePtr);
while (tokenize != NULL) {
tokenize = strtok_r(NULL, ",", &savePtr); // do not forget to pass NULL
//TOKENIZING into a struct
}
}
return 0;
}
As Weather Vane said, fd < 0 would work if you used open(). However, with fopen(), you should check to see if the pointer is NULL, ecquivalently fd == 0.
A comparison between this functions that open a file can be found in:
open and fopen function
C fopen vs open
The way I have it in mind is that fopen() is of higher level.
This line
char *fileLine = malloc(sizeof(char *));
allocates memory for a char * type, 4 or 8 bytes (depending on the platform).
So when you do
fgets(fileLine,200,fd)
it expects there to be 200 bytes of memory available.
Try this:
char *fileLine = malloc(200);
if (fileLine == NULL) { ... } // check for error
which will allocate the memory required.
You are using open() instead of fopen().
You can't be sure that the file did open correctly because fopen() does not return an integer, but a pointer to a FILE * object, on failure it returns NULL, so the right codition is
FILE *file;
file = fopen(filename, "r");
if (file == NULL)
{
perror("fopen()");
return -1;
}
In your code, you still go and use fgets() even when the fopen() fails, you should abort the program in that case.
Also, malloc() takes the number of bytes as the size parameter, so if you want fgets() to be limited to read just count bytes, then malloc() should be
char *buffer;
size_t count;
count = 200; /* or a value obtained someway */
buffer = malloc(count);
if (buffer == NULL)
{
fclose(file);
perror("malloc()");
return -1;
}
All the problems in your code would be pointed out by the compiler if you enable compilation warnings.

segfaulting with fgets, even though fopen doesn't return NULL in C

I've looked all over and made sure there were no warnings, but my code to replace text with digits keeps returning segfault. Any help?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc , char *argv[])
{
FILE *file;
file = fopen(argv[1] , "r");
char *line = malloc(1024);
if(file != NULL)
{
while(fgets(line , sizeof(line) , file))
{
//things
}
}
else
{
printf("ERROR: %s NOT AVAILABLE" , argv[1]);
}
return 0;
}
Replace:
char *line = malloc(1024);
with:
char line[1024] = {0};
or:
char line[1024];
if you don't want to clear out the line buffer.
Otherwise, you end up with two problems.
First:
sizeof(line)
returns the size of the pointer (4 or 8 bytes). That's not what you want.
Second: You have a memory leak because you don't free the line pointer at the end.
You can use malloc if you want, but you want to write clean(er) code to do this. You might do something like:
#define MAX_LINE_LENGTH 1024
/* ... */
char *line = NULL;
line = malloc(MAX_LINE_LENGTH);
if (!line) {
fprintf(stderr, "Error: Could not allocate space for line buffer!\n");
exit(EXIT_FAILURE);
}
FILE *file = NULL;
/* Avoid undefined behavior by making sure filename argument holds a value */
if (argv[1])
file = fopen(argv[1] , "r");
if (file != NULL) { /* You could also do "if (file) { ... }" */
while (fgets(line, MAX_LINE_LENGTH, file)) {
/* ... */
}
}
free(line);
line = NULL;
As a habit, explicitly initialize pointers to NULL, and check that they actually hold a value before using them. Welcome to C!

Why wont the program read from the 2 argument file?

So the assignment is to implement a substring search program using an input file to be searched from and an input to be searched. I created the following code:
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
FILE *fp;
fp = fopen(argv[1],"r");
if (fp == NULL)
{
printf("Error");
return 0;
}
char* tmpp[100];
int count = 0;
char* nexts = argv[2];
char* tmp = fgets(tmpp,100,fp);
while(tmp = strstr(tmp,nexts))
{
count++;
tmp++;
}
printf("%d\n\n",count);
fclose(fp);
return 0;
}
The program compiles but when i go to implement it in the ubuntu terminal as:
echo "aabb" >beta
./a.out beta a
1
Why isnt the program using the first argument (argv[1]) as beta and the second argument (argv[2]) as a correctly?
You should open a file and then read bytes from that file into temporary buffer:
FILE *file = fopen("file", "r");
while (1) {
char buffer[BUFSIZ+1];
size_t nread = fread(buffer, 1, sizeof(buffer)-1, file);
if (nread == 0) break; // read error or EOF
buffer[nread] = 0;
// chunk with BUFSIZ amount of bytes is available via buffer (and is zero-terminated)
}
If you want to search for string/pattern in a file, be aware that looked pattern in file may cross your chunk-size boundary, for example: you look for "hello", and BUFSIZ is 512. File contains "hello" at byte 510. Obviously, if you read by 512, you will get the first chunk ending with "he", and the second chunk starting with "llo". Probability of this situation is nonzero for all chunk sizes (except SIZE_MAX, but that buffer size is impossible by other reasons). Dealing with borders may be very complicated.
Close...but this is closer:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc != 3)
{
fprintf(stderr, "Usage: %s file pattern\n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL)
{
fprintf(stderr, "Error: failed to open file %s for reading\n", argv[1]);
return 1;
}
char tmpp[1000];
int count = 0;
char* nexts = argv[2];
while (fgets(tmpp, sizeof(tmpp), fp) != 0)
{
char *tmp = tmpp;
while ((tmp = strstr(tmp, nexts)) != 0)
{
count++;
tmp++;
}
}
printf("%d\n", count);
fclose(fp);
return 0;
}
The main difference is that this loops reading multiple lines from the input file. Yours would only work on files with a single line of input.

Reading a text file in C

I am reading a text file and trying to display its contents on the console. Here is my code:
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <fstream>
int main()
{
FILE* fp=NULL;
char buff[100];
fp=fopen("myfile.txt","r");
if(fp==NULL)
{
printf("Couldn't Open the File!!!\n");
}
fseek(fp, 0, SEEK_END);
size_t file_size = ftell(fp);
fread(buff,file_size,1,fp);
printf("Data Read [%s]",buff);
fclose(fp);
return 0;
}
but only redundant data is being displayed on the console; could someone please point out my mistake?
You forgot to reset the file pointer to start after doing this.
fseek(fp, 0, SEEK_END);
Do this after finding size (file_size).
rewind (fp);
You need to seek back to the start of the file before reading:
int main()
{
FILE* fp=NULL;
char buff[100];
fp=fopen("myfile.txt","r");
if(fp==NULL)
{
printf("Couldn't Open the File!!!\n");
exit(1); // <<< handle fopen failure
}
fseek(fp, 0, SEEK_END);
size_t file_size = ftell(fp);
fseek(fp, 0, SEEK_SET); // <<< seek to start of file
fread(buff,file_size,1,fp);
printf("Data Read [%s]",buff);
fclose(fp);
return 0;
}
Try it....
#include <stdio.h>
#include <stdlib.h>
void handle_line(char *line) {
printf("%s", line);
}
int main(int argc, char *argv[]) {
int size = 1024, pos;
int c;
char *buffer = (char *)malloc(size);
FILE *f = fopen("myfile.txt", "r");
if(f) {
do { // read all lines in file
pos = 0;
do{ // read one line
c = fgetc(f);
if(c != EOF) buffer[pos++] = (char)c;
if(pos >= size - 1) { // increase buffer length - leave room for 0
size *=2;
buffer = (char*)realloc(buffer, size);
}
}while(c != EOF && c != '\n');
buffer[pos] = 0;
// line is now in buffer
handle_line(buffer);
} while(c != EOF);
fclose(f);
}
free(buffer);
return 0;
}
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <fstream>
int main()
{
FILE* fp=NULL;
char *buff; //change array to pointer
fp=fopen("myfile.txt","r");
if(fp==NULL)
{
printf("Couldn't Open the File!!!\n");
}
fseek(fp, 0, SEEK_END);
size_t file_size = ftell(fp);
buff = malloc(file_size); //allocating memory needed for reading file data
fseek(fp,0,SEEK_SET); //changing fp to point start of file data
fread(buff,file_size,1,fp);
printf("Data Read [%s]",buff);
fclose(fp);
return 0;
}
having a buffer of 100 bytes to read a file is not a better idea as since the file size may be more than 100 bytes.
A better file io can be done by doing a fgets on the file, if its not a type of metadata that you wanted to read using the fread.
fgets in a while loop can be used to check whether its reached EOF or a feof call can be used to check the EOF.
a sample code listing of fgets can be like this:
while (fgets(buf, len, fp)) {
printf("%s", buf);
}
or a sample that is used with fgets can be like this:
while (fread(buf, len, 1, fp) >= 0) {
printf("%s\n", buf);
}

Resources