I'm writing a C Program that changes all uppercase characters to lowercase from a certain file. The problem is, after storing all characters (processed) in a string, and trying to print all characters inside that file, they are appended to that file, and not overwritten. I'm using "r+"
as a permission.
This is my code:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#define MAX 1024
int main(int argc, char **argv)
{
if (argc != 2) {
printf("Invalid number of arguments.\n");
exit(1);
}
FILE *f = fopen(argv[1], "r+");
if (f == NULL)
{
printf("Could not open specified file.\n");
exit(2);
}
char buf[MAX];
int len = 0;
while(!feof(f))
{
int c = fgetc(f);
if (c >= 'A' && c <= 'Z')
c = tolower(c);
if (c != EOF) {
buf[len] = c;
len++;
}
}
for (int i = 0; buf[i] != '\0'; i++)
fputc(buf[i], f);
fclose(f);
return 0;
}
Same problem with fputs(buf, f)
. Also even after appending, some "strange" characters appear at the end of the selected file.
What's the problem, exactly? How can I fix it? Thank you. Help would be appreciated.
Your program has incorrect and even undefined behavior for multiple reasons:
while(!feof(f))
does not detect end of file reliably, feof()
is set after an attempt at read from f
fails. You should instead write:
while ((c = getc(f)) != EOF) {
if (isupper(c))
c = tolower(c);
...
testing for uppercase with if (c >= 'A' && c <= 'Z')
works for ASCII but would fail for EBCDIC. You could instead use isupper(c)
or unconditionally use tolower(c)
.
reading and writing to the same file, open for read and update mode with "r+"
, requires a call to fseek()
or rewind()
to change from reading to writing and vice versa.
you do not check if len
stays within the boundaries of buf
, causing undefined behavior when attempting to write beyond the end of the array for sufficiently long input.
you do not set a null terminator at buf[len]
, hence calling fputs(buf, f)
has undefined behavior because buf
is not a proper C string. Similarly, the loop in the posted code iterates while buf[i] != '\0'
which causes undefined behavior as this null terminator has not been set at the end of the data read from the file.
Here is a modified version:
#include <ctype.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
FILE *f;
int c;
if (argc != 2) {
printf("Invalid number of arguments.\n");
return 1;
}
if ((f = fopen(argv[1], "r+")) == NULL) {
printf("Could not open file %s.\n", argv[1]);
return 2;
}
while ((c = getc(f)) != EOF) {
if (isupper(c) {
fseek(f, -1L, SEEK_CUR);
putc(tolower(c), f);
fseek(f, 0L, SEEK_CUR);
}
}
fclose(f);
return 0;
}
Note that it would be more efficient and reliable to read from the file and write to a different file. You can try this simplistic filter and compare with the above code for a large file:
#include <ctype.h>
#include <stdio.h>
int main() {
int c;
while ((c = getchar()) != EOF) {
putchar(tolower(c));
}
return 0;
}