Слияние линий двух файлов текст для вывода


Моя программа должна объединить линии двух текстовых файлов. Например, если у меня есть два файла, one.txt:

a b c d e f g h i j k l m
n o p q r s t u v w x y z

и two.txt:

0 1 2 3 4
5 6 7 8 9

Вывод:

a b c d e f g h i j k l m0 1 2 3 4
n o p q r s t u v w x y z5 6 7 8 9 

Однако, мне кажется, что некоторые условия являются избыточными. Однако, я не уверен, как я могу улучшить дизайн этого.

// Program to merge lines from two files and output results
#include <stdio.h>

int main(int argc, char *argv[])
{
    char *inName1 = argv[1], *inName2 = argv[2];
    FILE *in1, *in2;
    int c, d;

    // Ensure correct usage
    if (argc != 3)
    {
        fprintf(stderr, "Usage: ./merge <file1> <file2>\n");
        return 1;
    }

    // Open input files and return if unable to open
    if ((in1 = fopen(inName1, "r")) == NULL)
    {
        fprintf(stderr, "Can't open %s.\n", inName1);
        return 2;
    }

    if ((in2 = fopen(inName2, "r")) == NULL)
    {
        fprintf(stderr, "Can't open %s.\n", inName2);
        return 3;
    }

    // Take care of output
    while ((c = getc(in1)) != EOF)
    {
        if (c != '\n')
            putc(c, stdout);        
        else
        {
            while ((d = getc(in2)) != EOF)
            {
                if (d != '\n')
                    putc(d, stdout);
                else
                {
                    putc('\n', stdout);
                    break;
                }
            }

            if (d == EOF)
            {
                while (c != EOF)
                {
                    putc(c, stdout);
                    c = getc(in1);
                }
            }
        }
    }

    if (c == EOF)
    {
        while ((d = getc(in2)) != EOF)
        {
            putc(d, stdout);
        }
    }

    fclose(in1);
    fclose(in2);

    printf("\nProcess ended successfully.\n");

    return 0;
}


114
5
задан 26 марта 2018 в 04:03 Источник Поделиться
Комментарии
2 ответа

Некоторые хорошие вещи

Это компилируется с все мои обычные предупреждения компилятора включена - молодец!

Вы использовали stderr соответственно для вывода (за исключением заключительного сообщения); это хорошая практика.

Не разыменовать argv прежде чем вы проверяли ее пределами

Это неправильно:

    char *inName1 = argv[1], *inName2 = argv[2];

if (argc != 3) {
fprintf(stderr, "Usage: ./merge <file1> <file2>\n");
return 1;
}

Мы не можем читать из argv[1] или argv[2] пока после того, как мы знаем, что argc достаточно велик:

    if (argc != 3)
{
fprintf(stderr, "Usage: %s <file1> <file2>\n", argv[0]);
return 1;
}
char *inName1 = argv[1], *inName2 = argv[2];

Я усилил ваше сообщение об ошибке, чтобы использовать фактическое имя программы, прошло в argv[0].

Отчет о фактическом тип ошибки при сбое

"Не могу открыть ", по крайней мере, указывает, что файл не мог быть открыт, но мы можем быть более конкретными (файл не найден, доступ запрещен, ...) с помощью perror() функции:

    FILE *const in1 = fopen(inName1, "r");
if (!in1) {
perror(inName1);
return 1;
}

Я также изменил это, чтобы вернуться 1 из любой ошибки - нет большого преимущества в различении разных причин в код ошибки.

Небольшое дополнение к логике

Петли смотрю в основном правильно; есть небольшой дублирования можно избежать здесь:

        while ((d = getc(in2)) != EOF)
{
if (d != '\n')
putc(d, stdout);
else
{
putc('\n', stdout);
break;
}
}

Мы можем принести putc() вне if-else как это:

        while ((d = getc(in2)) != EOF) {
putc(d, stdout);
if (d == '\n') {
break;
}
}

Избыточного теста

while ((c = getc(in1)) != EOF)
{
/* ... */
}

/* c can only be EOF here */
if (c == EOF)
{
/* ... */
}

Рассмотрим некоторые крайние случаи

Вы взяли хорошую заботу, чтобы гарантировать, что, когда заканчивается один файл, остальные из другого выхода. Задумывались ли вы о том, когда, чтобы вывести новую строку, если конец файла не заканчиваются одним. Непонятно, что вы хотите случиться в этом случае - комментарии должны объяснить желаемый результат.

Альтернативная логика

Я предпочитаю менее глубоко вложенных циклов, где первая петля "нормальном" случае, когда мы читаем из файлов, и мы должны выйти из этой петли, когда либо файл исчерпан, и закончить копирование с других:

int c, d;
// Take care of output
while ((c = getc(in1)) != EOF) {
if (c != '\n') {
putc(c, stdout);
continue;
}

while ((d = getc(in2)) != EOF) {
putc(d, stdout);
if (d == '\n') {
break;
}
}

if (d == EOF) {
break;
}
}

while ((c = getc(in1)) != EOF) {
putc(c, stdout);
}
while ((d = getc(in2)) != EOF) {
putc(d, stdout);
}

