Я написал следующую программу на C для удаления всех пустых строк из текстового файла «LINES.txt».
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp, *ft;
fp = fopen("LINES.txt", "r");
ft = fopen("TEMP.txt", "w");
if (fp == NULL || ft == NULL)
{
printf("Cannot open file\n");
fclose(fp);
fclose(ft);
exit(1);
}
int e_flag = 0;
char ch, c[100];
int i = 0;
while((ch = fgetc(fp)) != EOF)
{
if (ch != '\n' || ch != '\r')
{
c[i] = ch;
i++;
if (c[i] != ' ' && c[i] != '\t')
e_flag = 1;
}
else
{
c[i] = '\0';
if (e_flag == 1)
{
fputs(c, fp);
e_flag = 0;
}
i = 0;
}
}
fclose(fp);
fclose(ft);
remove("LINES.txt");
rename("TEMP.txt", "LINES.txt");
printf("\nFile editing complete\n");
return 0;
}
Кажется, код компилируется хорошо, без ошибок и предупреждений. Однако, когда я пытаюсь запустить его на кодовых блоках с помощью GCC, я получаю сообщение «Программа перестала работать». Я не могу понять, что здесь происходит не так.
Того же самого можно легко добиться, используя fgets() вместо fgetc(). Однако проблема состоит в том, чтобы использовать fgetc(). Заранее спасибо.
Первая ошибка, которую я вижу, это if (ch!='\n'||ch!='\r'). Это условие всегда выполняется. Дальше я не пытался понять логику.
Не вызывайте fclose указателем NULL. Это не ваша проблема, если файлы открываются успешно, но определенная проблема, если один или оба не открываются. ch должно быть int, и ничто не мешает i превысить 99.
Если бы вы запустили это в отладчике, вы бы легко увидели, в чем проблема и что происходит.
Возможно, вы захотите прочитать эти две ссылки: 1. Как отлаживать небольшие программы 2. Что такое отладчик и как он может помочь мне диагностировать проблемы?
Вы ничего не пишете в ft.
Еще стоит почитать: stackoverflow.com/questions/26337003/…
Вместо чтения файла посимвольно прочитайте целую строку с помощью fgets(). Затем вы можете проверить, пуста ли строка. Если нет, запишите это в выходной файл.
Даже если вы исправите логическую ошибку и используете правильный дескриптор для записи в TEMP.txt, ваша проблема приведет к удалению всех «\r» и «\n», а не только пустых строк.
Вы не защищаете от переполнения c[i]
fgetc() возвращает int, а не char.
Вам нужно либо знать максимальную длину строки (в настоящее время жестко закодировано 100 и не проверено), динамически изменять размер c (как для вас подойдет getline()), чтобы прочитать всю строку, или использовать fseek(), чтобы запомнить начало строки, а затем посмотреть вперед, чтобы найти конец строки; если были только пробелы, пропустите строку, в противном случае вернитесь к началу строки с помощью fseek() и теперь распечатайте ее. Вы также можете mmap файл для просмотра вперед.
На самом деле, вам также следует указать, какие терминаторы строк вы хотите поддерживать. Вы работаете с eol («\r\n»), unix («\n»), устаревшим Mac («\r»). Некоторые или все? Если вам нужны DOS и Unix, но не Mac, как вы хотите обрабатывать «\r», за которым не следует «\n»?
Вы должны определить, что вы имеете в виду с помощью пустой строки. Пустая строка (с пробелом и табуляцией) пуста?
@Бармар говорит мудро. Просто объявите достаточный буфер, прочитайте с помощью fgets() и проверьте, является ли первый символ в буфере '\n'. Если да — строка пуста. Это кажется гораздо проще, чем чтение, ориентированное на персонажей. Допустим, ваша строка не может содержать более 1023 символов. Все, что вам нужно, это char buf[1024]; while (fgets (buf, sizeof buf, stdin)) { if (*buf == '\n') { puts ("empty line"); } } Это можно всю main() выплевывать "empty line" каждый раз, когда вы сталкиваетесь с ним.
Ниже вы получили пару хороших ответов. Убедитесь, что вы принимаете лучший вариант, нажав на галочку рядом с ним.





