Kesalahan RE: urutan byte ilegal di Mac OS X

184

Saya mencoba mengganti string di Makefile di Mac OS X untuk kompilasi silang ke iOS. String telah menyematkan tanda kutip ganda. Perintahnya adalah:

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

Dan kesalahannya adalah:

sed: RE error: illegal byte sequence

Saya telah mencoba melarikan diri dari tanda kutip ganda, koma, tanda hubung, dan titik dua tanpa sukacita. Sebagai contoh:

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

Saya mengalami banyak waktu men-debug masalah. Adakah yang tahu cara sedmencetak posisi urutan byte ilegal? Atau apakah ada yang tahu apa urutan byte ilegal itu?

jww
sumber
2
Urutan byte ilegal terdengar seperti sesuatu yang Anda dapatkan saat mengumpankan ascii 8-bit ke sesuatu yang mengharapkan utf-8.
Klas Lindbäck
36
Bisakah Anda mencoba:LC_CTYPE=C && LANG=C && sed command
anubhava
5
Terima kasih semuanya. Itu LANGmasalahnya. Sigh ....
jww
3
@ user2719058: BSD sed(seperti yang juga digunakan pada OS X) memerlukan -i ''(argumen pilihan-string kosong) untuk pembaruan di tempat tanpa file cadangan; dengan GNU sed, hanya -idengan sendirinya berfungsi - lihat stackoverflow.com/a/40777793/45375
mklement0
1
Ditambah satu untuk hal LANG. Astaga, itu tidak jelas, tidak jelas dan mengejutkan sulit untuk diteliti.
Spudley

Jawaban:

300

Perintah sampel yang menunjukkan gejala: sed 's/./@/' <<<$'\xfc'gagal, karena byte 0xfcbukan karakter UTF-8 yang valid.
Perhatikan bahwa, sebaliknya, GNU sed (Linux, tetapi juga dapat diinstal pada macOS) cukup melewati byte yang tidak valid, tanpa melaporkan kesalahan.

Menggunakan jawaban yang sebelumnya diterima adalah pilihan jika Anda tidak keberatan kehilangan dukungan untuk lokal Anda yang sebenarnya (jika Anda menggunakan sistem AS dan Anda tidak perlu berurusan dengan karakter asing, itu mungkin baik-baik saja.)

Namun, efek yang sama dapat memiliki ad-hoc untuk perintah tunggal hanya :

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

Catatan: Yang penting adalah efektif LC_CTYPE pengaturan C, sehingga LC_CTYPE=C sed ...akan normal juga bekerja, tetapi jika LC_ALLterjadi menjadi set (untuk sesuatu yang lain dari C), itu akan menimpa individu LC_*variabel -Kategori seperti LC_CTYPE. Dengan demikian, pendekatan yang paling kuat adalah menetapkanLC_ALL .

Namun, pengaturan (efektif) LC_CTYPEuntuk Cmemperlakukan string seolah-olah setiap byte adalah karakternya sendiri ( tidak ada interpretasi berdasarkan aturan pengkodean dilakukan), tanpa memperhatikan - multibyte-on-demand - pengkodean UTF-8 yang OS X mempekerjakan secara default , di mana karakter asing memiliki penyandian multibyte .

Singkatnya: pengaturan LC_CTYPEuntukC penyebab shell dan utilitas hanya mengenal huruf dasar bahasa Inggris sebagai huruf (yang di kisaran ASCII 7-bit), sehingga karakter asing. tidak akan diperlakukan sebagai huruf , menyebabkan, misalnya, konversi huruf besar / kecil gagal.

Sekali lagi, ini mungkin baik-baik saja jika Anda tidak perlu mencocokkan karakter multibyte-encoded seperti é, dan hanya ingin melewati karakter tersebut .

Jika ini tidak cukup dan / atau Anda ingin memahami penyebab kesalahan asli (termasuk menentukan byte input apa yang menyebabkan masalah) dan melakukan pengkodean konversi sesuai permintaan, baca terus di bawah ini.


