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

184

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

jww
источник
2
Неверная последовательность байтов звучит как что-то, что вы получаете при подаче 8-битного ascii чего-то, что ожидает utf-8.
Клас Линдбек
36
Можете ли вы попробовать:LC_CTYPE=C && LANG=C && sed command
Анубхава
5
Спасибо, ребята. Это была LANGвещь. Вздох ....
13
3
@ user2719058: BSD sed(как также используется в OS X) требует -i ''(отдельный параметр-аргумент с пустой строкой) для обновления на месте без файла резервной копии; с GNU sed, только -iсамо по себе работает - см. stackoverflow.com/a/40777793/45375
mklement0
1
Плюс один для LANG вещь. Боже мой, это неясно, неочевидно и удивительно трудно для исследования.
Спадли

Ответы:

301

Пример команды, которая демонстрирует симптом: sed 's/./@/' <<<$'\xfc'терпит неудачу, потому что байт 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_*переменные -категории, такие как LC_CTYPE. Таким образом, наиболее надежный подход заключается в установке LC_ALL.

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

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

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

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


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

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

Пример:

Буква с акцентом àимеет код Unicode 0xE0(224) - такой же, как и в ISO-8859-1. Однако из - за характера UTF-8 кодировке, этот единственный элемент кода представлен в виде 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перечисляет все поддерживаемые.

Примеры:

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

  # 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
mklement0
источник
4
Я бы сказал, что это гораздо лучший вариант. Во-первых, я бы не хотел потерять многоязычную поддержку во всех терминалах. Во-вторых, принятый ответ ощущается как глобальное решение локальной проблемы - чего-то, чего следует избегать.
Алекс
У меня было несколько небольших изменений в этом. Буду признателен за отзыв. stackoverflow.com/a/35046218/9636
Heath Borders
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'печатает sed: RE error: illegal byte sequenceдля меня на Сьерре. echo $LC_ALLвыходы en_US.UTF-8FWIW.
Ahcox
1
@ahcox: Да, потому что установка LC_ALL переопределяет все остальные LC_*переменные, в том числе LC_CTYPE, как объяснено в ответе.
mklement0
2
@ mklement0 Круто, это работает: "LC_ALL = Ced 's /.*/&/' <<< $ 'voil \ x {e0}'". Приоритет, объясненный здесь для моих коллег-невнимательных невежд: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox,
142

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

export LC_CTYPE=C 
export LANG=C
binarytemple_picsolve
источник
29
это на самом деле работает, но не могли бы вы объяснить, почему?
Хоанг Фам
11
@HoangPham: установка LC_CTYPEдля того, чтобы Cкаждый байт в строках был своим собственным символом без применения каких-либо правил кодирования. Поскольку нарушение правил кодирования (UTF-8) вызвало первоначальную проблему, это устраняет проблему. Однако цена, которую вы платите, заключается в том, что оболочка и утилиты распознают только буквы английского алфавита (те, что в 7-битном диапазоне ASCII). Смотрите мой ответ для более.
mklement0
6
Установка этого параметра в файлах запуска вашей оболочки отключит многие полезные действия. Вы хотите вставить это только для отдельных команд, которые абсолютно в этом нуждаются.
tripleee
4
Слишком опасный может привести к неожиданным последствиям. Можно использовать LC_CTYPE=C sed …, т.е. только по команде sed.
Юнвэй Ву
2
Это полностью отключит поддержку символов Unicode в вашей оболочке. До свидания, смайлики, необычные символы рисования линий, буквы с акцентами ... Гораздо лучше установить это только для команды sed, как описано в других ответах.
asmeurer
6

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

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
Виталий Зданевич
источник
Этот отлично работает. И у меня не было ошибок при экранировании специальных символов в отличие от других. Предыдущие проблемы вызывали такие проблемы, как «sed: RE error: недопустимая последовательность байтов» или sed: 1: «path_to_file»: неверный код команды.
JMags1632
3

Ответ mklement0 великолепен, но у меня есть небольшие хитрости.

