Eliminar parte de un archivo en C

9 minutos de lectura

¿Cómo puedo crear una función para eliminar cierta parte de un archivo? Por ejemplo, el archivo es:

-.
Presidente: A23
Número: 123
Nombre: Josué
-.
Presidente: B12
Número: 512
Nombre: Marco
-.
Presidente: C2
Numero 1
Nombre: Drake

Si la entrada es

B12

Entonces el archivo se convertirá

-.
Presidente: A23
Número: 123
Nombre: Josué
-.
Presidente: C2
Numero 1
Nombre: Drake

Necesito esta función para que mi programa funcione, pero no tengo idea de cómo puedo hacerlo.

Abra un nuevo archivo en el mismo directorio que el archivo original. Escriba en ese archivo lo que desea reemplazar el contenido del archivo original. Cierra el nuevo archivo. Cambie el nombre del nuevo archivo encima del archivo original.

Pero tal vez debería considerar usar una base de datos en lugar de un archivo de texto.

  • Pero, ¿cómo puedo hacer eso? Necesito encontrar lo que sobrescribiré, y sería identificado por B12. Entonces, el “cursor” estará después de B12 y solo podré sobrescribir lo que viene a continuación.

    –Rafael Santos

    22 de junio de 2018 a las 23:43

  • Puedes hacerlo de la forma que quieras. Puede leer entradas completas y luego decidir si desea escribirlas. Puede truncar el archivo si escribió demasiado. Puedes hacerlo como quieras.

    –David Schwartz

    22 de junio de 2018 a las 23:48

  • La cosa es que realmente necesito usar un archivo de texto xD

    –Rafael Santos

    22 de junio de 2018 a las 23:56

  • Si el archivo es pequeño, ¿por qué no simplemente leer todo en una base de datos en memoria? Luego puede modificar la base de datos en la memoria todo lo que quiera. Cuando haya terminado, escríbalo en un archivo y cambie el nombre de ese nuevo archivo encima del archivo existente.

    –David Schwartz

    23 de junio de 2018 a las 0:08

Primero podría leer el contenido del archivo en un búfer. Luego, podría analizar e insertar datos de este búfer en alguna estructura de datos, como una matriz de estructuras. Una vez que tenga eso, puede volver a escribir los contenidos filtrados en el archivo.

A continuación se muestra un código de muestra (modificado) que escribí hace un tiempo que hace algo similar a lo que desea. Toma 2 argumentos de línea de comando como entrada. El primero es el archivo para leer, y el segundo son los datos para no incluir, en su caso el valor de la silla. Puedes modificarlo para leer desde stdin Si quieres.

Código:

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

#define START_SIZE 10;
#define BASE 10

typedef struct {
    char *chair;
    int number;
    char *name;
} data_t;

typedef struct {
    data_t *data;
    size_t n;
} file_data_t;

char *get_file_contents(const char *);
file_data_t *insert_data(char *, const char *);

int main(int argc, char *argv[]) {

    // Check arguments
    if (argc != 3) {
        fprintf(stderr, "Usage: ./deletefile [file] [chair]\n");
        exit(EXIT_FAILURE);
    }

    // Get buffer
    char *buffer = get_file_contents(argv[1]);

    // Get structure holding data
    file_data_t *file_data = insert_data(buffer, argv[2]);

    // Free buffer
    free(buffer);

    // Open file to write to
    FILE *fp = fopen(argv[1], "w");
    if (fp == NULL) {
        fprintf(stderr, "Count not open file\n");
        exit(EXIT_FAILURE);
    }

    // Write to file, and free contents
    for (size_t i = 0; i < file_data->n; i++) {
        fprintf(fp, "-.\nChair: %s\nNumber: %d\nName: %s\n",
                    file_data->data[i].chair,
                    file_data->data[i].number,
                    file_data->data[i].name);

        free(file_data->data[i].chair);
        free(file_data->data[i].name);
    }

    // Free everything else
    free(file_data->data);
    free(file_data);

    fclose(fp);

    return EXIT_SUCCESS;
}