Masalahnya adalah bahwa pengkodean file input tidak cocok dengan shell.
Lebih khusus lagi, file input berisi karakter yang dikodekan dengan cara yang tidak valid di UTF-8 (seperti yang dinyatakan oleh @Klas Lindbäck dalam komentar) - itulah yang ingin disampaikan oleh sedpesan kesalahan invalid byte sequence.

Kemungkinan besar, file input Anda menggunakan pengodean 8-bit single-byte sepertiISO-8859-1 , yang sering digunakan untuk menyandikan bahasa "Eropa Barat".

Contoh:

Huruf beraksen àmemiliki Unicode codepoint 0xE0(224) - sama seperti pada ISO-8859-1. Namun, karena sifat dari UTF-8 encoding, codepoint tunggal ini diwakili sebagai 2 byte - 0xC3 0xA0, sedangkan mencoba untuk melewati byte tunggal 0xE0 adalah tidak valid di bawah UTF-8.

Berikut ini demonstrasi masalah menggunakan string yang voilàdisandikan sebagai ISO-8859-1, dengan yang àdirepresentasikan sebagai satu byte (melalui string bash yang dikutip ANSI-C $'...') yang menggunakan\x{e0} untuk membuat byte):

Perhatikan bahwa sedperintah ini efektif no-op yang hanya melewati input, tetapi kita perlu memprovokasi kesalahan:

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

Untuk mengabaikan masalah , LCTYPE=Cpendekatan di atas dapat digunakan:

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

Jika Anda ingin menentukan bagian input mana yang menyebabkan masalah , coba yang berikut ini:

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

Output akan menampilkan semua byte yang memiliki set bit tinggi (byte yang melebihi kisaran ASCII 7-bit) dalam bentuk heksadesimal. (Namun, perlu diketahui bahwa itu juga mencakup urutan multibyte UTF-8 yang dikodekan dengan benar - diperlukan pendekatan yang lebih canggih untuk secara spesifik mengidentifikasi byte yang tidak valid dalam UTF-8.)


Melakukan konversi pengkodean sesuai permintaan :

Utilitas standar iconvdapat digunakan untuk mengkonversi ke ( -t) dan / atau dari ( -f) pengkodean;iconv -ldaftar semua yang didukung.

Contoh:

Konversi FROM ISO-8859-1ke pengkodean yang berlaku di shell (berdasarkan LC_CTYPE, yang UTF-8-berdasarkan secara default), membangun contoh di atas:

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

Perhatikan bahwa konversi ini memungkinkan Anda untuk mencocokkan karakter asing dengan benar :

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

Untuk mengonversi input BACK ke ISO-8859-1setelah diproses, cukup pipa hasilnya ke iconvperintah lain :

sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1
mklement0
sumber
4
Saya akan mengatakan ini adalah pilihan yang jauh lebih baik. Pertama, saya tidak ingin kehilangan dukungan multi-bahasa di semua Terminal. Kedua, jawaban yang diterima terasa seperti solusi global untuk masalah lokal - sesuatu yang harus dihindari.
Alex
Saya punya beberapa tweak kecil untuk ini. Saya menghargai umpan balik. stackoverflow.com/a/35046218/9636
Perbatasan Heath
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'cetakan sed: RE error: illegal byte sequenceuntuk saya di Sierra. echo $LC_ALLoutput en_US.UTF-8FWIW.
ahcox
1
@ ahcox: Ya, karena pengaturan LC_ALL mengabaikan semua LC_*variabel lain , termasuk LC_CTYPE, seperti yang dijelaskan dalam jawaban.
mklement0
2
@ mklement0 Keren, ini berfungsi: "LC_ALL = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'". Precedence dijelaskan di sini untuk sesama orang bodoh yang lalai: pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
ahcox
142

Tambahkan baris berikut ke ~/.bash_profileatau ~/.zshrcfile (s).

