问题
How can I create a function to delete certain part of a file? For example, the file is:
-.
Chair: A23
Number: 123
Name: Joshua
-.
Chair: B12
Number: 512
Name: Marcus
-.
Chair: C2
Number: 1
Name: Drake
If the input is
B12
Then the file shall become
-.
Chair: A23
Number: 123
Name: Joshua
-.
Chair: C2
Number: 1
Name: Drake
I need this function for my program to work, but I have no idea how can I do that.
回答1:
Open a new file in the same directory as the original file. Write to that file what you want to replace the contents of the original file. Close the new file. Rename the new file on top of the original file.
But perhaps you should consider using a database instead of a text file.
回答2:
You could first read the file contents into a buffer. Then you could parse and insert data from this buffer into some data structure, such as an array of structs. Once you have that, you can just rewrite the filtered contents back to the file.
Below is some sample code(tweaked) that I wrote a while ago that does something similar to what you want. It takes in 2 command line arguements as input. The first is the file to read from, and the second one is the data to not include, in your case the chair value. You can amend it to read from stdin
if you wanted to.
Code:
#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;
}
Output:
$ 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
Note: The above code is not the best way to do this task, and can certainly be improved. You can use this as a base, and improve upon it.
回答3:
Taking the question at face value, consider using a combination of:
- The converse of Write in the middle of a binary file without overwriting any existing content
- How to truncate a file in C?
You'd use the converse of step 1 to copy the material from the end of the file (after the part to be deleted) over the part to be deleted, and then use step 2 to set the file size to the new value.
Or, probably more simply, copy the material before and after the part to be deleted into a new file, and then move the (content of the) new file in place of the old.
Relevant code:
#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 */
The non-standard headers and corresponding source files are available in my SOQ (Stack Overflow Questions) repository on GitHub as files posixver.h
, stderr.c
and stderr.h
in the src/libsoq sub-directory.
来源:https://stackoverflow.com/questions/50996698/delete-part-of-a-file-in-c