file_data_t *insert_data(char *buffer, const char *dont_keep) {
    size_t buffsize = START_SIZE;
    const char *delim_section = "-.", *delim_line = "\n";
    const char delim_colon = ':';
    char *token = NULL;
    char *rest = buffer;
    size_t count = 0;

    // Create main structure
    file_data_t *file_data = malloc(sizeof *file_data);
    if (file_data == NULL) {
        fprintf(stderr, "Could not allocate file data\n");
        exit(EXIT_FAILURE);
    }

    // Allocate data elements
    file_data->data = malloc(buffsize * sizeof *file_data->data);
    if (file_data->data == NULL) {
        fprintf(stderr, "Could not allocate %zu bytes for data\n", buffsize);
        exit(EXIT_FAILURE);
    }

    while ((token = strtok_r(rest, delim_section, &rest)) != NULL) {

        // Reallocate data if necessary
        if (count == buffsize) {
            buffsize *= 2;
            void *ptr = realloc(file_data->data, buffsize * sizeof *file_data->data);
            if (ptr == NULL) {
                fprintf(stderr, "Could not reallocate %zu bytes for buffer\n", buffsize);
                exit(EXIT_FAILURE);
            }

            file_data->data = ptr;
        }

        char *saveptr = NULL, *endptr = NULL;

        // Parse chair
        char *chair = strtok_r(token, delim_line, &saveptr);
        char *chair_value = strchr(chair, delim_colon);
        chair_value += 2;

        // If chair value is not the same as dont_keep, proceed
        if (strcmp(chair_value, dont_keep) != 0) {

            // Copy chair value over
            file_data->data[count].chair = strdup(chair_value);
            if (file_data->data[count].chair == NULL) {
                fprintf(stderr, "Could not copy chair buffer\n");
                exit(EXIT_FAILURE);
            }

            // Parse number
            char *number = strtok_r(NULL, delim_line, &saveptr);
            char *number_value = strchr(number, delim_colon);
            number_value += 2;

            // Convert number to integer
            long val = strtol(number_value, &endptr, BASE);

            // Didnt find a value number
            if (endptr == number_value || *endptr  != '\0') {
                fprintf(stderr, "Count not parse number\n");
                exit(EXIT_FAILURE);
            }

            // Add number value
            file_data->data[count].number = val;

            // Parse name
            char *name = strtok_r(NULL, delim_line, &saveptr);
            char *name_value = strchr(name, delim_colon);
            name_value += 2;

            // Copy name over
            file_data->data[count].name = strdup(name_value);
            if (file_data->data[count].name == NULL) {
                fprintf(stderr, "Coul not copy name buffer\n");
                exit(EXIT_FAILURE);
            }

            // Increment count
            count++;
        }
    }

    file_data->n = count;

    return file_data;
}

char *get_file_contents(const char *path) {

    // Open file
    FILE *fp = fopen(path, "r");
    if (fp == NULL) {
        fprintf(stderr, "Failed to open %s\n", path);
        exit(EXIT_FAILURE);
    }

    // Go to end of file
    int end = fseek(fp, 0L, SEEK_END);
    if (end != 0) {
        fprintf(stderr, "Could not go to end of file\n");
        exit(EXIT_FAILURE);
    }

    // Get size of file
    long buffsize = ftell(fp);
    if (buffsize == -1) {
        fprintf(stderr, "Count not get size of file\n");
        exit(EXIT_FAILURE);
    }

    // Allocate buffer
    char *buffer = malloc(buffsize + 1);
    if (buffer == NULL) {
        fprintf(stderr, "Could not allocate %ld bytes for buffer\n", buffsize);
        exit(EXIT_FAILURE);
    }

    // Go back to start of file
    int start = fseek(fp, 0L, SEEK_SET);
    if (start != 0) {
        fprintf(stderr, "Could not go to start of file\n");
        exit(EXIT_FAILURE);
    }

    // Read contents of file
    size_t newlen = fread(buffer, 1, buffsize, fp);
    if (ferror(fp) != 0) {
        fprintf(stderr, "Error reading contents of file into buffer\n");
        exit(EXIT_FAILURE);
    }

    fclose(fp);

    // Null terminate buffer
    buffer[newlen++] = '\0';

    return buffer;
}

Producción:

$ cat file.txt
-.
Chair: A23
Number: 123
Name: Joshua
-.
Chair: B12
Number: 512
Name: Marcus
-.
Chair: C2
Number: 1
Name: Drake
$ gcc -Wall -Wextra -o deletefile deletefile.c
$ ./deletefile file.txt B12
$ cat file.txt
-.
Chair: A23
Number: 123
Name: Joshua
-.
Chair: C2
Number: 1
Name: Drake

Nota: El código anterior no es la mejor manera de realizar esta tarea y ciertamente se puede mejorar. Puede usar esto como base y mejorarlo.

avatar de usuario
jonathan leffler

Tomando la pregunta al pie de la letra, considere usar una combinación de:

  1. Lo contrario de Escribir en medio de un archivo binario sin sobrescribir ningún contenido existente
  2. ¿Cómo truncar un archivo en C?

