Błąd RE: niedozwolona sekwencja bajtów w systemie Mac OS X

184

Próbuję zastąpić ciąg znaków w pliku Makefile w systemie Mac OS X w celu kompilacji krzyżowej do systemu iOS. Ciąg ma osadzone podwójne cudzysłowy. Polecenie to:

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

Błąd to:

sed: RE error: illegal byte sequence

Próbowałem uciec od podwójnych cudzysłowów, przecinków, myślników i dwukropków bez radości. Na przykład:

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

Mam problem z debugowaniem problemu. Czy ktoś wie, jak sedwydrukować pozycję nielegalnej sekwencji bajtów? A może ktoś wie, co to jest nielegalna sekwencja bajtów?

jww
źródło
2
Nielegalna sekwencja bajtów brzmi jak coś, co dostajesz, gdy podajesz 8-bitowy ascii do czegoś, co oczekuje utf-8.
Klas Lindbäck,
36
Czy możesz spróbować:LC_CTYPE=C && LANG=C && sed command
anubhava
5
Dzięki ludzie. O to chodziło LANG. Westchnienie ....
jww
3
@ user2719058: BSD sed(jak również używane w OS X) wymaga -i ''(osobnego argumentu opcji-pustego ciągu) do aktualizacji w miejscu bez pliku kopii zapasowej; z GNU działa sedtylko -isam - patrz stackoverflow.com/a/40777793/45375
mklement0
1
Plus jeden za LANG. Dobry żal, który jest niejasny, nieoczywisty i zaskakująco trudny do zbadania.
Spudley,

Odpowiedzi:

300

Przykładowa komenda, która wykazuje objaw: sed 's/./@/' <<<$'\xfc'kończy się niepowodzeniem, ponieważ bajt 0xfcnie jest prawidłowym znakiem UTF-8.
Zauważ, że dla kontrastu, GNU sed (Linux, ale także instalowalny na macOS) po prostu przesyła nieprawidłowy bajt bez zgłaszania błędu.

Użycie poprzednio przyjętej odpowiedzi jest opcją, jeśli nie masz nic przeciwko utracie wsparcia dla prawdziwych ustawień regionalnych (jeśli korzystasz z systemu amerykańskiego i nigdy nie musisz zajmować się obcymi postaciami, może to być w porządku).

Jednak sam efekt można było ad-hoc dla pojedynczego polecenia tylko :

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

Uwaga: Liczy się to skuteczne LC_CTYPE ustawienie C, tak LC_CTYPE=C sed ...by normalnie też praca, ale jeśli LC_ALLdzieje się zestaw (do czegoś innego niż C), to zastąpi poszczególne LC_*zmienne -category takie jak LC_CTYPE. Zatem najbardziej niezawodnym podejściem jest ustawienie LC_ALL.

Jednak (skutecznie) ustawienie LC_CTYPEdo Ctraktuje ciągi jakby każdy bajt był jego własny charakter ( nie interpretacja w oparciu o zasady kodowania jest wykonana), ze bez względu na - wielobajtowych-on-demand - kodowanie UTF-8 , że OS X wykorzystuje domyślnie , w których znaki obce mają kodowanie wielobajtowe .

W skrócie: ustawienie LC_CTYPEdoC przyczyn skorupę i narzędzia do rozpoznawania tylko podstawowe litery angielskich jak listy (te w 7-bitowego zakresu ASCII), dzięki czemu obcych znaków. nie będą traktowane jak litery , co spowoduje na przykład konwersję wielkich / małych liter.

Ponownie, może to być w porządku, jeśli nie musisz dopasowywać znaków zakodowanych w wielobajtach, takich jak é, i po prostu chcesz przepuścić takie znaki .

Jeśli jest to niewystarczające i / lub chcesz zrozumieć przyczynę pierwotnego błędu (w tym określić, które bajty wejściowe spowodowały problem) i wykonać konwersje kodowania na żądanie, przeczytaj poniżej.


