Помилка 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друку позиції незаконної послідовності байтів? Або хтось знає, що таке незаконна послідовність байтів?


2
Незаконна послідовність байтів звучить як щось, що ви отримуєте під час годування 8-бітової ассії до чогось, що очікує utf-8.
Клас Ліндбак

36
Чи можете ви спробувати:LC_CTYPE=C && LANG=C && sed command
anubhava

5
Дякую людям. Це була LANGріч.
Зітхніть

3
@ user2719058: BSD sed(як це також використовується в OS X) вимагає -i ''(окремий параметр-аргумент із порожнім рядком) для оновлення на місці без резервного файлу; з GNU sed, тільки -iсам по собі працює - див stackoverflow.com/a/40777793/45375
mklement0

1
Плюс один для мови LANG. Добре горе, це незрозуміле, неочевидне і напрочуд важке для дослідження.
Спудлі

Відповіді:


300

Зразок команди, що проявляє симптом: 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, із àпредставленим як один байт (через ANSI-C, цитовану рядок bash ( $'...'), який використовується \x{e0}для створення байта):

Зауважте, що sedкоманда фактично є неоперативною, яка просто передає вхід, але нам це потрібно, щоб спровокувати помилку:

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

Щоб перетворити вхідний BACK в ISO-8859-1обробку, просто передайте результат іншій iconvкоманді:

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

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 = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'". Тут пояснюється пріоритет для моїх неуважних невігласів: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox

142

Додайте наступні рядки до файлу ~/.bash_profileчи ~/.zshrcфайлів.

export LC_CTYPE=C 
export LANG=C

29
це насправді працює, але ви могли б пояснити чому?
Хоанг Фам

11
@HoangPham: налаштування LC_CTYPEдля того, щоб Cкожен байт у рядках був його власним символом без застосування будь-яких правил кодування. Оскільки порушення правил кодування (UTF-8) спричинило первісну проблему, це змушує проблему усунутись. Однак ціна, яку ви платите, полягає в тому, що оболонка та утиліти розпізнають лише основні англійські літери (ті, що знаходяться в 7-бітовому діапазоні ASCII). Дивіться мою відповідь для отримання додаткової інформації.
mklement0

6
Якщо встановити це постійно у файлах запуску вашої оболонки, вимкнете багато корисних способів поведінки. Ви хочете поставити це лише для окремих команд, які цього абсолютно вимагають.
трійчатка

4
Занадто небезпечний може спричинити несподівані наслідки. Можна використовувати LC_CTYPE=C sed …, тобто лише для команди sed.
Йонгвей Ву

2
Це повністю відключить підтримку символів Unicode у вашій оболонці. До побачення емоджи, фантазійні символи малювання рядків, букви з наголосами, .... Набагато краще просто встановити це лише для команди sed, як описано в інших відповідях.
асмеурер

6

У моєму вирішенні було використано Perl:

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

Цей чудово працює. І у мене не було помилок, уникаючи спеціальних символів на відміну від інших. Попередні задавали мені такі питання, як "sed: RE помилка: незаконна послідовність байтів" або sed: 1: "path_to_file": недійсний код команди.
JMags1632

3

Відповідь mklement0 чудова, але у мене є невеликі зміни.

Здається гарною ідеєю чітко вказати bashкодування при використанні iconv. Крім того, нам слід встановити позначку порядку байтів ( навіть якщо стандарт unicode не рекомендує ), оскільки між 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 використовується псевдо-BOM 0xef 0xbb 0xff ; за визначенням, UTF-8 не потребує BOM і не рекомендується (як ви заявляєте); за межами світу 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, з локальним на v='ä'; echo "${#v}"базі UTF-8, використовуючи: оболонку з інформацією про UTF-8, слід звітувати 1; тобто він повинен розпізнавати багатобайтову послідовність ä( 0xc3 0xa4), як єдиний символ. Можливо , що ще більш важливо, однак: стандартні утиліти ( sed, awk, cut...) також повинні бути локаль / кодує-курс, і в той час як більшість з них на сучасних Unix-подібні платформи, є винятки, наприклад, awkна OSX, і cutна Linux.
mklement0

1
Це похвально, що fileрозпізнає псевдо-BOM псевдо UTF-8, але проблема полягає в тому, що більшість утилітів Unix, які обробляють файл, не роблять , а зазвичай ламаються або принаймні погано поводяться, стикаючись з ним. Без BOM, 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.txt | sed 's / something / àéèêçùû / g' | .....

-f варіант - це "від" набору коду, а -t - перетворення "в".

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

UTF8-MAC - це сучасний набір кодів OS Mac для перетворення.


Також див. Імена iconv та шаблонів у списку розсилки iconv.
jww

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

Я О.Д. , щоб пролити певне світло: Зверніть увагу на 374 на півдорозі вниз цієї ода -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

Ви можете бачити трійку поруч із символом 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

У моєму вирішенні було використано gnu sed. Працював чудово для моїх цілей.


Дійсно, GNU sed - це варіант, якщо ви хочете ігнорувати недійсні байти у потоці введення (немає необхідності у LC_ALL=C sed ...вирішенні), оскільки GNU sedпросто передає недійсні байти замість повідомлення про помилку, але зауважте, що якщо ви хочете правильно розпізнати та обробити всі символів у рядку введення, неможливо спочатку змінити кодування входу (як правило, з iconv).
mklement0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.