1

Пытаюсь прочесть файл, но даже при правильной кодировке не получается получить данные.

FILE *file;
char fileName[256];

setlocale(LC_ALL, "utf-8");

scanf("%s", fileName);

if ((file = fopen(fileName, "r")) == NULL)
{
    printf("Unable to open file");
    return 0;
}
eanmos
  • 6,651
typical
  • 13
  • Очень мало информации даёте. Подробнее пожалуйста. Может быть неправильный путь к файлам. – AlexGlebe Aug 01 '19 at 06:26
  • Добавьте входные данные. Что Вы вводите, когда получаете ошибку? Пройдитесь отладчиком и скажите, что сохраняется в s. – V-Mor Aug 01 '19 at 06:35
  • Какая у вас операционная система? – Sergey Gornostaev Aug 01 '19 at 06:43
  • В переменной сохраняется правильный путь; ОС - Windows 7; Ввожу что то вроде: C:\Users\Андрей\Desktop\file.txt – typical Aug 01 '19 at 07:03
  • @typical в Windows консоль имеет кодировку cp866, файловая система cp1251, а локали "utf-8" вообще не существует. – Sergey Gornostaev Aug 01 '19 at 07:09
  • хм, допустим, а как я могу исправить программу, чтобы она смогла обрабатывать кириллицу?setlocale(LC_ALL, "Russian"); тоже не подходит – typical Aug 01 '19 at 07:14
  • @SergeyGornostaev Локаль UTF8 поддерживается в новых версиях Visual C++, хотя правильно она записывается как ".UTF8", а не "utf-8". В документации это не отражено, но я проверил, это работает. Консоль в Windows 10 также в некоторой мере поддерживает UTF8, но требует особых телодвижений по настройке буферизации для корректной работы (https://stackoverflow.com/a/45588456/8674428). Но я не думаю, что автору это действительно нужно, реальный ответ - использовать широкие символы. – MSDN.WhiteKnight Aug 01 '19 at 11:54
  • @typical setlocale в этом не поможет. – Sergey Gornostaev Aug 01 '19 at 18:02
  • @MSDN.WhiteKnight wscanf и _wfopen? – Sergey Gornostaev Aug 01 '19 at 18:03
  • @SergeyGornostaev Да, в сочетании с кодом для перевода стандартных потоков в режим UTF16 из https://ru.stackoverflow.com/questions/459154/Русский-язык-в-консоли – MSDN.WhiteKnight Aug 01 '19 at 18:50

2 Answers2

0

Вам нужно понять кодировку, которую использует консоль Windows. Можно воспользоваться функциями SetConsoleCP(1251) и SetConsoleOutputCP(1251), а можно внешней командой chcp 1251:

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

int main(void)
{
    system("chcp 1251");

    char fileName[128];
    scanf("%127s", fileName);

    FILE *file = fopen(fileName, "r");

    ...
}
eanmos
  • 6,651
  • SetConsoleCP(1251) лишает программу переносимости, а system("chcp 1251") ещё и не учитывает возможность запуска в командном интерпретаторе, не поддерживающим эту команду, или вообще не в командном интерпретаторе. – Sergey Gornostaev Aug 01 '19 at 18:05
  • @SergeyGornostaev, я сильно сомневаюсь, что эту задачу можно решить переносимым кодом. Если использовать широкие символы (wchar_t), то будет привязка как минимум к _wfopen, к тому же придется использовать _setmode. Так что это решение с минимальным оверхедом, тем более автору нужна кириллица, а не весь Unicode. – eanmos Aug 01 '19 at 18:48
  • спасибо за код, но вы сами знаете, что при вводе символов получается бред, а так все работает – typical Aug 02 '19 at 06:58
  • @typical, хм, у меня прекрасно вводятся. Вы стандартной консолью cmd пользуетесь? Я проверял на ней и на ConEmu. – eanmos Aug 02 '19 at 10:31
  • да, стандартная консоль – typical Aug 02 '19 at 13:25
  • @typical, можете сделать скриншот? – eanmos Aug 02 '19 at 13:32
  • @typical вот мой скриншот. Посмотрите этот пост, попробуйте настроить консоль как там описано. – eanmos Aug 02 '19 at 13:56
  • @eanmos, спасибо! все отлично работает – typical Aug 02 '19 at 14:20
0

Это далеко не идеальное и не самое правильное решение, просто демонстрация ещё одной возможности:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <getopt.h>
#include <iconv.h>

#define MAX_PATH_LENGTH 256
#define MAX_CHARSET_SEQUENCE_LENGTH 4

#ifdef _WIN32
char* from_encoding = "cp866";
char* to_encoding = "cp1251";
#else
char* from_encoding = "utf-8";
char* to_encoding = "utf-8";
#endif

int decode(const char* source, char* destination, size_t size) {
  #ifdef _WIN32
  size_t src_len = strlen(source);

  iconv_t conv = iconv_open(to_encoding, from_encoding);
  if (conv == (iconv_t) -1)
    return -1;

  size_t r = iconv(conv, (char**) &source, &src_len, &destination, &size);
  iconv_close(conv);

  if (r != -1)
    *destination = 0;

  return r;
  #else
  strncpy(destination, source, size);
  return 1;
  #endif
}

void parse_args(const int argc, char** argv) {
  const struct option options[] = {
    {"encoding.console", optional_argument, NULL, 0},
    {"encoding.files", optional_argument, NULL, 0},
    {NULL, 0, NULL, 0}
  };

  int c, i;
  while ((c = getopt_long(argc, argv, "", options, &i)) != -1) {
    switch (c) {
      case 0:
        if (strcmp("encoding.console", options[i].name) == 0)
          from_encoding = optarg;
        if (strcmp("encoding.files", options[i].name) == 0)
          to_encoding = optarg;
        break;
      default:
        break;
    }
  }
}

int main(int argc, char* argv[]) {
  setlocale(LC_ALL, "");

  parse_args(argc, argv);

  char file_name[MAX_PATH_LENGTH + 1];

  fgets(file_name, MAX_PATH_LENGTH, stdin);
  file_name[strcspn(file_name, "\r\n")] = 0;

  char decoded[MAX_PATH_LENGTH * MAX_CHARSET_SEQUENCE_LENGTH + 1];
  if (decode(file_name, decoded, MAX_PATH_LENGTH * MAX_CHARSET_SEQUENCE_LENGTH) == -1) {
      fprintf(stderr, "Не удалось перекодировать имя файла\n");
      return EXIT_FAILURE;
  }

  FILE* fh = fopen(decoded, "r");
  if (fh == NULL) {
      fprintf(stderr, "Файл %s не найден\n", decoded);
      return EXIT_FAILURE;
  }

  printf("Файл успешно открыт\n");

  fclose(fh);

  return EXIT_SUCCESS;
}
  • вау, столько кода, благодарю за старания, но я в си буквально пару дней, хоть и знаю еще один си-подобный яп, но даже так довольно сложно выглядит)) вариант @eanmos мне подошел, но там с визуализацией символов проблемы, т.к. получается бред, а не русские буквы – typical Aug 02 '19 at 06:57
  • @typical обычно, чем более низкого уровня язык, тем больше кода требуется для решения даже простых задач. А кодировки, локали и время - это задачи непростые. С ними всегда много головняка, особенно при желании делать кроссплатформенное ПО. – Sergey Gornostaev Aug 02 '19 at 07:01