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.

User__1
  • 21
  • 3
  • 2
    Your first question is answered with this example https://www.cplusplus.com/reference/cstdio/fread/ – Big Temp May 16 '20 at 20:55
  • 2
    And the last part of your question is answered with the functions [`fseek`](https://en.cppreference.com/w/c/io/fseek) and [`fwrite`](https://en.cppreference.com/w/c/io/fwrite). – Andreas Wenzel May 16 '20 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. – Craig Estey May 16 '20 at 20:59
  • 2
    @CraigEstey: I think the OP rather wants to open the file with `"r+"`, as `"r"` does not allow any writing. – Andreas Wenzel May 16 '20 at 21:04
  • And, if you're only changing `A` to `B` (i.e. the lengths of the strings are the same), you _can_ do this from a single file stream. If you had to do something more complex (e.g. `A` to `BZ`, where the lengths are different, you probably want _two_ streams, one to read from and one to write to, close the files, and rename the temp/output file back to the input file. – Craig Estey May 16 '20 at 21:04
  • take a look there https://stackoverflow.com/questions/3463426/in-c-how-should-i-read-a-text-file-and-print-all-strings – dt170 May 16 '20 at 21:14
  • 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 – Craig Estey May 16 '20 at 21:19
  • @CraigEstey: Yes, I agree that in this situation, [`fgetc`](https://en.cppreference.com/w/c/io/fgetc) and [`fputc`](https://en.cppreference.com/w/c/io/fputc) would probably be best. – Andreas Wenzel May 16 '20 at 21:25
  • 1
    OT: 1) Please consistently indent the code. Indent after every opening brace '{'. Unindent before every closing brace '}'. Suggest each indent level be 4 spaces. 2) do not insert 'random' blank line. Rather suggest: separating code blocks: `for` `if` `else` `while` `do...while` `switch` `case` `default` via a single blank line. 3) separate functions by 2 or 3 blank lines (be consistent). – user3629249 May 16 '20 at 21:37

3 Answers3

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;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • 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 May 16 '20 at 21:31
  • @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 May 16 '20 at 21:33
  • @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 May 16 '20 at 21:37
  • @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. – Craig Estey May 16 '20 at 22:21
  • 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. – G. Sliepen May 17 '20 at 07:37
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;
}
user3629249
  • 16,402
  • 1
  • 16
  • 17
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
Hitokiri
  • 3,607
  • 1
  • 9
  • 29
  • 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 May 17 '20 at 03:18
  • @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." – Hitokiri May 17 '20 at 08:38
  • Yes, It does seem useless, but here is the quote from https://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.` – isrnick May 17 '20 at 14:44