2

I am trying to write a program to learn to manipulate text files using C, every time my program detects the letter "A", it should substitute it to "B", here is what I have as source code so far:

    int main(){


Function();
return 0;
}

Function(){

    FILE * pFile = fopen("example.txt", "w+");



}

I have opened the file, but don't know how am I going to take what is inside of the file, to a variable, so that I can pass it to some "if statement" to change the letters "A" to "B", and also how could I write the result of this statement back to the file without. corrupting.

9
  • 2
    Your first question is answered with this example cplusplus.com/reference/cstdio/fread Commented May 16, 2020 at 20:55
  • 2
    And the last part of your question is answered with the functions fseek and fwrite. Commented May 16, 2020 at 20:58
  • 1
    w+ will truncate the file and then open it for reading and writing (or more correctly, writing and reading). You probably want: r to read from an existing file. Commented May 16, 2020 at 20:59
  • 2
    @CraigEstey: I think the OP rather wants to open the file with "r+", as "r" does not allow any writing. Commented May 16, 2020 at 21:04
  • 1
    @AndreasWenzel Yes, probably, for the limited case here. I suppose use of fopen et. al. is required [for this exercise]. But, I'd do mmap of the file, and loop on strchr. But, here, perhaps, fgetc and fputc are fastest Commented May 16, 2020 at 21:19

3 Answers 3

1

if you read the file, one char at a time, for instance using fgetc() then you could check if the character is a A and if it is an A then call fseek( file, -1, SEEK_CUR ); followed by fputc( 'B', file );

The following proposed code:

  1. cleanly compiles
  2. performs the desired functionality
  3. is not the fasted possible implementation, however, to keep the code simple, I used the slower fgetc() and fputc() rather than fread() and fwrite()
  4. properly checks for errors when calling fopen() and fgetc() but skipped error checking for fseek() and fputc(). You can easily add the skipped error checking. Read the MAN pages for those functions for details

and now, the proposed code:

#include <stdio.h>
#include <stdlib.h>

void Function( void )
{
    FILE * pFile = fopen("example.txt", "r+");
    if( ! pFile )
    {
        perror( "fopen failed" );
        exit( EXIT_FAILURE );
    }

    int ch;
    while(  (ch = fgetc( pFile )) != EOF )
    {
        if( ch == 'A' )
        {
            fseek( pFile, -1, SEEK_CUR );
            fputc( 'B', pFile );
        }

        //else
        //{
        //    fputc( pFile, ch );
        //}
    }
    fclose( pFile );
}


int main( void )
{
    Function();
    return 0;
}
Sign up to request clarification or add additional context in comments.

2 Comments

The fputc(pFile,ch) [in the else] is not required and forces the entire file to be rewritten instead of just the small segments that have an A.
Ok, I'll fix that.
1

Maybe my code can help you. (the explication in the code)

#include <stdio.h>
#include <stdlib.h>

void Function()
{
    FILE* fp = fopen("input.txt", "r+"); // Open file to read and write
    if (fp == NULL) return; // exit if you can not open the file
    char ch;
    while ((ch = fgetc(fp)) != EOF) // read character by character until end of the file
    {                                             
        if (ch == 'A') // if we meet A character
        {
            fseek(fp, - 1, SEEK_CUR); // set the position of the stream one character back
            fprintf(fp, "%c", 'B'); // write B character to position of A character
        }
    }
    fclose(fp);
}

int main() {
   Funtion();
   return 0;
}

The input and output:

$cat input.txt
AAA BBB AAA BBAA

The file after replacing:

BBB BBB BBB BBBB

3 Comments

I believe there should be a fseek(fp, 0, SEEK_CUR); after the fprintf. The C specification says there must always be a call to a positioning function or to fflush between a write operation and a read operation to a file that is open in update mode (+),.
@isrnick sorry, i do not get your idea. Because fseek(fp, 0, SEEK_CUR); makes me think: " I'm here, I don't move, so i will be still here."
Yes, It does seem useless, but here is the quote from en.cppreference.com/w/c/io/fopen : In update mode ('+'), both input and output may be performed, but output cannot be followed by input without an intervening call to fflush, fseek, fsetpos or rewind, and input cannot be followed by output without an intervening call to fseek, fsetpos or rewind, unless the input operation encountered end of file.
1

As mentioned above [by Andreas], you probably want r+ as the open mode.

Although you can use fread [and fwrite] with fseek as suggested, I think fgetc and fputc would be simpler.

#include <stdio.h>

int
main(void)
{
    long pos;
    int chr;

    FILE *pFile = fopen("example.txt", "r+");

    while (1) {
        chr = fgetc(pFile);
        if (chr == EOF)
            break;

        if (chr == 'A') {
            fseek(pFile,-1,SEEK_CUR);
            fputc('B',pFile);
        }
    }

    fclose(pFile);

    return 0;
}

UPDATE:

Others have suggested using fread, scanning the block, doing fseek with a rewrite of the block using fwrite.

For that, I'd just use open/read/write/lseek/close or pread/pwrite.

But, if we're going to go to that much trouble, as I mentioned in my top comments, I'd use mmap. It's still pretty simple, and it is much faster than any of the aforementioned methods.

Anyway, here's the code:

#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>

int
main(void)
{
    int fd;
    struct stat st;
    ssize_t remlen;
    char *base;
    char *cur;
    char *end;

    fd = open("example.txt",O_RDWR);
    fstat(fd,&st);

    base = mmap(NULL,st.st_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);

    remlen = st.st_size;
    cur = base;
    end = &cur[remlen];

    for (;  remlen > 0;  remlen = end - cur) {
        cur = memchr(cur,'A',remlen);

        if (cur == NULL)
            break;

        *cur++ = 'B';
    }

    munmap(base,st.st_size);
    close(fd);

    return 0;
}

5 Comments

Reading and writing one character at a time is really quite inefficient. I would rather use fread() and fwrite() to try to process blocks of say 4 kilobytes at a time. You fread() into the buffer, replace all 'A's with 'B's, and if you did any substitutions, fseek() the size of one block back and fwrite() the new contents of the block.
@G.Sliepen, This would be VERY problematic for the last block, especially if that that block was less than what ever 'block size' that the code is using.
@user3629249 This is not a problem at all, just do size_t num_read = fread(buf, 1, sizeof buf, pFile) and num_read will be the number of bytes actually read. If the last block is smaller than the buffer size, the return value will just be the remaining number of bytes.
@G.Sliepen Given the added complexity, if the simple fgetc/fputc loop is too slow, as I mentioned in my top comments, I'd use mmap because it's much faster than any other method for this. I've added an example for it.
Good mmap() example. One reason to go for fread()/fwrite() though is that it works in a pure C89 environment, whereas mmap() is platform specific. Also, in this linear access scenario, mmap() probably isn't that much faster than fread()/fwrite() with a properly chosen block size. You are missing error checking though, mmap() might fail. Especially on 32-bit systems, you can't use it to map very large files in one go.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.