Это небольшая модификация вашего кода. Одна из наиболее серьезных проблем, которые я обнаружил, заключается в том, что исходный код записывался во входной файл. Это может привести к серьезному неопределенному поведению и сбою программы.
Еще одно изменение заключается в том, что я ввел оператор switch(). Мне,
switch() представляет идеальный метод допроса каждого прочитанного символа
файл. По моему мнению, это делает понимание и установку флагов очень ясными.
Ваш пробег может отличаться.
Также добавлена проверка ошибок. Можно было бы добавить больше проверок на ошибки, и я выделил в комментариях как минимум одно такое место.
Пожалуйста, смотрите комментарии в коде для получения более подробной информации в целом. Этот код будет читать входной файл и по ходу исключать пустые строки из выходного файла. Код скомпилирован и протестирован мной всего несколько минут назад.
#include <stdio.h>
#include <stdlib.h>
/* Note: some files have lines longer than 100 chars. Update this value if needed: */
#define BUFF_SIZE 100
int main()
{
FILE *fp_in = fopen("LINES.txt", "r");
FILE *fp_out = fopen("TEMP.txt", "w");
int i=0, ch, e_flag = 0;
char c_buff[BUFF_SIZE+1];
if (fp_in == NULL || fp_out == NULL)
{
printf("Cannot open file\n");
if (fp_in)
fclose(fp_in);
if (fp_out)
fclose(fp_out);
exit(EXIT_FAILURE);
}
while((ch = fgetc(fp_in)) != EOF)
{
switch(ch)
{
case '\n': /* newline */
if (e_flag == 1)
{
/* Code comes here if we have a newline and it
* is Not the 1st char of the line. Else, it
* would be a blank line and we don't want
* to write blank lines.*/
c_buff[i++] = (char)ch;
c_buff[i] = '\0';
/* This is one reason I changed the file pointer
* naming convention...
* ... to add clarity and ease trouble-shooting.
* Original code was writing to the input file.
* Pretty sure That wasn't desired. */
fputs(c_buff, fp_out); /* Write to OUTPUT file */
i = e_flag = 0;
}
break;
/* Exclude these from buffer/output *unless* the line
* contains other char types too: */
case '\r': /* carriage return */
case '\t': /* tab */
case ' ': /* space */
if (e_flag == 1) /* Not the only character type on the line? */
c_buff[i++] = (char)ch; /* A: yes. OK, add to buffer. */
break;
default: /* all other chars: */
e_flag = 1; /* Line contains chars that are Not just a
* newline, tab, space, etc...
* Flag this line as a 'go' for OUTPUT */
c_buff[i++] = (char)ch; /* ALWAYS add default chars, */
break;
}
if (i > BUFF_SIZE-2)
{
printf("Need a larger buffer for this file!!\n");
printf("Buffer has %d chars.\n", i);
return EXIT_FAILURE;
}
}
fclose(fp_in);
fclose(fp_out);
remove("LINES.txt");
/* Should check to ensure remove() is successful before the rename().
* Left as an exercise for OP...*/
rename("TEMP.txt", "LINES.txt");
printf("\nFile editing complete\n");
return EXIT_SUCCESS;
}
Если вы хотите буферизовать каждую строку ввода, используйте getline() вместо fgetc(). Я написал вам альтернативную реализацию без ограничения строки, которая смотрит на 1 или 2 символа вперед. Он определил строку empty() как строку, которая начинается с:
{EOF}{'\n'}{'\r', EOF}{'\r', '\n'}Впереди может быть деталь реализации empty(), если вы используете ungetc() или fseek(). «гарантирован только один возврат» для ungetc() (хотя в моей системе работало два). fseek(input, -2, SEEK_CUR) не работает на трубах.
Я использовал потоки stdin и stdout для удобства тестирования. Включить уже имеющуюся у вас обработку файлов — это тривиально. Просто не забудьте использовать if-защиту для первых двух вызовов fclose(), как демонстрирует ответ @gregspears.
#include <stdint.h>
#include <stdio.h>
int empty(FILE *input, int lookahead[static 2], uint8_t *n) {
lookahead[0] = fgetc(input);
*n = 0;
if (lookahead[0] == EOF)
return 1;
*n = 1;
if (lookahead[0] == '\n')
return 1;
if (lookahead[0] != '\r')
return 0;
lookahead[1] = fgetc(input);
if (lookahead[1] == EOF)
return 1; // ?
*n = 2;
return lookahead[1] == '\n';
}
void print(int lookahead[static 2], uint8_t n, FILE *input, FILE *output) {
for(int i = 0; i < n; i++)
fputc(lookahead[i], output);
for(;;) {
int ch = fgetc(input);
if (ch == EOF) break;
fputc(ch, output);
if (ch == '\n') break;
}
}
int main() {
FILE *input = stdin;
FILE *output = stdout;
if (!input || !output) {
printf("Cannot open file\n");
return 1;
}
for(;;) {
int lookahead[2];
uint8_t n;
if (empty(input, lookahead, &n)) {
if (!n) break;
continue;
}
print(lookahead, n, input, output);
}
}
и пример запуска (echo используется для обеспечения отображения строки с «7»):
$ cat input.txt && echo && ./a.out < input.txt && echo
# \n
1
2
3
# \r\n
4
5
6
# {\r, EOF}
7
# \n
1
2
3
# \r\n
4
5
6
# {\r, EOF}
7
Насколько я знаю, небезопасно вызывать fclose для NULL-дескрипторов файлов, поэтому используйте брандмауэр для обработки «Невозможно открыть файл», например
if (fp != NULL) { fclose(fp); fp = NULL; }.