Ошибка RE: незаконная последовательность байтов в Mac OS X

Я пытаюсь заменить строку в Makefile в Mac OS X для кросс-компиляции в iOS. Строка содержит встроенные двойные кавычки. Команда:

sed -i "" 's|"iphoneos-cross","llvm-gcc:-O3|"iphoneos-cross","clang:-Os|g' Configure

И ошибка:

sed: RE error: illegal byte sequence

Я пытался избежать двойных кавычек, запятых, тире и двоеточий без радости. Например:

sed -i "" 's|\"iphoneos-cross\"\,\"llvm-gcc\:\-O3|\"iphoneos-cross\"\,\"clang\:\-Os|g' Configure

У меня чертовски время отладить проблему. Кто-нибудь знает, как получить sed для печати позиции незаконной последовательности байтов? Или кто-нибудь знает, что такое незаконная последовательность байтов?

Ответы

Ответ 1

Пример команды с симптомом: sed 's/./@/' <<<$'\xfc' завершается неудачно, поскольку байт 0xfc не является допустимым 0xfc UTF-8.
Обратите внимание, что, напротив, GNU sed (Linux, но также устанавливается на macOS) просто пропускает недействительный байт, не сообщая об ошибке.

Использование ранее принятого ответа - вариант, если вы не против потерять поддержку своего истинного языка (если вы работаете в системе США и вам никогда не нужно иметь дело с иностранными символами, это может быть хорошо).

Тем не менее, тот же эффект может иметь место ad-hoc только для одной команды:

LC_ALL=C sed -i "" 's|"iphoneos-cross","llvm-gcc:-O3|"iphoneos-cross","clang:-Os|g' Configure

Примечание: важна эффективная настройка LC_CTYPE для C, поэтому LC_CTYPE=C sed... обычно также будет работать, но если для LC_ALL будет установлено значение (отличное от C), он будет переопределять отдельные LC_* -category такие как LC_CTYPE. Таким образом, самый надежный подход - установить LC_ALL.

Однако (эффективно) установка LC_CTYPE в C обрабатывает строки так, как если бы каждый байт был своим собственным символом (не выполняется интерпретация на основе правил кодирования), без учета кодирования UTF-8 - multibyte-on-demand - которое использует OS X по умолчанию, где иностранные символы имеют многобайтовые кодировки.

В двух словах: установка LC_CTYPE в C заставляет оболочку и утилиты распознавать только основные английские буквы как буквы (те, которые находятся в 7-битном диапазоне ASCII), так что внешние символы. не будут рассматриваться как буквы, что приведет, например, к неудачным преобразованиям upper-/строчными буквами.

Опять же, это может быть хорошо, если вам не нужно сопоставлять многобайтовые символы, такие как é, и просто хотите пропустить такие символы.

Если этого недостаточно и/или вы хотите понять причину исходной ошибки (включая определение того, какие входные байты вызвали проблему) и выполнить преобразования кодирования по требованию, читайте ниже.


Проблема в том, что кодировка входного файла не соответствует оболочке.
Более конкретно, входной файл содержит символы, закодированные таким образом, который недопустим в UTF-8 (как @Klas Lindbäck заявил в комментарии) - это то, что пытается сказать сообщение об ошибке sed с помощью invalid byte sequence.

Скорее всего, ваш входной файл использует однобайтовую 8-битную кодировку, такую как ISO-8859-1, часто используемую для кодирования "западноевропейских" языков.

Пример:

Буква с акцентом à имеет кодовую 0xE0 Unicode 0xE0 (224) - так же, как в ISO-8859-1. Однако из-за характера кодирования UTF-8 эта единственная 0xC3 0xA0 представлена в виде 2 байтов - 0xC3 0xA0, тогда как попытка передать один байт 0xE0 недопустима в UTF-8.

Здесь демонстрация проблемы с использованием строки voilà закодированной как ISO-8859-1, с à представленной одним байтом (через строку bash в кавычках ANSI-C ($'...'), которая использует \x{e0} создать байт):

Обратите внимание на то, что команда sed по сути является no-op, которая просто пропускает ввод, но она нам нужна, чтобы вызвать ошибку:

  # -> 'illegal byte sequence': byte 0xE0 is not a valid char.
sed 's/.*/&/' <<<$'voil\x{e0}'

Чтобы просто проигнорировать проблему, можно использовать вышеуказанный LCTYPE=C:

  # No error, bytes are passed through ('á' will render as '?', though).
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'

Если вы хотите определить, какие части ввода вызывают проблему, попробуйте следующее:

  # Convert bytes in the 8-bit range (high bit set) to hex. representation.
  # -> 'voil\x{e0}'
iconv -f ASCII --byte-subst='\x{%02x}' <<<$'voil\x{e0}'

