C fopen и fgets возвращают странные символы вместо содержимого файла

Я делаю упражнение по кодированию, и мне нужно открыть файл данных, который содержит много данных. Это файл .raw. Прежде чем создавать приложение, я открываю файл «card.raw» в текстовом и шестнадцатеричном редакторах. Если вы откроете его в textEdit, вы увидите «bit.ly/18gECvy ˇÿˇ‡JFIFHHˇ€Cˇ€Cˇ¿Vˇƒ» в качестве первой строки. (URL указывает на Рик Ролла как на шутку профессора.)

Итак, я начинаю создавать свое приложение, чтобы открыть тот же файл «card.raw». Я делаю первоначальные проверки, чтобы увидеть, как приложение выводит на консоль тот же «материал», что и при открытии его с помощью TextEdit. Вместо того, чтобы распечатать, я вижу, когда я открываю его с помощью TextEdit (см. Текст выше), он начинает и продолжает распечатывать текст, который выглядит следующим образом:

\377\304 'у\204\206\226\262\302\3227\205\246\266\342GSc\224\225\245\265\305\306\325\326Wgs\244\346(w\345 \362\366\207\264\304ǃ\223\227\2678H\247\250\343\344\365\377\304

Теперь я понятия не имею, как называются «\» и цифры (что мне искать, чтобы узнать больше?), почему он печатает это вместо символов (юникод?), которые я вижу, когда я открываю в TextEdit, или если я могу преобразовать этот вывод в шестнадцатеричный или юникод.

Мой код:

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

    int main(int argc, const char * argv[]) {

        FILE* file;

        file = fopen("/Users/jamesgoldstein/CS50/CS50Week4/CS50Recovery/CS50Recovery/CS50Recovery/card.raw", "r");

        char output[LINE_MAX];

        if (file != NULL)
        {
            for (int i = 1; fgets(output, LINE_MAX, file) != NULL; i++)
            {
                printf("%s\n", output);
            }
        }

        fclose(file);

        return 0;
    }

ОБНОВЛЕННЫЙ И УПРОЩЕННЫЙ КОД С ИСПОЛЬЗОВАНИЕМ fread()

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

int main(int argc, const char * argv[]) {

    FILE* fp = fopen("/Users/jamesgoldstein/CS50/CS50Week4/CS50Recovery/CS50Recovery/CS50Recovery/card.raw", "rb");

    char output[256];

    if (fp == NULL)
    {
        printf("Bad input\n");
        return 1;
    }

    for (int i = 1; fread(output, sizeof(output), 1, fp) != NULL; i++)
    {
        printf("%s\n", output);
    }

    fclose(fp);

    return 0;
}

Вывод частично правильный (вот фрагмент начала):

бит.лы/18gECvy

\377\330\377\340 \221\241\26145\301\321\341 "#&23DE\3616BFRTUe\202CVbdfrtv\222\242 'у\204\206\226\262\302\3227\205\246\ 266\342GSc\224\225\245\265\305\306\325\326Wgs\244\346(w\345\362\366\207\264\304ǃ\223\227\2678H\247\250\343\ 344\365\377\304 =\311\345\264\352\354 7\222\315\306\324+\342\364\273\274\205$z\262\313g-\343wl\306\ 375My:}\242o\210\377 3(\266l\356\307T饢"2\377 \267\212ǑP\2218 \344

Настоящий фрагмент файла card.raw с начала

bit.ly/18gECvy ˇÿˇ‡JFIFHHˇ€Cˇ€Cˇ¿Vˇƒ
ˇƒÖ
!1AQa$%qÅë°±45¡—· "#&23DEÒ6BFRTUeÇCVbdfrtví¢


person James Goldstein    schedule 24.06.2016    source источник
comment
Они выглядят как escape-последовательности для специальных символов.   -  person edhurtig    schedule 24.06.2016
comment
Кроме того, поскольку это файл .rtf, это будет странно. не используйте textedit для проверки содержимого файла, поскольку он будет преобразовывать необработанные данные .rtf в презентабельную информацию. Откройте терминал и cat /Users/jamesgoldstein/CS50/CS50Week4/CS50Recovery/CS50Recovery/CS50Recovery/test.rtf, и вы должны увидеть то же самое, что и ваша программа.   -  person edhurtig    schedule 24.06.2016
comment
Файл .rtf был тестовым, реальный файл .raw. Я просто исправил код.   -  person James Goldstein    schedule 24.06.2016
comment
Что вам показывает od -b /Users/jamesgoldstein/CS50/CS50Week4/CS50Recovery/CS50Recovery/CS50Recovery/card.raw? (возможно, передать это через less). Если значения соответствуют вашему выводу, то, я думаю, ваша программа ведет себя правильно; \xxx — восьмеричные управляющие последовательности.   -  person davmac    schedule 24.06.2016
comment
если содержимое файла представляет собой изображение JPEG, то предложите поискать в Google формат изображения JPEG и использовать эту информацию для декодирования каждого поля в файле во что-то понятное для печати.   -  person user3629249    schedule 25.06.2016
comment
в измененном вопросе, опубликованном коде, эта строка: for (int i = 1; fread(output, sizeof(output), 1, fp) != NULL; i++) немного сомнительна, поскольку переменная i никогда не используется. Предложите: while( fread(output, sizeof(output), 1, fp) ), так как он будет делать то же самое, без посторонней переменной i А если (как предлагает один ответ) прочитать весь файл одним «глотком», то даже while не понадобится.   -  person user3629249    schedule 25.06.2016