export LC_CTYPE=C 
export LANG=C
binarytemple_picsolve
sumber
29
ini benar-benar berfungsi, tetapi bisakah Anda menjelaskan mengapa?
Hoang Pham
11
@HoangPham: Pengaturan LC_CTYPEuntuk Cpenyebab setiap byte dalam string menjadi karakter tersendiri tanpa menerapkan aturan pengkodean. Karena pelanggaran aturan pengkodean (UTF-8) menyebabkan masalah asli, ini membuat masalah hilang. Namun, harga yang Anda bayar adalah bahwa shell dan utilitas kemudian hanya mengenali huruf Inggris dasar (yang dalam kisaran ASCII 7-bit) sebagai huruf. Lihat jawaban saya untuk lebih lanjut.
mklement0
6
Mengatur ini secara permanen di file startup shell Anda akan menonaktifkan banyak perilaku yang berguna. Anda ingin memasukkan ini hanya untuk perintah individu yang benar-benar membutuhkannya.
tripleee
4
Terlalu berbahaya dapat menyebabkan konsekuensi yang tidak terduga. Orang bisa menggunakan LC_CTYPE=C sed …, yaitu hanya pada perintah sed.
Yongwei Wu
2
Ini sepenuhnya akan menonaktifkan dukungan untuk karakter Unicode di shell Anda. Selamat tinggal emoji, karakter menggambar garis mewah, huruf dengan aksen, .... Jauh lebih baik hanya mengatur ini untuk perintah sed saja, seperti yang dijelaskan dalam jawaban lain.
penanggung jawab
6

Solusi saya telah menggunakan Perl:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
Vitaly Zdanevich
sumber
Ini bekerja dengan baik. Dan saya tidak memiliki kesalahan untuk keluar dari karakter khusus tidak seperti yang lain. Yang sebelumnya memberi saya masalah seperti "sed: RE error: urutan byte ilegal" atau sed: 1: "path_to_file": kode perintah tidak valid.
JMags1632
3

Jawaban mklement0 bagus, tapi saya punya sedikit penyesuaian.

Sepertinya ide yang bagus untuk secara spesifik menentukan bashpenyandian saat menggunakan iconv. Selain itu, kita harus menambahkan tanda byte-order ( meskipun standar unicode tidak merekomendasikannya ) karena mungkin ada kebingungan yang sah antara UTF-8 dan ASCII tanpa tanda byte-order . Sayangnya, iconvtidak menambahkan tanda byte-order ketika Anda secara eksplisit menentukan endianness ( UTF-16BEatau UTF-16LE), jadi kita perlu menggunakan UTF-16, yang menggunakan endianness platform-spesifik, dan kemudian gunakan file --mime-encodinguntuk menemukan endianness sebenarnya yang iconvdigunakan.

(Saya huruf besar semua pengkodean saya karena ketika Anda daftar semua iconvpengkodean yang didukung dengan iconv -lmereka semua huruf besar.)

