Как предотвратить кодирование аргумента командной строки?

(Проблема решена, смотрите обновления)

У меня есть файлы с неправильными именами из-за проблем с кодировкой. Поэтому я хочу написать скрипт Python для его обработки. Однако я столкнулся со странной проблемой.

Чтобы лучше проиллюстрировать, я приведу пример: имя файла отображается как ¹þÀï·ÑÇ.mp3. Однако следующий результат отличается:

# only one mp3 file is in this directory:
$ ls *mp3  | hexdump 
0000000 c2 b9 c3 be 41 cc 80 69 cc 88 41 cc 82 c2 b7 4e
0000010 cc 83 43 cc a7 2e 6d 70 33 0a                  
000001a

$ echo "¹þÀï·??Ç.mp3"  | hexdump 
0000000 c2 b9 c3 be c3 80 c3 af c3 82 c2 b7 c3 91 c3 87
0000010 2e 6d 70 33 0a                                 
0000015

По сути, вторая строка (или байты) — это та, которую я хотел, но в моем скрипте Python аргументы командной строки всегда дают мне первую строку. У меня нет возможности обойти. Я заметил, что это происходит только в Mac OS X. Следовательно, я подозреваю, что аргумент каким-то образом закодирован или обработан с помощью bash/system/python. Вот список моих инструментов:

  • Питон: 2.7.2
  • ОС: Mac OS X 10.6.7
  • Оболочка: GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)

Обновление: следующий код хорошо работает в моем Arch Linux, но страдает от описанной выше проблемы в моей Mac OS X:

#!/usr/bin/env python

import sys 
import os
for name in sys.argv[1:]:
    try:
        # This line does the magic:
        new_name = name.decode('utf8').encode('latin-1').decode('gbk')
        new_name_utf8 = new_name.encode('utf8')
        if name != new_name_utf8:
            print "%s -> %s" % (name, new_name_utf8)
            os.rename(name, new_name)
    except:
        print "Ignoring %s" % name

В оболочке запустите:

$ ./the_script *mp3 # Let bash pass the file name string

Вы можете запустить приведенный выше код для строки ¹þÀï·ÑÇ.mp3, и она должна быть правильно идентифицирована как 哈里路亚.mp3. Обратите внимание, что для правильного отображения у вас должна быть локаль UTF-8 и правильный китайский шрифт, поддерживающий Unicode, или проверьте следующее изображение:

Исходное имя файла

К вашему сведению: имя файла в кодировке GBK не распознается моей программой загрузки и интерпретируется как строка Unicode, которая кодируется как UTF-8. Байт, отличный от ascii, в исходном файле интерпретируется как кодовая точка Unicode и кодируется с использованием UTF-8, что вызывает проблему.

Update2: скрипт, переносимый между Mac и Linux, теперь загружен здесь.


person Ivan Xiao    schedule 23.07.2011    source источник
comment
Я подозреваю, что кодирование происходит по-разному в самом терминале/оболочке, и Python не имеет над ним контроля. Можете ли вы сделать пример, в котором скрипт Python вызывает ваш собственный скрипт Python через вызов подпроцесса и передает аргумент командной строки в качестве аргумента функции, вместо того, чтобы пытаться сделать это непосредственно из терминала.   -  person Mikko Ohtamaa    schedule 24.07.2011
comment
Ваш сценарий перебирает сам каталог или вы передаете имена файлов (по отдельности или в виде подстановочного знака) сценарию в командной строке?   -  person John Flatness    schedule 24.07.2011
comment
@Джон, я передаю аргументы. Одно наблюдение: я могу использовать обе строки для open файла.   -  person Ivan Xiao    schedule 24.07.2011
comment
Почему конкретная последовательность байтов важна для вас? Оба этих дампа представляют собой строки UTF-8, только второй, более короткий, предварительно составлен (использует прямые кодовые точки для символов с диакритическими знаками), а первый, более длинный, имеет отдельные комбинированные метки.   -  person John Flatness    schedule 24.07.2011
comment
Не могли бы вы опубликовать свой код Python? Пробовали ли вы использовать разные оболочки, например. csh или sh?   -  person William Niu    schedule 24.07.2011
comment
@John Потому что для меня это просто важно: закодированный поток UTF8 не имеет никакого значения. Я должен использовать поток байтов правильного (2-го) для обработки. В этом смысле две версии могут быть выведены как одна и та же строка, но для меня они совершенно разные.   -  person Ivan Xiao    schedule 25.07.2011
comment
@William Niu Пожалуйста, проверьте исходный пост. Пробовал fish, csh, zsh, безрезультатно.   -  person Ivan Xiao    schedule 25.07.2011


Ответы (2)


Проблема в том, что файловая система MacOS X по умолчанию изменяет все имена файлов, которые вы ей даете, на необычную форму нормализации, которая не использует предварительно составленные символы. Модуль unicodedata Python позволяет выполнять преобразование между этими формами, например:

import unicodedata
print len(unicodedata.normalize("NFD", u"\u00C7"))
print len(unicodedata.normalize("NFC", u"\u00C7"))

Они печатают 2 и 1 соответственно.

person jilles    schedule 24.07.2011
comment
Именно тот ответ, который я ищу! Теперь проблема решена!!! Спасибо большое! - person Ivan Xiao; 25.07.2011

Как насчет такого:

J=1
for I in * ; do
    mv -i "$I" "$J"
    J=$((J+1))
done

Это перебирает все файлы и переименовывает их в последовательные номера, поэтому вы избавляетесь от проблемных символов.

person Tomas    schedule 24.07.2011
comment
Это не решает проблему, я хочу обнаружить исходное имя файла. Пожалуйста, проверьте мое обновление. - person Ivan Xiao; 25.07.2011