Problem polega na tym, że kodowanie pliku wejściowego nie jest zgodne z kodowaniem powłoki.
Mówiąc dokładniej, plik wejściowy zawiera znaki zakodowane w sposób, który nie jest poprawny w UTF-8 (jak stwierdził @Klas Lindbäck w komentarzu) - to właśnie sedpróbuje przekazać komunikat o błędzie invalid byte sequence.

Najprawdopodobniej plik wejściowy używa 8-bitowego kodowania jednobajtowego, na przykład ISO-8859-1często używanego do kodowania języków „zachodnioeuropejskich”.

Przykład:

Akcentowana litera àma kod Unicode 0xE0(224) - taki sam jak w ISO-8859-1. Jednak ze względu na naturę kodowania UTF-8 ten pojedynczy punkt kodowy jest reprezentowany jako 2 bajty - 0xC3 0xA0podczas gdy próba przekazania pojedynczego bajtu 0xE0 jest nieprawidłowa w UTF-8.

Oto demonstracja problemu przy użyciu łańcucha voilàzakodowanego jako ISO-8859-1, z àreprezentowanym jako jeden bajt (za pomocą ciągu bash cytowanego w ANSI-C ( $'...'), który używa \x{e0}do utworzenia bajtu):

Zauważ, że sedpolecenie to faktycznie nie działa, po prostu przekazuje dane wejściowe, ale potrzebujemy go, aby wywołać błąd:

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

Aby po prostu zignorować problem , LCTYPE=Cmożna zastosować powyższe podejście:

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

Jeśli chcesz ustalić, które części danych wejściowych powodują problem , spróbuj wykonać następujące czynności:

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

Dane wyjściowe pokażą wszystkie bajty, które mają ustawiony wysoki bit (bajty przekraczające 7-bitowy zakres ASCII) w postaci szesnastkowej. (Należy jednak pamiętać, że obejmuje to również poprawnie zakodowane wielobajtowe sekwencje UTF-8 - potrzebne byłoby bardziej wyrafinowane podejście do konkretnej identyfikacji bajtów typu „nieprawidłowa w UTF-8”).


Przeprowadzanie konwersji kodowania na żądanie :

Standardowego narzędzia iconvmożna użyć do konwersji na kodowanie ( -t) i / lub z ( -f); iconv -lwyświetla wszystkie obsługiwane.

Przykłady:

Konwertuj FROM ISO-8859-1na kodowanie obowiązujące w powłoce (na podstawie LC_CTYPE, która jest UTF-8domyślnie oparta na) , bazując na powyższym przykładzie:

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

Pamiętaj, że ta konwersja pozwala odpowiednio dopasować obce znaki :

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

Aby przekonwertować wejściowy BACK na ISO-8859-1przetworzony, po prostu potokuj wynik do innego iconvpolecenia:

sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1
mklement0
źródło
4
Powiedziałbym, że jest to znacznie lepsza opcja. Po pierwsze, nie chciałbym stracić obsługi wielu języków w całym terminalu. Po drugie, przyjęta odpowiedź wydaje się globalnym rozwiązaniem lokalnego problemu - czegoś, czego należy unikać.
Alex
Miałem do tego kilka drobnych poprawek. Byłbym wdzięczny za opinie. stackoverflow.com/a/35046218/9636
Heath Borders
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'odciski sed: RE error: illegal byte sequencedla mnie na Sierra. echo $LC_ALLwyjścia en_US.UTF-8FWIW.
ahcox
1
@ahcox: Tak, ponieważ ustawienie LC_ALL zastępuje wszystkie inne LC_*zmienne, w tym LC_CTYPE, jak wyjaśniono w odpowiedzi.
mklement0
2
@ mklement0 Fajnie, to działa: „LC_ALL = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'”. Wyjaśniono tutaj pierwszeństwo dla moich nieuważnych ignorantów: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox
142

Dodaj następujące wiersze do pliku ~/.bash_profilelub ~/.zshrcplików.

