Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Для новичков > Как преобразовать 0x0D в 0x0A?


Автор: ddsoft 17.4.2013, 10:01
Подскажите, пожалуйста, как преобразовать конец строк файлов из Windows в Linux. Т.е. надо пару символов 0D 0A файла Windows преобразовать в символ 0A (как ф должно быть в файле Linux)? Нужно на классическом C. Я Делаю так. Открываю исходный файл читаю посимвольно из него и делаю проверку, если идут подряд два символа 0D0A, пишу в целевой файл только 0A. Но такое не работает. В итоге получается точная копия исходного файла. Использовал функции getc() и putc(). Посмотрел, что записывается в считываемый символ. Оказалось, что туда никогда не записывается символ 0D, только 0A. Поэтому и не работает проверка. Но в целевой файл-то пишется правильно и 0D и 0A. Как прочитать символ 0D?
Вот кусок, который читает их исходного файла:
Код

char ch;

while (!feof(input))
    {
        ch=getc(input);
        ............
    }

Непонятно как в переменную ch пишется сразу два байта, т.к. при записы в файл оба байта там есть (0D0A).

Автор: math64 17.4.2013, 10:20
Ты в какой операционке это делаешь?
В Линуксе твоя программа будет работать правильно.
А вот в Windows по умолчанию при чтении 0D-0A ("\r\n") будет заменяться на 0A ('\n'),
а при записи - 0A в автомате заменится 0D-0A.
Чтобы этого не было, файл нужно откывать в двоичном режиме:
Код

FILE* input = fopen(inputfilename, "rb");
FILE* output = fopen(outputfilename, "wb");
while (!feof(input))
    {
        ch=fgetc(input);
        ............
        fputc(ch, output);
    }
fclose(input);
fclose(output);

Автор: feodorv 17.4.2013, 10:23
Цитата(ddsoft @  17.4.2013,  11:01 Найти цитируемый пост)
Непонятно как в переменную ch пишется сразу два байта, т.к. при записы в файл оба байта там есть (0D0A). 

Ну, Вы же код не привели, поэтому и комментировать нечего.
Побайтовое чтение - не очень эффективно, но пусть будет.

Цитата(ddsoft @  17.4.2013,  11:01 Найти цитируемый пост)
Но в целевой файл-то пишется правильно и 0D и 0A. Как прочитать символ 0D?

Надо просто запоминать, что предыдущий символ был 0D, и если текущий - 0A, то 0D не записывать:
Код

int maybe = 0;
int ch;
while( (ch = fgets(input)) != EOF )
{
  if( ch == '\n' )
  {
    fputc( '\n', output);
    maybe = 0;
  }
  else if( ch == '\r' )
  {
    if( maybe ) fputc( '\r', output);
    maybe = 1;
  }
  else
  {
    if( maybe ) fputc( '\r', output);
    fputc( ch, output);
    maybe = 0;
  }
}
if( maybe ) fputc( '\r', output);

И не забывать проверять ошибки вызовов)))

Автор: ddsoft 17.4.2013, 21:29
Цитата(math64 @  17.4.2013,  10:20 Найти цитируемый пост)
Ты в какой операционке это делаешь?
В Линуксе твоя программа будет работать правильно.
А вот в Windows по умолчанию при чтении 0D-0A ("\r\n") будет заменяться на 0A ('\n'),
а при записи - 0A в автомате заменится 0D-0A.
Чтобы этого не было, файл нужно откывать в двоичном режиме

Я делаю в Windows. Действительно, в линуксе работает как надо.
Это особенность операционки при работе с текстовыми файлами? 
А как-нибудь можно, при работе с текстовыми файлами, указать в Windows, что не надо заменять при чтении 0D-0A на 0A?
Да, при открытии в двоичном режиме всё работает. Спасибо!

Автор: ddsoft 17.4.2013, 23:09
Цитата(feodorv @  17.4.2013,  10:23 Найти цитируемый пост)
Ну, Вы же код не привели, поэтому и комментировать нечего.

Привожу код, преобразующий символы конца срок из Windows формата в Linux формат:
Код

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

int main(int argc, char *argv[])
{
    char ch;
    FILE *input, *output;
    input=fopen("input.txt","r+");
    output=fopen("output.txt","w+");
    if (input==NULL)
    {
        printf("Source file not found or can not be opened.\n");
        return -1;
    }
    while (!feof(input))
    {
        ch=getc(input);
        if (!feof(input)) if (ch!=0x0D) putc(ch, output);        
    }
    fclose(input);
    fclose(output);
    return 0;
}


Цитата(feodorv @  17.4.2013,  10:23 Найти цитируемый пост)
Побайтовое чтение - не очень эффективно

А как посоветуете сделать эффективнее?

Цитата(feodorv @  17.4.2013,  10:23 Найти цитируемый пост)
Надо просто запоминать, что предыдущий символ был 0D, и если текущий - 0A, то 0D не записывать