Я держал оба c и d переменные для этого, хотя мы только действительно нужно для обоих файлов.

Проверка ошибок на выходе

Ни один из перечисленных выше код не проверяет возвращаемое значение putc(). Файловые системы вообще завались, и пользователи отсоединить потоков и хуже, поэтому мы должны убедиться, что пишет преуспеть.

Проверка ошибок ввода

В настоящее время мы предполагаем, что EOF вернулся из getc() означает, что мы достигли конца файла. Однако, это может означать, что у нас есть ошибка чтения, и нам следует различать те случаи, используя feof() или ferror().

Последнее сообщение

printf("\nProcess ended successfully.\n");

Это не часть выходного (и вы не хотите его, чтобы быть переданной в производство). Отправить статус сообщения stderr - или лучше все-таки молчать, пока успешно, как и стандартные утилиты.

3
ответ дан 26 марта 2018 в 11:03 Источник Поделиться

Используйте функции, чтобы избежать повторения кода

У вас есть один большой main() функция, которая делает все. Пока это небольшая программа, это еще лучше отколоть некоторые логические куски кода в свои собственные функции. В этом случае, операция, которые вы повторите несколько раз копирует одну строку ввода из входного файла в stdout. Так написать функцию вроде этой:

int copyline(FILE *in, FILE *out) {
int c;

// Copy all characters up to a '\n' or EOF
while ((c = getc(in)) != '\n' && c != EOF) {
putc(c, out);
}

// This will return either '\n' or EOF
return c;
}

Обратите внимание, как эта функция возвращает последний результат от getc().
В основном, вы можете использовать его, чтобы упростить ваш код такой:

// Copy all lines from in1
while (copyline(in1, stdout) != EOF) {
copyline(in2, stdout);
putc('\n', stdout);
}

// Copy any remaining lines from in2
while (copyline(in2, stdout) != EOF) {
putc('\n', stdout);
}

Стараюсь читать все строки, а не символы одновременно

Теперь, когда у вас уже есть работающий код, вы можете думать о его оптимизации.
Ваш код считывает и записывает один символ в то время. Этого не может быть идеальным.
Вызовы функций, как getc() и putc() не бесплатно, и поскольку они не могут быть встроены, компилятор будет иметь трудное время оптимизации циклов.

Если вы можете читать и писать все строки сразу, что будет лучше.
Если вы разделяете вашу линию копировать код в функцию, как copyline() выше, тогда вам только нужно обновить эту функцию. Одна возможность заключается в использовании
в fgets() и fputs() функции, которые читают и пишут целые строки сразу.
Помните, однако, что fgets() могу только читать как можно больше символов, а размер буфера поставить (минус один), поэтому вы должны обнаруживать и обрабатывать длинные строки как-то. Кроме того, эти функции предполагают, что вы работаете с C строками,
какое значение Nul байт, как терминаторы, и как таковые, они дадут вам проблем, если
вы хотите, чтобы ваши программы, чтобы быть в состоянии обрабатывать строки, содержащие нулевым байтом.

Другой вариант fread() весь файл в память, или использовать mmap() на карте его в памяти, и реализовать свои собственные функции для сканирования памяти для символов новой строки.

При оптимизации кода, убедитесь, что вы запустить тесты на старых и новых реализаций, никогда не предполагайте, что конкретная реализация будет быстрее или медленнее, чем другой, не измерив его.

Рядом stdout и проверить на ошибки

Вы должны позвонить fclose(stdout) как хорошо в конце, и проверить на ошибки. Выходной файл буферизуется, и fclose() вызов убедиться, что все буферы сбрасываются. Ошибки могут наверняка произойдет; например, пользователь может быть перенаправлен стандартный поток вывода в файл и файловая система не хватает места для хранения продукции.

Если вы молча игнорировать эти ошибки, повреждение данных может произойти более легко. Например, если вы хотите автоматизировать процесс, в котором создаются два промежуточных вывода файлов, но вас интересуют только Объединенный вывод, и впоследствии удалить временные файлы, вы могли бы иметь скрипт, который содержит следующую строку:

mergelines temp1.txt temp2.txt >result.txt && rm temp*.txt

Возвращая ошибку, вам придется предотвратить потери оригинальных файлов.
Примечание в GNU Coreutils, пакет, содержащий все основные Unix для обработки текстовых утилиты, как cat, делает именно это.

Попробуйте сделать свою программу как можно более универсальный

Ваша программа может объединить линии из двух файлов, но что если кто-то хочет объединить строки из трех файлов? Это звучит как очень логичное продолжение, и если вы сделали вашу программу немного более универсальный, вы можете легко обработать произвольное количество входных файлов. Просто сделать массив FILE * указатели, как большой, как argc - 1и для каждого имени файла в командной строке, откройте этот файл. Затем скопировать строки из всех открытых файлов, пока все они не вернуться EOF.

Вы могли также хотеть можно указывать только одно имя файла в командной строке, или даже вообще нет!

2
ответ дан 26 марта 2018 в 08:03 Источник Поделиться