export LC_CTYPE=C 
export LANG=C
binarytemple_picsolve
źródło
29
to faktycznie działa, ale czy mógłbyś wyjaśnić dlaczego?
Hoang Pham
11
@HoangPham: Ustawienie LC_CTYPEdo Cprzyczyn każdy bajt w ciągów się własnym charakterem bez stosowania żadnych zasad kodowania. Ponieważ naruszenie zasad kodowania (UTF-8) spowodowało pierwotny problem, problem ten zniknął. Jednak płacona cena jest taka, że ​​powłoka i narzędzia rozpoznają wtedy tylko podstawowe litery angielskie (te w 7-bitowym zakresie ASCII) jako litery. Zobacz moją odpowiedź, aby uzyskać więcej.
mklement0
6
Ustawienie tego na stałe w plikach startowych powłoki spowoduje wyłączenie wielu przydatnych zachowań. Chcesz umieścić to tylko dla pojedynczych poleceń, które absolutnie tego wymagają.
tripleee
4
Zbyt niebezpieczny może powodować nieoczekiwane konsekwencje. Można użyć LC_CTYPE=C sed …, tj. Tylko na polecenie sed.
Yongwei Wu
2
To całkowicie wyłączy obsługę znaków Unicode w twojej powłoce. Żegnaj emoji, fantazyjne znaki rysowania linii, litery z akcentami ... Znacznie lepiej jest ustawić to tylko dla polecenia sed, jak opisano w innych odpowiedziach.
asmeurer
6

Moje obejście polegało na użyciu Perla:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
Witalij Zdanevich
źródło
Ten działa świetnie. I nie miałem żadnych błędów w ucieczce od znaków specjalnych w przeciwieństwie do innych. Poprzednie dały mi problemy takie jak „błąd sed: RE: niedozwolona sekwencja bajtów” lub sed: 1: „ścieżka_do_pliku”: niepoprawny kod polecenia.
JMags1632
3

Odpowiedź mklement0 jest świetna, ale mam kilka drobnych poprawek.

Wydaje się, że dobrym pomysłem jest jawne określenie bashkodowania podczas używania iconv. Powinniśmy również wstawić znak kolejności bajtów ( nawet jeśli standard Unicode tego nie zaleca ), ponieważ mogą istnieć uzasadnione pomyłki między UTF-8 a ASCII bez znaku kolejności bajtów . Niestety iconvnie wstawia znaku kolejności bajtów, gdy jawnie określasz endianness ( UTF-16BElub UTF-16LE), więc musimy go użyć UTF-16, który wykorzystuje endianness specyficzny dla platformy, a następnie użyć, file --mime-encodingaby odkryć prawdziwą iconvzastosowaną endianness .

(Wszystkie moje kodowania wielkimi literami, ponieważ kiedy wyświetlasz listę wszystkich iconvobsługiwanych kodowań iconv -l, wszystkie są wielkie).

# 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
Granice zdrowia
źródło
1
++ za przydatne techniki, szczególnie file -b --mime-encodingdo odkrywania i zgłaszania kodowania pliku. Jest jednak kilka aspektów, na które warto zwrócić uwagę, które omówię w osobnych komentarzach.
mklement0
2
Myślę, że bezpiecznie jest powiedzieć, że świat Unixa w tym momencie objął UTF-8: LC_CTYPEwartość domyślna to zazwyczaj <lang_region>.UTF-8, więc każdy plik bez BOM (znak kolejności bajtów) jest interpretowany jako plik UTF-8. Tylko w świecie Windows jest używany pseudo-BOM 0xef 0xbb 0xff ; z definicji UTF-8 nie potrzebuje BOM i nie jest zalecany (jak twierdzisz); poza światem Windows ten pseudo-BOM powoduje uszkodzenie .
mklement0
2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): to jest z założenia: jeśli wyraźnie określisz endianizm , nie ma potrzeby odzwierciedlania go również za pomocą BOM, więc żadne nie jest dodawane.
mklement0
1
Re LC_*/ LANGzmienne: bash, ksh, oraz zsh(ewentualnie innych, ale nie dash ) zrobić respektować kodowanie znaków; zweryfikuj w powłokach podobnych do POSIX z ustawieniami regionalnymi opartymi na UTF-8 za pomocą v='ä'; echo "${#v}": powłoka rozpoznająca UTF-8 powinna zgłosić 1; tzn. powinien rozpoznać sekwencję wielobajtową ä( 0xc3 0xa4) jako pojedynczy znak. Być może nawet ważniejsze, jednak: to standardowe narzędzia ( sed, awk, cut, ...) również muszą być locale / kodowania świadomy, a jednocześnie większość z nich na nowoczesny Uniksopodobny platformy są, istnieją wyjątki, takie jak awkna OSX, i cutna Linuksie.
mklement0
1
Godne pochwały jest filerozpoznanie pseudo-BOM UTF-8, ale problem polega na tym, że większość narzędzi uniksowych przetwarzających plik nie , i zwykle psuje się lub przynajmniej źle zachowuje, gdy ma do czynienia z jednym. Bez BOM filepoprawnie identyfikuje plik 7-bitowych bajtów jako ASCII, a taki, który ma prawidłowe znaki wielobajtowe UTF-8 jako UTF-8. Zaletą UTF-8 jest to, że jest nadzbiorem ASCII: każdy prawidłowy plik ASCII jest z definicji prawidłowym plikiem UTF-8 (ale nie odwrotnie); traktowanie pliku ASCII jako UTF-8 jest całkowicie bezpieczne (co technicznie jest, po prostu nie zawiera znaków wielobajtowych.)
mklement0
2