# 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
Perbatasan Heath
sumber
1
++ untuk teknik yang bermanfaat, terutama file -b --mime-encodinguntuk menemukan dan melaporkan penyandian file. Ada beberapa aspek yang perlu diperhatikan, yang akan saya lakukan dalam komentar terpisah.
mklement0
2
Saya pikir aman untuk mengatakan bahwa dunia Unix telah memeluk UTF-8 pada saat ini: nilai default LC_CTYPEbiasanya <lang_region>.UTF-8, jadi setiap file tanpa BOM (byte-order mark) karenanya ditafsirkan sebagai file UTF-8. Hanya di dunia Windows bahwa BOM semu 0xef 0xbb 0xff digunakan; menurut definisi, UTF-8 tidak memerlukan BOM dan tidak direkomendasikan (seperti yang Anda nyatakan); di luar dunia Windows, BOM semu ini menyebabkan banyak hal rusak .
mklement0
2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): itu dengan desain: jika Anda menentukan endianness secara eksplisit , tidak perlu juga mencerminkannya melalui BOM, jadi tidak ada yang ditambahkan.
mklement0
1
Re LC_*/ LANGvariabel: bash,, kshdan zsh(mungkin yang lain, tetapi tidak dash ) menghormati pengkodean karakter; verifikasi dalam kerang mirip POSIX dengan lokal berbasis UTF-8 dengan v='ä'; echo "${#v}": shell sadar UTF-8 harus melaporkan 1; yaitu, ia harus mengenali urutan multi-byte ä( 0xc3 0xa4), sebagai karakter tunggal . Mungkin bahkan lebih penting, namun: utilitas standar ( sed, awk, cut, ...) juga harus lokal / encoding-sadar, dan sementara sebagian besar dari mereka pada yang modern Unix-seperti platform yang, ada pengecualian, seperti awkdi OSX, dan cutdi Linux.
mklement0
1
Sangat terpuji yang filemengenali pseudo-BOM UTF-8, tetapi masalahnya adalah sebagian besar utilitas Unix yang memproses file tidak , dan biasanya rusak atau setidaknya bertingkah salah ketika dihadapkan dengan satu. Tanpa BOM, filemengidentifikasi dengan benar file byte semua-7-bit sebagai ASCII, dan yang memiliki karakter multi-byte UTF-8 yang valid sebagai UTF-8. Keindahan dari UTF-8 adalah bahwa ia adalah superset dari ASCII: setiap file ASCII yang valid secara definisi merupakan file UTF-8 yang valid (tetapi tidak sebaliknya); itu sangat aman untuk memperlakukan file ASCII sebagai UTF-8 (yang secara teknis, tidak mengandung karakter multi-byte.)
mklement0
2

Anda hanya perlu menyalurkan perintah ikonv sebelum perintah sed . Mis dengan input file.txt:

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

-f opsi adalah 'dari' codeset dan -t opsi adalah konversi 'ke' codeset.

Jaga kasus, halaman web biasanya menunjukkan huruf kecil seperti itu <charset = iso-8859-1" /> dan iconv menggunakan huruf besar. Anda harus daftar iconv didukung codesets di Anda sistem dengan perintah iconv -l

UTF8-MAC adalah kode OS Mac modern untuk konversi.

Denis dari Val Thorens
sumber
Juga lihat iconv dan nama charset pada milis iconv.
jww
1

Apakah ada yang tahu bagaimana cara mencetak posisi urutan byte ilegal? Atau apakah ada yang tahu apa urutan byte ilegal itu?

$ 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

Saya mendapat bagian dari cara untuk menjawab pertanyaan di atas hanya dengan menggunakan tr .

Saya memiliki file .csv yang merupakan pernyataan kartu kredit dan saya mencoba mengimpornya ke Gnucash. Saya berbasis di Swiss jadi saya harus berurusan dengan kata-kata seperti Zürich. Mencurigai Gnucash tidak suka "" di bidang angka, saya memutuskan untuk mengganti semuanya

; ;

dengan

;;

Ini dia:

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

Saya menggunakan od untuk menjelaskan: Perhatikan 374 di tengah-tengah output od -c ini

$ 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

Lalu saya pikir saya mungkin mencoba membujuk tr untuk mengganti 374 untuk kode byte yang benar. Jadi pertama-tama saya mencoba sesuatu yang sederhana, yang tidak berhasil, tetapi memiliki efek samping dengan menunjukkan kepada saya di mana byte merepotkan itu:

$ 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

Anda dapat melihat tr bails di karakter 374.

Menggunakan perl tampaknya menghindari masalah ini

$ 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
Paha Ajaib
sumber
0

Solusi saya telah menggunakan gnu sed. Bekerja dengan baik untuk tujuan saya.

lu_zero
sumber
Memang, GNU sed adalah opsi jika Anda ingin mengabaikan byte yang tidak valid dalam aliran input (tidak perlu untuk LC_ALL=C sed ...penyelesaian), karena GNU sedhanya melewati byte yang tidak valid melalui bukannya melaporkan kesalahan, tetapi perhatikan bahwa jika Anda ingin mengenali dan memproses semua karakter dalam string input, tidak ada jalan lain untuk mengubah pengkodean input terlebih dahulu (biasanya, dengan iconv).
mklement0