I'm trying to overwrite a line in a file that contains only unsigned long numbers.
The contents of the file look like this:
1
2
3
4
5
I want to replace a specific number with the number 0. The code I wrote looks like this:
FILE *f = fopen("timestamps", "r+");
unsigned long times = 0;
int pos = 0;
while(fscanf(f, "%lu\n", ×) != EOF)
{
if(times == 3)
{
fseek(f, pos, SEEK_SET);
fprintf(f, "%lu\n", 0);
}
times = 0;
pos = ftell(f);
}
fclose(f);
f = fopen("timestamps", "r");
times = 0;
while(fscanf(f, "%lu\n", ×) != EOF)
{
printf("%lu\n", times);
times = 0;
}
fclose(f);
The output of the program looks like this:
1
2
10
5
Interestingly, if I cat the file, it looks like this:
1
2
10
5
Am I making a mistake in my ftell? Also, why didn't the printf show the missing line that the cat showed?
I could reproduce and fix.
The present problem is that when you open a file in r+ you must call fseek at each time you switch from reading to writing and from writing to reading.
Here, you correctly call fseek before writing the 0, but not after that write and the following read. The file pointer is not correctly positionned and you get undefined behaviour.
Fix is trivial, simply replace :
if(times == 3)
{
fseek(f, pos, SEEK_SET);
fprintf(f, "%lu\n", 0);
}
with
if(times == 3)
{
fseek(f, pos, SEEK_SET);
fprintf(f, "%lu\n", 0);
pos = ftell(f);
fseek(f, pos, SEEK_SET);
}
But BEWARE : it works here because you replace a line by a line of exactly same length. If you tried to replace a line containing 1000 with a line containing 0 you would get an extra line containing 0 on a windows system where end of line is \r\n and 00 on an unix like system with end of line \n.
Because here is what would happen (Windows case) :
Before rewrite :
... 1 0 0 0 \r \n ...
After :
... 0 \r \n 0 \r \n ...
because a sequential file is ... a sequential serie of byte !
The most comfortable way (in my opinion) to change text files is to create a new temporary file, copy the old one, line by line, with whatever changes you need, delete the old (or rename) and rename the temporary file.
Something like
char line[1000];
FILE *original, *temporar;
original = fopen("original", "r");
temporar = fopen("temporar", "w");
while (fgets(line, sizeof line, original)) {
processline(line);
fprintf(temporar, "%s", line);
}
fclose(temporar);
fclose(original);
unlink("original"); // or rename("original", "original.bak");
rename("temporar", "original");
Of course you need to validate all calls in real code.
Related
When going through a text file line by line I would like to be able to look ahead to the next line and inspect it while working on the current line. I am working in the C language. I believe that fseek() or other similar functions would help me with this but I'm unsure and don't know how to use them. I would want to achieve something to the effect of:
fp = fopen("test-seeking.txt", "r");
while((fgets(line, BUFMAX, fp))) {
// Peek over to next line
nextline = ...;
printf("Current line starts with: %-3.3s / Next line starts with %-3.3s\n",
line, nextline);
}
I appreciate any help.
Indeed you can use fseek and try something like this:
fp = fopen("test-seeking.txt", "r");
while ((fgets(line, BUFMAX, fp))) {
// Get the next line
fgets(nextline, BUFMAX, fp);
// Get the length of nextline
int nextline_len = strlen(nextline);
// Move the file index back to the previous line
fseek(fp, -nextline_len, SEEK_CUR); // Notice the - before nextline_len!
printf("Current line starts with: %-3.3s / Next line starts with %-3.3s\n", line, nextline);
}
Another way is to use fgetpos and fsetpos, like this:
fp = fopen("test-seeking.txt", "r");
while ((fgets(line, BUFMAX, fp))) {
// pos contains the information needed from
// the stream's position indicator to restore
// the stream to its current position.
fpos_t pos;
// Get the current position
fgetpos(fp, &pos);
// Get the next line
fgets(nextline, BUFMAX, fp);
// Restore the position
fsetpos(fp, &pos);
printf("Current line starts with: %-3.3s / Next line starts with %-3.3s\n", line, nextline);
}
The following code is inspired by #Jean-François Fabre comment. It will use a 2D character array lineBuffer which is used to hold the lines. The first read line is written to index 0 lineBuffer[0] and the second line to lineBuffer[1]. After that the writings are alternating between index 0 and 1 with the help of a toggle variable lineSel. As last step the curLine pointer will be set to nextLine.
As result you can use curLine and nextLine within the loop.
If you have a file that consists of:
line 1
line 2
line 3
...
You will work with:
curLine = "line 1\n"
nextLine = "line 2\n"
curLine = "line 2\n"
nextLine = "line 3\n"
...
See live example with stdin instead of a file on ideone.
Code:
#include <stdio.h>
#define BUFMAX 256
#define CURLINE 0
#define NEXTLINE 1
#define TOGGLELINE (CURLINE ^ NEXTLINE)
int main ()
{
FILE* fp = fopen("test-seeking.txt", "r");
char lineBuffer[2][BUFMAX];
char* curLine;
char* nextLine;
int lineSel;
if (fp != NULL)
{
if ((curLine = fgets(lineBuffer[CURLINE], BUFMAX, fp)))
{
for (lineSel = NEXTLINE;
(nextLine = fgets(lineBuffer[lineSel], BUFMAX, fp));
lineSel ^= TOGGLELINE)
{
printf("Current line: \"%s\" / Next line \"%s\"\n",
curLine, nextLine);
curLine = nextLine;
}
}
fclose(fp);
}
return 0;
}
I'm using the fopen with fread for this:
FILE *fp;
if (fopen_s(&fp, filePath, "rb"))
{
printf("Failed to open file\n");
//exit(1);
}
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
rewind(fp);
char buffer = (char)malloc(sizeof(char)*size);
if (!buffer)
{
printf("Failed to malloc\n");
//exit(1);
}
int charsTransferred = fread(buffer, 1, size, fp);
printf("charsTransferred = %d, size = %d\n", charsTransferred, strlen(buffer));
fclose(fp);
I'm not getting the file data in the new file. Here is a comparison between the original file (right) and the one that was sent over the network (left):
Any issues with my fopen calls?
EDIT: I can't do away with the null terminators, because this is a PDF. If i get rid of them the file will corrupt.
Be reassured: the way you're doing the read ensures that you're reading all the data.
you're using "rb" so even in windows you're covered against CR+LF conversions
you're computing the size all right using ftell when at the end of the file
you rewind the file
you allocate properly.
BUT you're not storing the right variable type:
char buffer = (char)malloc(sizeof(char)*size);
should be
char *buffer = malloc(size);
(that very wrong and you should correct it, but since you successfully print some data, that's not the main issue. Next time enable and read the warnings. And don't cast the return value of malloc, it's error-prone specially in your case)
Now, the displaying using printf and strlen which confuses you.
Since the file is binary, you meet a \0 somewhere, and printf prints only the start of the file. If you want to print the contents, you have to perform a loop and print each character (using charsTransferred as the limit).
That's the same for strlen which stops at the first \0 character.
The value in charsTransferred is correct.
To display the data, you could use fwrite to stdout (redirect the output or this can crash your terminal because of all the junk chars)
fwrite(buffer, 1, size, stdout);
Or loop and print only if the char is printable (I'd compare ascii codes for instance)
int charsTransferred = fread(buffer, 1, size, fp);
int i;
for (i=0;i<charsTransferred;i++)
{
char b = buffer[i];
putchar((b >= ' ') && (b < 128) ? b : "-");
if (i % 80 == 0) putchar('\n'); // optional linefeed every now and then...
}
fflush(stdout);
that code prints dashes for characters outside the standard printable ASCII-range, and the real character otherwise.
I'm trying to count the number of numbers in a text file. I have the following code:
FILE *f1;
char pathname[4096];
snprintf(pathname, 4095, "%s%d%s\n", "Key_", 2, ".txt");
if( ( f1 = fopen(pathname, "w+") ) == NULL )
perror("fopen");
for(int i = 0; i<20; ++i)
fprintf(f1, "%d\n", i+1);
int sum = 0;
int num;
while( fscanf(f1, "%d", &num) != EOF )
++sum;
printf("number of numbers: %d\n", sum);
This code says the number of numbers in the file is zero. However, if I fclose the file stream and refopen it, the sum will be 20 as expected. Any idea as to why this happens?
Thanks
The file pointer at which reads occur is shared with writes. Since w+ creates a new file or truncates an existing file, in the beginning the file is empty. As you write to the file the file pointer is moved forward and always points to the end of file. Now, when you read at that position, you will hit EOF right away.
After writing, but before reading, seek to the beginning with fseek(f1, 0, SEEK_SET);
You need to use fseek to reset the current position in the file before you read from the beginning again: fseek(f1, 0, SEEK_SET);
In the following code, I am searching for a '.' in my template to paste a string after it. For some reason, although the string is pasted as expected, it deletes some text from my template. I do not have an idea where the problem could be. Tried fflush() with no good effect.
#include <stdio.h>
#include <string.h>
int main() {
FILE * fp;
int tmp_char, tmp_offset;
char file_name[50] = "FileIoTest_template.txt";
char tmp_string1[50] = "Generic String 1";
char tmp_string2[50] = "Generic String 2";
long tmp_long;
fp = fopen(file_name, "r+");
//fseek(fp, 0, SEEK_SET);
do {
tmp_char = fgetc(fp);
printf("%c ", tmp_char);
if (tmp_char == '.')
break;
} while (tmp_char != EOF);
tmp_long = ftell(fp);
fseek(fp, tmp_long, SEEK_SET);
tmp_offset = strlen(tmp_string1);
fputs(tmp_string1, fp);
fputs("\n", fp);
//fflush(fp);
fseek(fp, tmp_long+tmp_offset, SEEK_SET);
do {
tmp_char = fgetc(fp);
printf("%c ", tmp_char);
if (tmp_char == '.')
break;
} while (tmp_char != EOF);
tmp_long = ftell(fp);
fseek(fp, tmp_long, SEEK_SET);
fputs(tmp_string2, fp);
fputs("\n", fp);
//fflush(fp);
fclose(fp);
return 0;
}
Here is my template, "FileIoTest_template.txt":
Sample list:
1.
2.
3.
random text
4.
5.
6.
bunch of random text
The output of my code is:
Sample list:
1.Generic String 1
ext
4.Generic String 2
of random text
You cannot easily modify a file by inserting data into the middle of it without replacing anything that's already there. You would need to overwrite the whole file, from the insertion point to the end (past the original end to whatever point the new end needs to be). It is tricky to do so correctly, and it is unsafe to try, for if the process is interrupted in the middle then your file is trashed.
Usually, one instead creates a new version of the file contents in a temporary file, and once that is completed successfully, one replaces the original file with the new one.
This program want to read from a file. the content in the file is the string "Hello, world". then judge each character of the string to see if the character greater than or equal to the const character 'e', if the character meet the condition, than change the character to its previous character in the alphabetical order (eg. 'b' change to 'a', 'e' change to 'd'). Finally, output the changed file content to the screen.
The question is how do the fwrite and fread work? why can't I get rid off the variable pos2 to simplify the expression. If anyone can help, thanks a lot!
#include <stdio.h>
int main()
{
FILE *fp;
char s[20];
char t[20];
char transfer;
int i;
int pos; // storing the position of the file before reading from the file
int pos1; // check the position of the file
int pos2; // storing the position of the file after reading from the file
#pragma region create a file named "Hello", write "Hello, world" into the file, close it
if ((fp = fopen("Hello", "wb") )== NULL)
{
printf("can't open file\n");
exit(0);
}
strcpy(s, "Hello, world");
fwrite(s, sizeof(char)*20, 1, fp);
fseek(fp, 0, SEEK_SET);
fclose(fp);
#pragma endregion create a file named "Hello", write "Hello, world" into the file, close it
#pragma region read from the file named "Hello", deal with its current, write the change into the file.
if ((fp = fopen("Hello", "rb+")) == NULL )
{
printf("can't open file\n");
exit(1);
}
i = 0;
while(i < 20)
{
// 提问,该处为何不能利用fwrite的自动定位免去注释掉的语句行(即使用了pos2的语句行)。
// Here is the problem. since the fread and fwrite function can move the position of the
// file, I think I can get rid off the following commented two sentences with the
// variable pos2 in it
pos = ftell(fp); // storing the position before reading from file
fread(&transfer, sizeof(char), 1, fp); // the position of the file moved to the next char
// pos2 = ftell(fp); // storing the position after reading from file
pos1 = ftell(fp);
if (transfer >= 'e') // if the character greater or equal to 'e' minus 1.
{
transfer -= 1;
}
fseek(fp, pos, SEEK_SET); // back to the position where the character is read to change the char
fwrite(&transfer, sizeof(char), 1, fp);// the position of the file moved to the next char
// fseek(fp, pos2, SEEK_SET); //
pos1 = ftell(fp);
i++;
}
fseek(fp, 0, SEEK_SET);
fclose(fp);
#pragma endregion read from the file named "Hello", deal with its current, write the change into the file.
#pragma region read from the file named "Hello", output the changed string
if((fp = fopen("Hello", "rb")) == NULL)
{
printf("Can't open file\n");
exit(2);
}
fread(t, sizeof(char)*20, 1, fp);
printf("The output is: %s \n", t);
// the right output is (the two sentences above with pos2 in it is commented) :
// The output is: Hdkkn,vnqkd
// the wrong output is (the two sentences above with pos2 in it isn't commented):
// The output is: Hddddddddddddddddddd烫烫烫烫Hello, world
fseek(fp, 0, SEEK_SET);
fclose(fp);
#pragma endregion read from the file named "Hello", output the changed string
system("pause");
}
I didn't actually get the point why you are trying to comment in/out the 2 lines. Because nothing changes whether you comment them in or comment them out. You have already got rid of pos2 in your code (which is what you are asking).
So if you use the following code for your while loop
pos = ftell(fp); // storing the position before reading from file
fread(&transfer, sizeof(char), 1, fp);
pos1 = ftell(fp);
if (transfer >= 'e') // if the character greater or equal to 'e' minus 1.
{
transfer -= 1;
}
fseek(fp, pos, SEEK_SET);
fwrite(&transfer, sizeof(char), 1, fp);
i++;
then you get "The output is: Hdkkn,vnqkd" which is the expected result.
You could also take each line from the file to an array and make operations on it then write it back to the file. By this way, it could be more generic and you don't have to use magic numbers like "20".
EDIT:
I use gcc 4.5.2 on my linux platform. I don't want to comment on other platforms but as I mentioned before you could take the line to a buffer and then after operation you could write it back. You could try to replace the following code with your while loop:
char line[20] = {0};
fread(line, sizeof(char), 20, fp);
for(i = 0; i < strlen(line); i++)
{
if(line[i] >= 'e')
line[i] -= 1;
}
fseek(fp, 0, SEEK_SET);
fwrite(line, sizeof(char), strlen(line), fp);
By this way you could get rid of many variables. It is your choice.