Программа на C для копирования.csv целых чисел копирует на один элемент меньше, если размер элемента не установлен равным +1
Я новичок в изучении языка Си, и я хотел написать простую программу, которая бы копировала целые числа массива из одного файла.csv в новый файл.csv. Мой код работает как задумано, однако, когда мой размер массива для fread / fwrite установлен на точное количество элементов в массиве.csv (в нашем случае 10), он копирует только девять элементов.
Когда размер массива установлен равным +1, он копирует все элементы.
#include <stdio.h>
#include <stdlib.h>
#define LISTSIZE 11
//program that copies an array of integers from one .csv to another .csv
int main(int argc, char * argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage ./file_sort file.csv\n");
return 1;
}
char * csvfile = argv[1];
FILE * input_csvile = fopen(csvfile, "r"); //open .csv file and create file pointer input_csvile
if(input_csvile == NULL)
{
fprintf(stderr, "Error, Could not open\n");
return 2;
}
unsigned int giving_total[LISTSIZE];
if(input_csvile != NULL) //after file opens, read array from .csv input file
{
fread(giving_total, sizeof(int), LISTSIZE, input_csvile);
}
else
fprintf(stderr, "Error\n");
FILE * printed_file = fopen("school_currentfy1.csv", "w");
if (printed_file != NULL)
{
fwrite(giving_total, sizeof(int), LISTSIZE, printed_file); //copy array of LISTSIZE integers to new file
}
else
fprintf(stderr, "Error\n");
fclose(printed_file);
fclose(input_csvile);
return 0;
}
Это как-то связано с индексированием массива 0 и индексированием файла.csv? У меня также был вывод с LISTSIZE, равным 11, в котором последний (10) элемент отображался некорректно; 480 вместо 4800.
https://imgur.com/lLOozrc Вывод / ввод с LISTSIZE из 10
https://imgur.com/IZPGwsA Ввод / вывод с LISTSIZE из 11
1 ответ
Примечание: как отмечено в комментарии, fread
а также fwrite
предназначены для чтения и записи двоичных данных, а не текста. Если вы имеете дело с .csv
(значения, разделенные запятыми - например, как экспортированные из MS Excel или Open / LibreOffice calc) Вам нужно будет использовать fgets
(или любая другая символьно-ориентированная функция) с последующим sscanf
(или же strtol
, strtoul
), чтобы прочитать значения в виде текста и выполнить преобразование в int
ценности. Чтобы записать значения в выходной файл, используйте fprintf
, (fscanf
также доступна для обработки ввода текста и преобразования, но вы теряете гибкость в обработке изменений в формате ввода)
Однако, если вашей целью было прочитать двоичные данные для 10
целые числа (например, 40-bytes
данных), то fread
а также fwrite
это нормально, но, как и во всех подпрограммах ввода / вывода, вам нужно проверить количество прочитанных и записанных байтов, чтобы убедиться, что вы имеете дело с действительными данными в вашем коде. (и что у вас есть действительный файл выходных данных, когда вы закончите)
Есть много способов прочитать .csv
файл, в зависимости от формата. Одним из общих способов является просто прочитать каждую строку текста с fgets
а затем повторно позвонить sscanf
конвертировать каждое значение. (это имеет ряд преимуществ при обработке разного расстояния вокруг ','
по сравнению с fscanf
Вы просто читаете каждую строку, назначаете указатель на начало буфера, читаемого fgets
, а затем позвоните sscanf
(с %n
вернуть номер символа, обработанного каждым вызовом), а затем переместить указатель на этот номер и сканировать вперед в буфере до следующего '-'
(для отрицательных значений) или встречается цифра. (с помощью %n
и сканирование вперед может позволить fscanf
использоваться аналогичным образом) Например:
/* read each line until LISTSIZE integers read or EOF */
while (numread < LISTSIZE && fgets (buf, MAXC, fp)) {
int nchars = 0; /* number of characters processed by sscanf */
char *p = buf; /* pointer to line */
/* (you should check a whole line is read here) */
/* while chars remain in buf, less than LISTSIZE ints read
* and a valid conversion to int perfomed by sscanf, update p
* to point to start of next number.
*/
while (*p && numread < LISTSIZE &&
sscanf (p, "%d%n", &giving_total[numread], &nchars) == 1) {
numread++; /* increment the number read */
p += nchars; /* move p nchars forward in buf */
/* find next digit in buf */
while (*p && *p != '-' && (*p < '0' || *p > '9'))
p++;
}
}
Теперь, чтобы создать выходной файл, вы просто пишете numread
значения возвращаются в формате значений, разделенных запятыми. (вы можете отрегулировать количество записей в каждой строке)
for (i = 0; i < numread; i++) /* write in csv format */
fprintf (fp, i ? ",%d" : "%d", giving_total[i]);
fputc ('\n', fp); /* tidy up -- make sure file ends with '\n' */
Затем нужно просто закрыть выходной файл и проверить наличие ошибок потока (всегда проверяйте закрытие при записи значений в файл).
if (fclose (fp)) /* always validate close after write to */
perror("error"); /* validate no stream errors occurred */
В целом, вы можете сделать что-то похожее на следующее:
#include <stdio.h>
#include <stdlib.h>
#define LISTSIZE 10
#define MAXC 256
int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "Usage ./file_sort file.csv [outfile]\n");
return 1;
}
int giving_total[LISTSIZE]; /* change to int to handle negative values */
size_t i, numread = 0; /* generic i and number of integers read */
char *csvfile = argv[1],
buf[MAXC] = ""; /* buffer to hold MAXC chars of text */
FILE *fp = fopen (csvfile, "r");
if (fp == NULL) { /* validate csvfile open for reading */
fprintf(stderr, "Error, Could not open input file.\n");
return 2;
}
/* read each line until LISTSIZE integers read or EOF */
while (numread < LISTSIZE && fgets (buf, MAXC, fp)) {
int nchars = 0; /* number of characters processed by sscanf */
char *p = buf; /* pointer to line */
/* (you should check a whole line is read here) */
/* while chars remain in buf, less than LISTSIZE ints read
* and a valid conversion to int perfomed by sscanf, update p
* to point to start of next number.
*/
while (*p && numread < LISTSIZE &&
sscanf (p, "%d%n", &giving_total[numread], &nchars) == 1) {
numread++; /* increment the number read */
p += nchars; /* move p nchars forward in buf */
/* find next digit in buf */
while (*p && *p != '-' && (*p < '0' || *p > '9'))
p++;
}
}
if (numread < LISTSIZE) /* warn if less than LISTSIZE integers read */
fprintf (stderr, "Warning: only '%zu' integers read from file", numread);
fclose (fp); /* close input file */
fp = fopen (argc > 2 ? argv[2] : "outfile.csv", "w"); /* open output file */
if (fp == NULL) { /* validate output file open for writing */
fprintf(stderr, "Error, Could not open output file.\n");
return 3;
}
for (i = 0; i < numread; i++) /* write in csv format */
fprintf (fp, i ? ",%d" : "%d", giving_total[i]);
fputc ('\n', fp); /* tidy up -- make sure file ends with '\n' */
if (fclose (fp)) /* always validate close after write to */
perror("error"); /* validate no stream errors occurred */
return 0;
}
Как я уже сказал, есть много-много способов подойти к этому. Идея состоит в том, чтобы обеспечить максимально возможную гибкость чтения, чтобы он мог обрабатывать любые изменения в формате ввода без удушья. Другой очень надежный способ приблизиться к чтению - использовать strtol
(или же strtoul
для беззнаковых значений). Оба параметра allow передадут вам указатель на следующий символ после преобразованного целого числа, чтобы вы могли начать поиск следующей цифры оттуда.
Пример гибкости чтения, предоставляемой в любом из этих подходов, показан ниже. Чтение файла с любым количеством строк со значениями, разделенными любым разделителем, и преобразование каждого целого числа в значение в вашем массиве, например
Пример ввода
$ cat ../dat/10int.csv
8572, -2213, 6434, 16330, 3034
12346, 4855, 16985, 11250, 1495
Пример использования программы
$ ./bin/fgetscsv ../dat/10int.csv dat/outfile.csv
Пример выходного файла
$ cat dat/outfile.csv
8572,-2213,6434,16330,3034,12346,4855,16985,11250,1495
Посмотрите вещи и дайте мне знать, если у вас есть вопросы. Если ваше намерение было прочитать 40-bytes
в двоичном виде, просто дайте мне знать, и я рад помочь с примером там.
Если вы хотите действительно общее чтение значений в файле, вы можете настроить код, который находит число во входном файле, для сканирования вперед в файле и проверки того, что любой '-'
сопровождается цифрой. Это позволяет читать любой формат и просто выбирать целые числа из файла. Например, со следующим незначительным изменением:
while (*p && numread < LISTSIZE) {
if (sscanf (p, "%d%n", &giving_total[numread], &nchars) == 1)
numread++; /* increment the number read */
p += nchars; /* move p nchars forward in buf */
/* find next number in buf */
for (; *p; p++) {
if (*p >= '0' && *p <= '9') /* positive value */
break;
if (*p == '-' && *(p+1) >= '0' && *(p+1) <= '9') /* negative */
break;
}
}
Вы можете легко обработать следующий файл и получить те же результаты:
$ cat ../dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a
- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495
Пример использования программы
$ ./bin/fgetscsv ../dat/10intmess.txt dat/outfile2.csv
Пример выходного файла
$ cat dat/outfile2.csv
8572,-2213,6434,16330,3034,12346,4855,16985,11250,1495