Обнаружить символ 0D не получилось при открытии файла в текстовом режиме.
А когда я поменял строки
Код

    input=fopen("input.txt","r+");
    output=fopen("output.txt","w+");

на строки
Код

    input=fopen("input.txt","rb");
    output=fopen("output.txt","wb");

всё заработало как надо.

Цитата(feodorv @  17.4.2013,  10:23 Найти цитируемый пост)
  if( ch == '\n' )
  {
    fputc( '\n', output);
    maybe = 0;
  }

Мне такой код писал сразу два байта (\r\n) в файл, открытый в текстовом режиме.

Цитата(feodorv @  17.4.2013,  10:23 Найти цитируемый пост)
  else if( ch == '\r' )
  {
    if( maybe ) fputc( '\r', output);
    maybe = 1;
  }

А такой код у меня совсем не выполнялся.

Автор: volatile 18.4.2013, 00:18
можно логику упростить. просто игнорировать 0xd, и все. 

Автор: feodorv 18.4.2013, 04:34
Цитата(ddsoft @  18.4.2013,  00:09 Найти цитируемый пост)
А как посоветуете сделать эффективнее?

Читайте, например, по 4 килобайта, выбрасывайте из буфера '\r' (длина буфера уменьшится), записывайте получившийся буфер... 

Цитата(ddsoft @  18.4.2013,  00:09 Найти цитируемый пост)
Мне такой код писал сразу два байта (\r\n) в файл, открытый в текстовом режиме.

Ну, с этим уже разобрались  smile Фича такая у стандартной библиотеки от MS... Вот http://forum.vingrad.ru/forum/topic-345576/anchor-entry2450109/0.html человеку ещё хуже пришлось...

Автор: ddsoft 18.4.2013, 06:50
Цитата(volatile @  18.4.2013,  00:18 Найти цитируемый пост)
можно логику упростить. просто игнорировать 0xd, и все.  

Что вы имеете ввиду? Можно поподробнее, пожалуйста?

Добавлено @ 06:55
Цитата(feodorv @  18.4.2013,  04:34 Найти цитируемый пост)
Цитата(ddsoft @  18.4.2013,  00:09)
А как посоветуете сделать эффективнее?

Читайте, например, по 4 килобайта, выбрасывайте из буфера '\r' (длина буфера уменьшится), записывайте получившийся буфер... 

А что будет, если длина буфера окажется больше оставшегося куска файла? Автоматически буфер будет подгоняться под размер оствшегося куска файла или целевой файл окажется длиннее исходного на длину остатка буфера?

Автор: feodorv 18.4.2013, 08:25
Цитата(ddsoft @  18.4.2013,  07:50 Найти цитируемый пост)
А что будет, если длина буфера окажется больше оставшегося куска файла?

Не понял, как это... Договорились же открывать файлы в бинарной моде, там чтение 1 в 1... Вот скелет алгоритма:
Код

int count, i, j;
while( (count = fread( buffer, 1, sizeof(buffer), input)) > 0 )
{
  for( i = j = 0; i < count; i++) 
    if( buffer[i] != '\r' ) buffer[j++] = buffer[i];
  if( j > 0 ) fwrite( buffer, 1, j, output);
}

Автор: math64 18.4.2013, 09:05
Ну можно открыть файл в текстовом режиме, прочитать всё, и записать двоичном. Но так можно только в Windows, в Linux получите на выходе копию входного файла, поскольку там текстовый и двоичный режим не отличаются.

При чтении же в текстовом режиме режиме 0D не игнорируется
0D 0A  и 0A 0D (стандарт MAC) заменяются на 0A.
Одиночный 0D нужно либо оставлять так, либо заменять на 0A (есть ОС, в которых для разделения строк использется 0D)

Автор: xvr 18.4.2013, 10:10
Цитата(ddsoft @  17.4.2013,  23:09 Найти цитируемый пост)
Привожу код, преобразующий символы конца срок из Windows формата в Linux формат:

Как правильно заметил math64 код можно несколько упростить:
Код

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int ch;
    FILE *input, *output;
    input=fopen("input.txt","rt");
    output=fopen("output.txt","wb");
    if (input==NULL)
    {
        printf("Source file not found or can not be opened.\n");
        return -1;
    }
    if (output==NULL)
    {
        printf("Can't create output file.\n");
        return -1;
    }
    while ( (ch=getc(input)) != EOF) putc(ch, output);        
    fclose(input);
    fclose(output);
    return 0;
}

Автор: ddsoft 18.4.2013, 16:28
Цитата(feodorv @  18.4.2013,  08:25 Найти цитируемый пост)
Цитата(ddsoft @  18.4.2013,  07:50 )
А что будет, если длина буфера окажется больше оставшегося куска файла?

Не понял, как это... Договорились же открывать файлы в бинарной моде, там чтение 1 в 1... 

