Ошибка 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 для печати позиции незаконной последовательности байтов? Или кто-нибудь знаете, что такое незаконная последовательность байтов?

5 ответов


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

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

на такой же эффект может быть 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_* - переменные категории, такие как LC_CTYPE. Таким образом, самый надежный подход-установить LC_ALL.

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

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

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

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


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

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

пример:

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

здесь демонстрация проблема используя строку voilà кодируется как ISO-8859-1 С à представлены один байт (через ANSI-C-цитируемая строка bash ($'...'), которая использует \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 - более сложный подход, необходимый для конкретного определения недопустимых байтов в UTF-8.)


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

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

примеры:

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

  # 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}')"

для преобразования входных данных обратно в ISO-8859-1 после обработки просто передайте результат другому :

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

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

export LC_CTYPE=C 
export LANG=C

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

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

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

# Find out MY_FILE's encoding
# We'll convert back to this at the end
FILE_ENCODING="$( file --brief --mime-encoding MY_FILE )"
# Find out bash's 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

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

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

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