Кажется хорошей идеей явно указать bashкодировку при использовании iconv. Кроме того, нам следует добавить метку порядка байтов ( даже если стандарт Юникода не рекомендует этого ), потому что между UTF-8 и ASCII могут быть допустимые путаницы без метки порядка байтов . К сожалению, iconvне предшествует метке порядка байтов, когда вы явно указываете порядковый номер ( UTF-16BEили UTF-16LE), поэтому нам нужно использовать UTF-16, который использует платформенно-зависимый порядковый номер, а затем использовать file --mime-encodingдля обнаружения истинного 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
Хит Границы
источник
1
++ для полезных методов, особенно file -b --mime-encodingдля обнаружения и сообщения о кодировке файла. Однако есть некоторые аспекты, на которые стоит обратить внимание, о чем я расскажу в отдельных комментариях.
mklement0
2
Я думаю, можно с уверенностью сказать, что мир Unix охватил UTF-8 на этом этапе: LC_CTYPEобычно значением по умолчанию является значение <lang_region>.UTF-8, поэтому любой файл без BOM (метка порядка байтов) поэтому интерпретируется как файл UTF-8. Только в мире Windows используется псевдо-спецификация 0xef 0xbb 0xff ; по определению UTF-8 не нуждается в спецификации и не рекомендуется (как вы заявляете); вне мира Windows эта псевдо-BOM приводит к поломке .
mklement0
2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): это сделано по замыслу: если вы указываете порядковый номер явно , нет необходимости также отражать его через спецификацию, поэтому ничего не добавляется.
mklement0
1
Re LC_*/ LANGпеременные: bash, ksh, и zsh(возможно , и другие, но не dash ) уважают кодировку; проверьте в POSIX-подобных оболочках с языком на основе UTF-8 с v='ä'; echo "${#v}": оболочка с поддержкой UTF-8 должна сообщать 1; т.е. он должен распознавать многобайтовую последовательность ä( 0xc3 0xa4) как один символ. Возможно , что еще более важно, однако: стандартные утилиты ( sed, awk, cut...) также должны быть локаль / кодирующей-курс, и в то время как большинство из них на современных Unix-подобные платформы, есть исключения, например, awkна OSX, и cutв линуксе.
mklement0
1
Похвально, что fileраспознается псевдо-BOM UTF-8, но проблема в том, что большинство утилит Unix, которые обрабатывают файл, этого не делают , и обычно ломаются или, по крайней мере, плохо себя ведут , когда сталкиваются с ним. Без спецификации fileправильно идентифицирует все 7-битные байтовые файлы как ASCII, а файл с действительными многобайтовыми символами UTF-8 - как UTF-8. Прелесть UTF-8 в том, что он является надмножеством ASCII: любой действительный файл ASCII по определению является действительным файлом UTF-8 (но не наоборот); совершенно безопасно обрабатывать ASCII-файл как UTF-8 (технически это так, он не содержит многобайтовых символов)
mklement0
2

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

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

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

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

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

Денис из Валь Торанс
источник
Также смотрите имена iconv и charset в списке рассылки iconv.
19
1

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

$ uname -a
Darwin Adams-iMac 18.7.0 Darwin Kernel Version 18.7.0: Tue Aug 20 16:57:14 PDT 2019; root:xnu-4903.271.2~2/RELEASE_X86_64 x86_64

Я получил часть способа ответить на вышесказанное, просто используя tr .

У меня есть файл .csv, который является выпиской по кредитной карте, и я пытаюсь импортировать его в Gnucash. Я живу в Швейцарии, поэтому мне приходится иметь дело с такими словами, как Цюрих. Подозреваю, что Gnucash не любит "" в числовых полях, я решил просто заменить все

; ;

с участием

;;

Поехали:

$ head -3 Auswertungen.csv | tail -1 | sed -e 's/; ;/;;/g'
sed: RE error: illegal byte sequence

Я использовал od, чтобы пролить свет: обратите внимание на 374 на полпути вниз по этому выводу od -c

$ head -3 Auswertungen.csv | tail -1 | od -c
0000000    1   6   8   7       9   6   1   9       7   1   2   2   ;   5
0000020    4   6   8       8   7   X   X       X   X   X   X       2   6
0000040    6   0   ;   M   Y       N   A   M   E       I   S   X   ;   1
0000060    4   .   0   2   .   2   0   1   9   ;   9   5   5   2       -
0000100        M   i   t   a   r   b   e   i   t   e   r   r   e   s   t
0000120                Z 374   r   i   c   h                            
0000140    C   H   E   ;   R   e   s   t   a   u   r   a   n   t   s   ,
0000160        B   a   r   s   ;   6   .   2   0   ;   C   H   F   ;    
0000200    ;   C   H   F   ;   6   .   2   0   ;       ;   1   5   .   0
0000220    2   .   2   0   1   9  \n                                    
0000227

Тогда я подумал, что мог бы попытаться убедить tr заменить 374 на любой правильный байт-код. Итак, сначала я попробовал что-то простое, которое не сработало, но побочным эффектом показало, где находится проблемный байт:

$ head -3 Auswertungen.csv | tail -1 | tr . .  ; echo
tr: Illegal byte sequence
1687 9619 7122;5468 87XX XXXX 2660;MY NAME ISX;14.02.2019;9552 - Mitarbeiterrest   Z

Вы можете увидеть Tr поручительств в 374 символа.

Использование Perl, кажется, позволяет избежать этой проблемы.

$ head -3 Auswertungen.csv | tail -1 | perl -pne 's/; ;/;;/g'
1687 9619 7122;5468 87XX XXXX 2660;ADAM NEALIS;14.02.2019;9552 - Mitarbeiterrest   Z?rich       CHE;Restaurants, Bars;6.20;CHF;;CHF;6.20;;15.02.2019
Волшебные бедра
источник
0

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

lu_zero
источник
Действительно, GNU sed является опцией, если вы хотите игнорировать недопустимые байты во входном потоке (нет необходимости в LC_ALL=C sed ...обходном пути), потому что GNU sedпросто пропускает недопустимые байты вместо сообщения об ошибке, но учтите, что если вы хотите правильно распознать и обработать все символов во входной строке, сначала нет способа изменить кодировку ввода (обычно с помощью iconv).
mklement0