Я имел ввиду, что если буфер 4 килобайта, а файл 10 килобайт, то при двукратной записи буфера в файл,
весь файл ещё не перепишется, а при трёхкратной целевой файл окажется уже 12 килобайт вместо 10 килобайт.
По вашему коду теперь понял, что записываться будет только количество реально прочитанных байт.

Цитата(math64 @  18.4.2013,  09:05 Найти цитируемый пост)
Ну можно открыть файл в текстовом режиме, прочитать всё, и записать двоичном.

Я об этом не подумал. Спасибо за наводку smile 

Цитата(xvr @  18.4.2013,  10:10 Найти цитируемый пост)
Как правильно заметил math64 код можно несколько упростить

Спасибо!

Автор: volatile 18.4.2013, 23:31
Цитата(math64 @  18.4.2013,  09:05 Найти цитируемый пост)
При чтении же в текстовом режиме режиме 0D не игнорируется
0D 0A  и 0A 0D (стандарт MAC) заменяются на 0A.
Одиночный 0D нужно либо оставлять так, либо заменять на 0A (есть ОС, в которых для разделения строк использется 0D) 

При преобравании виндозных файлов можно просто игнорировать. 
Если -же пишецца универсальный конвертер текстовых файлов любых операционок, то ни одна программа приведенная здесь работать правильно все равно не будет.  smile 

Автор: ddsoft 19.4.2013, 11:19
Допилил программу. Программа работает в windows и linux, преобразовывает концы строк win->unix и unix->win, имеет 3 входных параметра: 1) режим преобразования (win->unix или unix->win); 2) исходный файл; 3) целевой файл. Как на ваш взгляд можно сделать оптимальнее? Вот что у меня получилось:
Код

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

int main(int argc, char *argv[])
{
    char ch;
    FILE *input, *output;
    int value; //возвращаемое функцией main значение

    if (argc<3) //если не хватает параметров
    {
        printf("too few parameters to program.\n");
        printf("usage: program <change_mode> <source_file> <destination_file>\n");
        printf("change_mode = (win2nix, nix2win)\n");
    }
    else
    {
        input=fopen(argv[2],"rb");
        output=fopen(argv[3],"wb");
        if (input==NULL)
        {
            printf("Source file is not found or can not be opened.\n\n...end.");
            value = -1;
        }
        else
        {
            while (!feof(input)) // Пока не конец файла
            {
                ch=getc(input);
                if (!feof(input))
                {
                    if (strcmp(argv[1], "win2nix")==0)
                      if (ch!=0x0D) putc(ch, output); //пишем всё, кроме символа возврата каретки, в целевой файл
                    if (strcmp(argv[1], "nix2win")==0)
                     if (ch!=0x0D) //проверка на случай, если преобразовываться будет файл не с unix концом строки, а с windows концом строки (иначе в файле будут копиться лишние символы 0x0D)
                     {
                       if (ch==0x0A) //если встречаем unix символ конца строки,
                       {
                           //пишем к нему ещё перевод каретки для windows конца строки
                           putc(0x0D, output); putc(ch, output);
                       }
                       else putc(ch, output); //иначе просто пишем очередной символ
                     }
                }
            }
            fclose(input);
            fclose(output);
            value = 0;
        }
    }
    return value;
}

Автор: xvr 19.4.2013, 12:11
Цитата(ddsoft @  19.4.2013,  11:19 Найти цитируемый пост)
 Как на ваш взгляд можно сделать оптимальнее?

Можно, причем существенно оптимальнее. И вам тут уже говорили как именно  smile 

Автор: ddsoft 19.4.2013, 12:36
Цитата(xvr @  19.4.2013,  12:11 Найти цитируемый пост)
Цитата(ddsoft @  19.4.2013,  11:19 )
 Как на ваш взгляд можно сделать оптимальнее?

Можно, причем существенно оптимальнее. И вам тут уже говорили как именно   

Да, но программа должна работать и в windows, и в linux. А оптимизация касалась только работы в windows. Вышеописанный трюк, основанный на особенности windows читать и писать конец строки, в linux не проходит.

Автор: math64 19.4.2013, 22:04
stdio уже включает в себя буферизацию - так что явная буферизация не обязательно будет оптимальнее.
А вот вызывать strcmp внутри цикла для проверки режима - явно не оптимально.
Режим обычно задаётся краткая форма -u -w полная форма --unix-to-windows --windows-to-unix.
Далее запоминается в булевскую или int переменную и либо вызываются разные функции для разных режимов либо проверяется эта переменная.

Автор: volatile 20.4.2013, 00:01
Буферизацию надо попробовать сделать, если требуецца оптимизация, конечно.
Стандартная буферизация хоть и есть, но почему-то очень медленная.
Проверялось правда давно в старой студии, может в новых получче уже стало, не знаю.
С простым буфером размером 16К, чтение/запись fread, fwrite
работало раз в 100 быстрее чем через putc;  smile 


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)