Musisz po prostu przesłać polecenie iconv przed poleceniem sed . Ex z wejściem file.txt:

iconv -f ISO-8859-1 -t UTF8-MAC plik.txt | sed 's / something / àéèêçùû / g' | .....

Opcja -f jest zestawem kodowym „od”, a opcja -t to konwersja zestawu kodowego „na”.

Dbaj o wielkość liter, strony internetowe zwykle wyświetlają małe litery, takie jak <charset = iso-8859-1 "/>, a iconv używa wielkich liter. Masz listę obsługiwanych zestawów kodów iconv w systemie za pomocą polecenia iconv -l

UTF8-MAC jest nowoczesnym zestawem kodowym Mac OS do konwersji.

Denis z Val Thorens
źródło
Zobacz także iconv i nazwy zestawów znaków na liście mailingowej iconv.
jww
1

Czy ktoś wie, jak nakłonić sed do wydrukowania pozycji nielegalnej sekwencji bajtów? A może ktoś wie, co to jest nielegalna sekwencja bajtów?

$ 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

Pomogłem odpowiedzieć na powyższe pytanie, używając tylko tr .

Mam plik .csv, który jest wyciągiem z karty kredytowej i próbuję go zaimportować do Gnucash. Mieszkam w Szwajcarii, więc mam do czynienia z takimi słowami jak Zurych. Podejrzewając, że Gnucash nie lubi „” w polach numerycznych, postanawiam po prostu zastąpić wszystko

; ;

z

;;

Tutaj idzie:

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

Użyłem od, aby rzucić trochę światła: zwróć uwagę na 374 w połowie tego wyjścia 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

Pomyślałem wtedy, że mogę spróbować przekonać tr do podstawienia 374 na dowolny poprawny kod bajtowy. Najpierw więc wypróbowałem coś prostego, co nie działało, ale efektem ubocznym było pokazanie mi, gdzie był problem:

$ 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

Możesz zobaczyć tr bails na 374 znakach.

Używanie perla wydaje się unikać tego problemu

$ 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
Magiczne uda
źródło
0

Moje obejście polegało na użyciu GNU sed. Działa dobrze dla moich celów.

lu_zero
źródło
Rzeczywiście, GNU sed jest opcją, jeśli chcesz zignorować nieprawidłowe bajty w strumieniu wejściowym (nie ma potrzeby LC_ALL=C sed ...obejścia tego problemu), ponieważ GNU sedpo prostu przekazuje nieprawidłowe bajty zamiast zgłaszania błędu, ale pamiętaj, że jeśli chcesz poprawnie rozpoznać i przetworzyć wszystkie znaków w ciągu wejściowym, nie ma możliwości, aby najpierw zmienić kodowanie wejścia (zwykle za pomocą iconv).
mklement0