So I am trying to make a function print_file_rows() that prints the first ten rows of a file. If the file has more than 10 rows it works perfectly fine but if there's 10 or less it starts printing garbage. Any ideas on how I can fix this? (MUST ONLY USE THE SYSTEM FUNCTIONS OPEN/READ/WRITE/CLOSE)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
void print_file_rows(char *path)
{
int fd = open(path, O_RDONLY);
if (fd < 0)
{
return NULL;
}
size_t size = 100;
size_t offset = 0;
size_t res;
char *buff = malloc(size);
while((res = read(fd, buff offset, 100)) != 0)
{
offset = res;
if (offset 100 > size)
{
size *= 2;
buff = realloc(buff, size);
}
}
close(fd);
int j = 0;
for(int i = 0;buff[i] != '\0'; i )
{
if(j == 10)
{
break;
}
if(buff[i] == '\n')
{
j ;
}
printf("%c", buff[i]);
}
free(buff);
}
int main()
{
print_file_rows("a.txt");
return 0;
}
CodePudding user response:
You do not need any buffers. It is most likely buffered on the OS level so you may print char by char.
int print_file_rows(char *path, size_t nrows)
{
int result = -1;
int fd = open(path, O_RDONLY);
char c;
if (fd > 0)
{
while(nrows && read(fd, &c, 1) == 1)
{
write(STDOUT_FILENO, &c, 1);
if(c == `\n`) nrows--;
}
result = nrows;
}
close(fd);
return result;
}
int main()
{
if(print_file_rows("a.txt", 10) == -1)
printf("Something has gone wrong\n");
return 0;
}
CodePudding user response:
From man 2 read
:
SYNOPSIS
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
DESCRIPTION read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
read
is for reading raw bytes, and as such has no notion of strings. It does not place a NUL terminating byte ('\0'
) at the end of the buffer. If you are going to treat the data you read as a string, you must terminate the buffer yourself.
To make room for this NUL terminating byte you should always allocate one extra byte in your buffer (i.e., read one less byte that your maximum).
We can see the return value is actually of type ssize_t
, rather than size_t
, which allows for
On error, -1 is returned, and errno is set to indicate the error.
This means we will need to check that the return value is greater than zero, rather than not zero which would cause the offset to be decremented on error.
With all that said, note that this answer from a similar question posted just yesterday shows how to achieve this without the use of a dynamic buffer. You can simply read the file one byte at a time and stop reading when you've encountered 10 newline characters.
If you do want to understand how to read a file into a dynamic buffer, then here is an example using the calculated offset
to NUL terminate the buffer as it grows. Note that reading the entire file this way is inefficient for this task (especially for a large file).
(Note: the call to write
, instead of printf
)
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
void print_file_rows(const char *path)
{
int fd = open(path, O_RDONLY);
const size_t read_size = 100;
size_t size = read_size;
size_t offset = 0;
ssize_t res;
char *buff = malloc(size 1);
while ((res = read(fd, buff offset, read_size)) > 0) {
offset = res;
buff[offset] = '\0';
if (offset read_size > size) {
size *= 2;
buff = realloc(buff, size 1);
}
}
close(fd);
int lines = 0;
for (size_t i = 0; lines < 10 && buff[i] != '\0'; i ) {
write(STDOUT_FILENO, &buff[i], 1);
if (buff[i] == '\n')
lines ;
}
free(buff);
}
int main(void)
{
print_file_rows("a.txt");
}
(Error handling omitted for code brevity. malloc
, realloc
, and open
can all fail, and should normally be handled.)