Что такое кодировка argv? - PullRequest
38 голосов
/ 23 марта 2011

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

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

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

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

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

Windows

Имена файлов хранятся на диске в некотором формате 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 сменить язык.

Ответы [ 4 ]

20 голосов
/ 08 апреля 2011

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

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

  2. В Unix, argv не имеет фиксированной кодировки:

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

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

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

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

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

6 голосов
/ 24 марта 2011

Пока я могу говорить только о 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 всегда будет содержать аргументы в кодовой странице current приложение, поскольку информация о том, какую кодовую страницу (L1) использовала оригинальная программа, была необратимо потеряна на промежуточном этапе UTF-16.

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

2 голосов
/ 08 апреля 2011

Вывод вашего тестового приложения нуждался в некоторых модификациях, чтобы иметь какой-либо смысл, вам нужны шестнадцатеричные коды и вам нужно избавиться от отрицательных значений.Или вы не можете печатать такие вещи, как специальные символы 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 box, который использует UTF-8Я получаю этот вывод.

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

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

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

$> env | grep LANG
LANG=en_US.utf8

Надеюсь, это немного прояснит ситуацию с Linux.

/ Удачи

1 голос
/ 08 апреля 2011

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

...