Ответы (3)


Я думаю, вам следует открыть файл .raw в режиме "rb". Затем используйте fread()

person Rafael Lagemann    schedule 24.06.2016
comment
Хорошо, гораздо ближе! Некоторые файлы должны пройти, а некоторые - как /###/###/####. - person James Goldstein; 25.06.2016
comment
@JamesGoldstein fread не дает вам строки. Вы не можете просто передать его printf %s. - person melpomene; 25.06.2016
comment
Привет. Я действительно не знаю, как точно сказать вам, в чем проблема. Но я скажу то, что, по моему мнению, вам, вероятно, следует проверить: a) Использует ли файл .raw ту же кодировку символов (UTF-8, Unicode и т. д.) b) Обратите внимание, что строки в C имеют ограничитель NULL '\0'. Если у вас есть какие-либо новые результаты, отредактируйте и сообщите нам об этом. Удачи! - person Rafael Lagemann; 26.06.2016

Судя по наличию строки "JFIF" в первой строке файла card.raw ("bit.ly/18gECvy ˇÿˇ‡JFIFHHˇ€Cˇ€Cˇ¿Vˇƒ"), кажется, что card.raw — это файл формата изображения JPEG с URL-адресом bit.ly вставлен в его начало.

В этом случае вы увидите странные/специальные символы, потому что это вовсе не обычный текстовый файл.

Кроме того, как указал davmac, то, как вы используете fgets, не подходит, даже если вы имеете дело с реальным текстовым файлом. При работе с обычными текстовыми файлами в C лучший способ — прочитать весь файл сразу, а не построчно, при условии, что доступно достаточно памяти:

size_t f_len, f_actualread;

char *buffer = NULL;

fseek(file, 0, SEEK_END)
f_len = ftell(fp);
rewind(fp);

buffer = malloc(f_len + 1);

if(buffer == NULL)
{
    puts("malloc failed");
    return;
}

f_actualread = fread(buffer, 1, f_len, file);
buffer[f_actualread] = 0;

printf("%s\n", output);

free(buffer);
buffer = NULL;

Таким образом, вам не нужно беспокоиться о длине строк или чем-то подобном.

person Govind Parmar    schedule 24.06.2016
comment
На самом деле, fgets нормально читает текстовые файлы. Он всегда завершает данные нулем. - person Alok--; 25.06.2016
comment
@Alok-- Я думаю, дело в том, что не текстовый файл. - person davmac; 25.06.2016
comment
@Alok-- Поздний ответ, но я конкретно имел в виду то, как [OP] использовал fgets - person Govind Parmar; 08.11.2020

Вероятно, вам следует использовать fread, а не fgets, так как последний действительно предназначен для чтения текстовых файлов, а это явно не текстовый файл.

Ваш обновленный код на самом деле имеет ту самую проблему, о которой я изначально писал (но с тех пор отказался), поскольку теперь вы используете fread, а не fgets:

for (int i = 1; fread(output, sizeof(output), 1, fp) != NULL; i++)
{
    printf("%s\n", output);
}

т.е. вы печатаете буфер output, как если бы это была строка с завершающим нулем, хотя на самом деле это не так. Лучше использовать от fwrite до STDOUT.

Однако я думаю, что суть проблемы здесь заключается в попытке отобразить произвольные байты (которые на самом деле не представляют собой строку символов) на терминал. Терминал может интерпретировать некоторые последовательности байтов как команды, влияющие на то, что вы видите. Кроме того, textEdit может определить, что файл находится в какой-то кодировке символов, и соответствующим образом декодировать символы.

Теперь я понятия не имею, как называются «\» и цифры (что мне искать, чтобы узнать больше?)

Для меня они выглядят как восьмеричные escape-последовательности.

почему он печатает это вместо символов (юникод?)

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

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

person davmac    schedule 24.06.2016
comment
fgets в порядке. Он добавляет завершающий нулевой байт. На самом деле fgets — это стандартный способ чтения текстовых файлов построчно. - person Alok--; 24.06.2016
comment
@Alok-- ты прав, конечно. Я неправильно запомнил. Я отредактировал эту часть, но, возможно, вскоре просто удалю ответ. - person davmac; 25.06.2016
comment
@Alok-- (за исключением, конечно, того, что файл не является текстовым и не обязательно состоит из строк, а в обновленном коде используется fread, а не fgets, поэтому является с учетом проблемы. Я отредактировал соответственно). - person davmac; 25.06.2016