Вывод покажет вам все байты с установленным старшим битом (байты, которые превышают 7-битный диапазон ASCII) в шестнадцатеричной форме. (Тем не менее, обратите внимание, что это также включает в себя правильно закодированные многобайтовые последовательности UTF-8 - потребуется более сложный подход для конкретной идентификации байтов invalid-in-UTF-8.)


Выполнение кодирования преобразований по требованию:

Стандартная утилита iconv может использоваться для преобразования в (-t) и/или из (-f) кодировок; iconv -l перечисляет все поддерживаемые.

Примеры:

Преобразование из ISO-8859-1 в действующую кодировку в оболочке (на основе LC_CTYPE, по умолчанию UTF-8 -based), основываясь на приведенном выше примере:

  # Converts to UTF-8; output renders correctly as 'voilà'
sed 's/.*/&/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')"

Обратите внимание, что это преобразование позволяет вам правильно сопоставлять иностранные символы:

  # Correctly matches 'à' and replaces it with 'ü': -> 'voilü'
sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')"

Чтобы преобразовать ввод BACK в ISO-8859-1 после обработки, просто передайте результат в другую команду iconv:

sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1

Ответ 2

Добавьте следующие строки в ~/.bash_profile или ~/.zshrc файл (ы).

export LC_CTYPE=C 
export LANG=C

Ответ 3

mklement0 answer отлично, но у меня есть небольшие настройки.

Кажется хорошей идеей явно указать bash кодировку при использовании iconv. Кроме того, мы должны добавить знак байтового байта (хотя стандарт unicode не рекомендует его), потому что может быть законные путаницы между UTF-8 и ASCII без знака байтового порядка. К сожалению, iconv не добавляет знак байтового байта, когда вы явно указываете endianness (UTF-16BE или UTF-16LE), поэтому нам нужно использовать UTF-16, который использует специфичность для платформы, а затем используйте file --mime-encoding, чтобы обнаружить истинную сущность iconv.

(Я задерживаю все свои кодировки, потому что, когда вы перечисляете все поддерживаемые iconv кодировки с помощью iconv -l, они все в верхнем регистре.)

# Find out MY_FILE encoding
# We'll convert back to this at the end
FILE_ENCODING="$( file --brief --mime-encoding MY_FILE )"
# Find out bash encoding, with which we should encode
# MY_FILE so sed doesn't fail with 
# sed: RE error: illegal byte sequence
BASH_ENCODING="$( locale charmap | tr [:lower:] [:upper:] )"
# Convert to UTF-16 (unknown endianness) so iconv ensures
# we have a byte-order mark
iconv -f "$FILE_ENCODING" -t UTF-16 MY_FILE > MY_FILE.utf16_encoding
# Whether we're using UTF-16BE or UTF-16LE
UTF16_ENCODING="$( file --brief --mime-encoding MY_FILE.utf16_encoding )"
# Now we can use MY_FILE.bash_encoding with sed
iconv -f "$UTF16_ENCODING" -t "$BASH_ENCODING" MY_FILE.utf16_encoding > MY_FILE.bash_encoding
# sed!
sed 's/.*/&/' MY_FILE.bash_encoding > MY_FILE_SEDDED.bash_encoding
# now convert MY_FILE_SEDDED.bash_encoding back to its original encoding
iconv -f "$BASH_ENCODING" -t "$FILE_ENCODING" MY_FILE_SEDDED.bash_encoding > MY_FILE_SEDDED
# Now MY_FILE_SEDDED has been processed by sed, and is in the same encoding as MY_FILE

Ответ 4

Мой обходной путь использовал Perl:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'

Ответ 5

Вы просто должны передать команду iconv перед командой sed. Например, с вводом file.txt:

iconv -f ISO-8859-1 -t UTF8-MAC file.txt | Sed 's/что-то/àéèêçùû /g' |...

Опция -f - это набор кодов "из", а опция -t - это преобразование кодового набора "в".

Позаботьтесь о том, чтобы веб-страницы обычно отображались в нижнем регистре, например <charset = iso-8859-1 "/>, а iconv использует верхний регистр. В вашей системе есть список поддерживаемых наборов кодов iconv с помощью команды iconv -l

UTF8-MAC - это современный OS Mac кодовый набор для конвертации.

Ответ 6

В моем обходном пути использовался gnu sed. Работал хорошо для моих целей.

Ответ 7

Просто используйте каналы из внешней системы в macOS

... | iconv  -f ISO-8859-1 -t UTF8-MAC | sed 's/a/A/g'|....

или во внешнюю систему из macOS

... | sed 's/a/A/g'| iconv  -f UTF8-MAC t- ISO-8859-1 |....

В этом примере

UTF8-MAC - стандартная современная кодировка macOS

ISO-8859-1 - западная западная кодировка

список кодировок командной строки в вашей системе с

iconv -l