Usaría lo contrario del paso 1 para copiar el material desde el final del archivo (después de la parte que se eliminará) sobre la parte que se eliminará, y luego usaría el paso 2 para establecer el tamaño del archivo en el nuevo valor.

O, probablemente más simple, copie el material antes y después de la parte que se eliminará en un archivo nuevo y luego mueva el (contenido del) archivo nuevo en lugar del anterior.


Código relevante:

#include "posixver.h"
#include <sys/stat.h>
#include <unistd.h>

#if !defined(BUFFERSIZE)
#if defined(DO_NOT_TEST)
enum { BUFFERSIZE = 64 * 1024 };
#else
enum { BUFFERSIZE = 4 };
#endif /* DO_NOT_TEST */
#endif /* !BUFFERSIZE */

static inline size_t min_size(size_t x, size_t y) { return (x < y) ? x : y; }

static int shrink_file_and_delete(int fd, size_t offset, size_t dellen)
{
    char buffer[BUFFERSIZE];
    struct stat sb;
    int rc = -1;

    if (fstat(fd, &sb) == 0)
    {
        size_t file_size = sb.st_size;   /* off_t to size_t conversion */
        if (file_size > offset && dellen > 0)
        {
            /* Move data after offset + dellen bytes down by dellen bytes */
            if (file_size > offset + dellen)
            {
                size_t tbytes = file_size - offset - dellen;
                size_t rd_pos = offset + dellen;
                size_t wr_pos = offset;
                while (tbytes != 0)
                {
                    ssize_t nbytes = min_size(BUFFERSIZE, tbytes);
                    lseek(fd, rd_pos, SEEK_SET);
                    if (read(fd, buffer, nbytes) != nbytes)
                        return -1;
                    lseek(fd, wr_pos, SEEK_SET);
                    if (write(fd, buffer, nbytes) != nbytes)
                        return -1;
                    tbytes -= nbytes;
                    rd_pos += nbytes;
                    wr_pos += nbytes;
                }
                ftruncate(fd, file_size - dellen);
            }
            else
                ftruncate(fd, offset);
        }
        rc = 0;
    }
    return rc;
}

#if !defined DO_NOT_TEST

#include "stderr.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

typedef struct Data
{
    size_t      offset;
    size_t      length;
} Data;

static const Data delete_ops[] =
{
    {    2,    3 },
    {   84,   33 },
    {  212,  333 },
    { 1022, 1233 },
    { 1024, 2048 },
};
enum { NUM_DELETE = sizeof(delete_ops) / sizeof(delete_ops[0]) };

static void make_data_file(const char *name)
{
    FILE *fp = fopen(name, "w");
    if (fp == 0)
        err_syserr("failed to open '%s' for writing: ", name);
    printf("%s:\n", name);
    char format[] = "%.3d: ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234 abcdefghijklmnopqrstuvwxyz\n";
    for (int i = 0; i < 64; i++)
    {
        fprintf(fp, format, i);
    }
    fclose(fp);
}

int main(int argc, char **argv)
{
    if (argc > 0)
        err_setarg0(argv[0]);
    const char filename[] = "test.dat";

    make_data_file(filename);
    printf("BUFFERSIZE = %d\n", BUFFERSIZE);

    int fd = open(filename, O_RDWR);
    if (fd > 0)
    {
        for (int i = 0; i < NUM_DELETE; i++)
        {
            printf("Delete: offset %4zu, length %4zu\n", delete_ops[i].offset, delete_ops[i].length);
            if (shrink_file_and_delete(fd, delete_ops[i].offset, delete_ops[i].length) != 0)
                break;
            lseek(fd, 0, SEEK_SET);
            char buffer[BUFFERSIZE];
            ssize_t nbytes;
            size_t  tbytes = 0;
            char lastbyte="\n";
            while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
            {
                printf("%.*s", (int)nbytes, buffer);
                lastbyte = buffer[nbytes-1];
                tbytes += nbytes;
            }
            if (lastbyte != '\n')
                putchar('\n');
            printf("Total bytes: %zu\n", tbytes);
        }
        close(fd);
    }
    return(0);
}

#endif /* !DO_NOT_TEST */

Los encabezados no estándar y los archivos fuente correspondientes están disponibles en mi SOQ (Preguntas de desbordamiento de pila) repositorio en GitHub como archivos posixver.h, stderr.c y stderr.h en el src/libsoq subdirectorio.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad