Подтвердить что ты не робот

Что такое кодировка argv?

Мне непонятно, какие кодировки используются там, где в C argv. В частности, меня интересует следующий сценарий:

  • Пользователь использует локаль L1 для создания файла с именем N, содержит символы, отличные от ASCII
  • Позже пользователь использует локаль L2 для добавления имени файла в командной строке, который передается в программу P как аргумент командной строки

Какая последовательность байтов P видна в командной строке?

Я заметил, что в Linux, создавая имя файла в локали UTF-8, а затем заворачивая его в (например), локаль zw_TW.big5, кажется, заставляет мою программу P подавать UTF-8, а не Big5. Тем не менее, в OS X такая же серия действий приводит к тому, что моя программа P получает кодированное имя Big5.

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

Окна

Имена файлов хранятся на диске в формате Unicode. Поэтому Windows принимает имя N, преобразует из L1 (текущую кодовую страницу) в Unicode-версию N, мы будем называть N1 и сохраняет N1 на диске.

То, что я потом предполагаю, заключается в том, что при последующем заполнении вкладки имя N1 преобразуется в локаль L2 (новая текущая кодовая страница) для отображения. Если повезет, это даст исходное имя N - но это будет неверно, если N содержит символы, непредставимые в L2. Мы называем новое имя N2.

Когда пользователь на самом деле нажимает Enter для запуска P с этим аргументом, имя N2 преобразуется обратно в Unicode, снова получая N1. Этот N1 теперь доступен для программы в формате UCS2 через GetCommandLineW/wmain/tmain, но пользователи GetCommandLine/main будут видеть имя N2 в текущей локали (кодовая страница).

OS X

История дискового хранилища такая же, насколько я знаю. OS X хранит имена файлов как Unicode.

С терминалом Unicode я думаю, что происходит, что терминал строит командную строку в буфере Unicode. Поэтому, когда вы завершаете вкладку, она копирует имя файла в качестве имени файла Unicode в этот буфер.

Когда вы запускаете эту команду, этот буфер Unicode преобразуется в текущую локаль, L2 и подается в программу через argv, и программа может декодировать argv с текущей локалью в Unicode для отображения.

Linux

В Linux все по-другому, и я слишком смущен тем, что происходит. Linux хранит имена файлов в байтах, а не в Unicode. Поэтому, если вы создаете файл с именем N в локали L1, то N в качестве байтовой строки - это то, что хранится на диске.

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

Когда вы запускаете программу, я думаю, что буфер отправляется непосредственно на argv. Теперь, какая кодировка имеет argv? Это похоже на любые символы, введенные в командной строке, а в локали L2 - в кодировке L2, но имя файла будет в кодировке L1. Итак, argv содержит смесь двух кодировок!

Вопрос

Мне бы очень хотелось, чтобы кто-нибудь мог сообщить мне, что здесь происходит. Все, что у меня есть на данный момент, - это догадки и спекуляции, и это не очень хорошо сочетается. То, что я действительно хотел бы быть правдой, заключается в том, что argv должен быть закодирован на текущей кодовой странице (Windows) или текущей локали (Linux/OS X), но это, похоже, не так...

Дополнительно

Вот простая программа-кандидат P, которая позволяет вам следить за кодировками:

#include <stdio.h>

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Not enough arguments\n");
        return 1;
    }

    int len = 0;
    for (char *c = argv[1]; *c; c++, len++) {
        printf("%d ", (int)(*c));
    }

    printf("\nLength: %d\n", len);

    return 0;
}

Вы можете использовать locale -a, чтобы увидеть доступные локали, и используйте export LC_ALL=my_encoding для изменения вашей локали.

4b9b3361

Ответ 1

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

  • Как уже говорилось, в Windows argv кодируется с использованием текущей кодовой страницы. Однако вы можете получить командную строку как UTF-16 с помощью GetCommandLineW. Использование argv не рекомендуется для современных приложений Windows с поддержкой Unicode, поскольку страницы с кодом устарели.

  • В Unixes у argv нет фиксированной кодировки:

    a) Имена файлов, вложенные в tab-completion/globbing, будут выполняться в argv verbatim как точно последовательности байтов, с помощью которых они называются на диске. Это верно, даже если эти байтовые последовательности не имеют смысла в текущей локали.

    b) Вход, введенный непосредственно пользователем с использованием их IME, будет происходить в argv в кодировке локали. (Ubuntu, похоже, использует LOCALE, чтобы решить, как кодировать ввод IME, тогда как OS X использует Preference.)

Это раздражает языки, такие как Python, Haskell или Java, которые хотят обрабатывать аргументы командной строки в виде строк. Им нужно решить, как декодировать argv в любую кодировку, используемую внутренне для String (что является UTF-16 для этих языков). Однако, если они просто используют кодировку locale для выполнения этого декодирования, то допустимые имена файлов на входе могут не декодироваться, вызывая исключение.

Решение этой проблемы, принятое Python 3, представляет собой схему кодирования суррогатного байта (http://www.python.org/dev/peps/pep-0383/), которая представляет любой недокакаемый байт в argv как специальные кодовые обозначения Unicode. Когда эта кодовая точка декодируется обратно в поток байтов, она снова становится исходным байтом. Это позволяет передавать данные округления из argv, которые недействительны в текущей кодировке (то есть имя файла, названное в чем-то отличном от текущего языкового стандарта), через собственный тип строки Python и обратно в байты без потери информации.

Как вы можете видеть, ситуация довольно грязная: -)

Ответ 2

Сейчас я могу говорить только о Windows. В Windows кодовые страницы предназначены только для устаревших приложений и не используются системой или современными приложениями. Windows использует UTF-16 (и делала это целую вечность) для всего: текстовый дисплей, имена файлов, терминал, системный API. Конверсии между UTF-16 и устаревшими кодовыми страницами выполняются только на максимально возможном уровне, непосредственно на интерфейсе между системой и приложением (технически более старые функции API реализованы дважды - одна функция FunctionW, которая выполняет реальную работу и ожидает строки UTF-16 и одну функцию совместимости FunctionA, которая просто преобразует входные строки с текущей (потоковой) кодовой страницы в UTF-16, вызывает FunctionW и преобразует результаты). Завершение табуляции всегда должно выводить строки UTF-16 (это определенно происходит при использовании шрифта TrueType), поскольку консоль использует только UTF-16. Завершенное имя файла UTF-16 передается приложению. Если теперь это приложение является устаревшим приложением (т.е. Использует main вместо wmain/GetCommandLineW и т.д.), Тогда среда выполнения Microsoft C (возможно) использует GetCommandLineA для преобразования системы в командную строку. Поэтому в основном я думаю, что то, что вы говорите о Windows, является правильным (только, что, вероятно, нет никакого преобразования при выполнении табуляции): массив argv всегда будет содержать аргументы на кодовой странице текущего приложения, потому что информация о том, что кодовая страница (L1), используемая исходной программой, была необратимо потеряна во время промежуточной стадии UTF-16.

В Windows всегда делается вывод: избегайте устаревших кодовых страниц; используйте API UTF-16, где бы вы ни находились. Если вам нужно использовать main вместо wmain (например, для независимости от платформы), используйте GetCommandLineW вместо массива argv.

Ответ 3

Результат вашего тестового приложения нуждался в некоторых модификациях, чтобы иметь какой-то смысл, вам нужны шестнадцатеричные коды, и вам нужно избавиться от отрицательных значений. Или вы не можете печатать такие вещи, как специальные символы UTF-8, чтобы вы могли их прочитать.

Сначала измененный SW:

#include <stdio.h>

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Not enough arguments\n");
        return 1;
    }

    int len = 0;
    for (unsigned char *c = argv[1]; *c; c++, len++) {
        printf("%x ", (*c));
    }

    printf("\nLength: %d\n", len);

    return 0;
}

Затем в моем поле Ubuntu, использующем UTF-8, я получаю этот вывод.

$> gcc -std=c99 argc.c -o argc
$> ./argc 1ü
31 c3 bc 
Length: 3

И здесь вы можете видеть, что в моем случае ü закодировано более чем на 2 символа, и что 1 является одиночным char. Более или менее точно, что вы ожидаете от кодировки UTF-8.

И это фактически соответствует тому, что есть в переменной env LANG.

$> env | grep LANG
LANG=en_US.utf8

Надеемся, что это немного пояснит случай с Linux.

/Удача

Ответ 4

Да, пользователи должны быть осторожны при смешивании локалей в Unix в целом. Диспетчер файлов GUI, отображающий и изменяющий имена файлов, также имеет эту проблему. В Mac OS X стандартная кодировка Unix - UTF-8. Фактически файловая система HFS + при вызове через Unix-интерфейсы применяет имена файлов UTF-8, потому что она должна конвертировать ее в UTF-16 для хранения в самой файловой системе.