Lỗi RE: chuỗi byte bất hợp pháp trên Mac OS X


183

Tôi đang cố gắng thay thế một chuỗi trong Makefile trên Mac OS X để biên dịch chéo sang iOS. Chuỗi đã nhúng dấu ngoặc kép. Lệnh là:

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

Và lỗi là:

sed: RE error: illegal byte sequence

Tôi đã cố gắng thoát khỏi dấu ngoặc kép, dấu phẩy, dấu gạch ngang và dấu hai chấm không có niềm vui. Ví dụ:

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

Tôi đang có một thời gian gỡ lỗi vấn đề. Có ai biết làm thế nào để sedin vị trí của chuỗi byte bất hợp pháp không? Hoặc có ai biết chuỗi byte bất hợp pháp là gì không?


2
Chuỗi byte bất hợp pháp nghe có vẻ giống như thứ gì đó bạn nhận được khi cung cấp ascii 8 bit cho thứ gì đó mong đợi utf-8.
Klas Lindbäck

36
Bạn có thể thử:LC_CTYPE=C && LANG=C && sed command
anubhava

5
Cảm ơn ba mẹ. Đó là LANGđiều. Tiếng thở dài ....
jww

3
@ user2719058: BSD sed(cũng được sử dụng trên OS X) yêu cầu -i ''(riêng biệt, đối số tùy chọn chuỗi trống) để cập nhật tại chỗ mà không cần tệp sao lưu; với GNU sed, chỉ có -ichính nó hoạt động - xem stackoverflow.com/a/40777793/45375
mkuity0

1
Thêm một cho điều LANG. Thật đau buồn, điều đó tối nghĩa, không rõ ràng và khó nghiên cứu.
Spudley

Câu trả lời:


298

Một lệnh mẫu thể hiện triệu chứng: sed 's/./@/' <<<$'\xfc'thất bại, bởi vì byte 0xfckhông phải là char UTF-8 hợp lệ.
Lưu ý rằng, ngược lại, GNU sed (Linux, nhưng cũng có thể cài đặt trên macOS) chỉ đơn giản chuyển byte không hợp lệ qua mà không báo cáo lỗi.

Sử dụng câu trả lời được chấp nhận trước đây là một tùy chọn nếu bạn không mất việc hỗ trợ cho địa điểm thực sự của mình (nếu bạn đang sử dụng hệ thống ở Hoa Kỳ và bạn không bao giờ cần phải xử lý các ký tự nước ngoài, điều đó có thể ổn.)

Tuy nhiên, hiệu ứng tương tự có thể chỉ có đặc biệt cho một lệnh duy nhất :

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

Lưu ý: Điều quan trọng là hiệu quả LC_CTYPE thiết lập của C, vì vậy LC_CTYPE=C sed ...sẽ thường cũng làm việc, nhưng nếu LC_ALLxảy ra là bộ (một cái gì đó khác hơn C), nó sẽ ghi đè cá nhân LC_*biến -Danh mục như LC_CTYPE. Vì vậy, cách tiếp cận mạnh mẽ nhất là thiết lập LC_ALL.

Tuy nhiên, cài đặt (một cách hiệu quả) LC_CTYPEđể Cxử lý các chuỗi như thể mỗi byte là ký tự riêng của nó ( không có giải thích dựa trên quy tắc mã hóa nào được thực hiện), không liên quan đến mã hóa UTF-8 theo yêu cầu - mặc định mà OS X sử dụng theo mặc định , nơi các ký tự nước ngoàimã hóa đa bào .

Tóm lại: cài đặt LC_CTYPEđểC làm cho trình bao và các tiện ích chỉ nhận ra các chữ cái tiếng Anh cơ bản là các chữ cái (các ký tự trong phạm vi ASCII 7 bit), sao cho các ký tự nước ngoài. sẽ không được coi là chữ cái , ví dụ, chuyển đổi chữ hoa / chữ thường không thành công.

Một lần nữa, điều này có thể ổn nếu bạn không cần phải khớp các ký tự được mã hóa đa chuỗi như é, và chỉ đơn giản là muốn chuyển các ký tự đó qua .

Nếu điều này là không đủ và / hoặc bạn muốn hiểu nguyên nhân gây ra lỗi ban đầu (bao gồm cả việc xác định byte đầu vào nào gây ra sự cố) và thực hiện chuyển đổi mã hóa theo yêu cầu, hãy đọc phần bên dưới.


Vấn đề là mã hóa của tệp đầu vào không khớp với shell.
Cụ thể hơn, tệp đầu vào chứa các ký tự được mã hóa theo cách không hợp lệ trong UTF-8 (như @Klas Lindbäck đã nêu trong một nhận xét) - đó là những gì mà thông sedbáo lỗi đang cố nói invalid byte sequence.

Rất có thể, tệp đầu vào của bạn sử dụng mã hóa 8 bit một byte như ISO-8859-1, thường được sử dụng để mã hóa các ngôn ngữ "Tây Âu".

Thí dụ:

Chữ có dấu àcó bảng mã Unicode 0xE0(224) - giống như trong ISO-8859-1. Tuy nhiên, do bản chất của mã hóa UTF-8 , mã hóa đơn này được biểu diễn dưới dạng 2 byte - 0xC3 0xA0trong khi cố gắng vượt qua byte đơn 0xE0không hợp lệ theo UTF-8.

Dưới đây là một minh chứng cho vấn đề sử dụng chuỗi voilàđược mã hóa dưới dạng ISO-8859-1, với àđại diện là một byte (thông qua chuỗi bash ( $'...') được sử dụng \x{e0}để tạo byte):

Lưu ý rằng sedlệnh thực sự là một lệnh cấm đơn giản chỉ cần chuyển đầu vào qua, nhưng chúng ta cần nó để gây ra lỗi:

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

Để đơn giản bỏ qua vấn đề , LCTYPE=Ccách tiếp cận trên có thể được sử dụng:

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

Nếu bạn muốn xác định phần nào của đầu vào gây ra sự cố , hãy thử các cách sau:

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

Đầu ra sẽ hiển thị cho bạn tất cả các byte có tập bit cao (byte vượt quá phạm vi ASCII 7 bit) ở dạng thập lục phân. (Tuy nhiên, lưu ý rằng điều đó cũng bao gồm các chuỗi đa bào UTF-8 được mã hóa chính xác - sẽ cần một cách tiếp cận phức tạp hơn để xác định cụ thể các byte không hợp lệ trong UTF-8.)


Thực hiện chuyển đổi mã hóa theo yêu cầu :

Tiện ích tiêu chuẩn iconvcó thể được sử dụng để chuyển đổi sang mã hóa ( -t) và / hoặc từ ( -f); iconv -lliệt kê tất cả những người được hỗ trợ.

Ví dụ:

Chuyển đổi TỪ ISO-8859-1sang mã hóa có hiệu lực trong shell (dựa trên LC_CTYPE, được UTF-8dựa trên mặc định), dựa trên ví dụ trên:

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

Lưu ý rằng chuyển đổi này cho phép bạn khớp đúng các ký tự nước ngoài :

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

Để chuyển đổi BACK đầu vào thành ISO-8859-1sau khi xử lý, chỉ cần chuyển kết quả sang iconvlệnh khác :

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

4
Tôi muốn nói rằng đây là một lựa chọn tốt hơn nhiều. Đầu tiên, tôi sẽ không muốn mất hỗ trợ đa ngôn ngữ trong tất cả Terminal. Thứ hai, câu trả lời được chấp nhận cảm thấy giống như một giải pháp toàn cầu cho một vấn đề cục bộ - điều cần tránh.
Alex

Tôi đã có một vài điều chỉnh nhỏ cho việc này. Tôi đánh giá cao phản hồi. stackoverflow.com/a/35046218/9636
Biên giới Heath

LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'in sed: RE error: illegal byte sequencecho tôi trên Sierra. echo $LC_ALLđầu ra en_US.UTF-8FWIW.
ahcox

1
@ahcox: Có, vì cài đặt LC_ALL ghi đè tất cả các LC_*biến khác , bao gồm LC_CTYPE, như được giải thích trong câu trả lời.
mkuity0

2
@ mkuity0 Thật tuyệt, cái này hoạt động: "LC_ALL = C sed 's /.*/&/' <<< $ 'voil \ x {e0}'". Ưu tiên giải thích ở đây cho những người ngu dốt vô tâm của tôi: pubs.opengroup.org/onlinepub/7908799/xbd/envvar.html
ahcox

142

Thêm các dòng sau vào ~/.bash_profilehoặc ~/.zshrctập tin của bạn.

export LC_CTYPE=C 
export LANG=C

29
nó thực sự hoạt động, nhưng bạn có thể vui lòng giải thích tại sao?
Hoàng Phạm

11
@HoangPham: Cài đặt LC_CTYPEđể Clàm cho mỗi byte trong chuỗi là ký tự riêng của nó mà không áp dụng bất kỳ quy tắc mã hóa nào. Do vi phạm quy tắc mã hóa (UTF-8) gây ra sự cố ban đầu, điều này làm cho vấn đề không còn nữa. Tuy nhiên, cái giá bạn phải trả là vỏ và các tiện ích sau đó chỉ nhận ra các chữ cái tiếng Anh cơ bản (những chữ cái trong phạm vi ASCII 7 bit) là các chữ cái. Xem câu trả lời của tôi để biết thêm.
mkuity0

6
Đặt cái này vĩnh viễn trong các tệp khởi động của shell của bạn sẽ vô hiệu hóa nhiều hành vi hữu ích. Bạn muốn đặt cái này chỉ cho các lệnh riêng lẻ hoàn toàn yêu cầu nó.
tripleee

4
Quá nguy hiểm có thể gây ra hậu quả không mong muốn. Người ta có thể sử dụng LC_CTYPE=C sed …, tức là chỉ trên lệnh sed.
Yongwei Wu

2
Điều này sẽ hoàn toàn vô hiệu hóa hỗ trợ cho các ký tự Unicode trong vỏ của bạn. Tạm biệt biểu tượng cảm xúc, ký tự vẽ đường kẻ lạ mắt, chữ cái có dấu, .... Tốt hơn hết là chỉ đặt điều này cho lệnh sed, như được mô tả trong các câu trả lời khác.
asmeker

6

Cách giải quyết của tôi đã sử dụng Perl:

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

Điều này làm việc tuyệt vời. Và tôi đã không có lỗi khi thoát khỏi các ký tự đặc biệt không giống như những người khác. Những cái trước đã cho tôi các vấn đề như "sed: RE error: chuỗi byte bất hợp pháp" hoặc sed: 1: "path_to_file": mã lệnh không hợp lệ.
JMags1632

3

Câu trả lời của mkuity0 là tuyệt vời, nhưng tôi có một số điều chỉnh nhỏ.

Có vẻ như một ý tưởng tốt để xác định rõ ràng bashmã hóa khi sử dụng iconv. Ngoài ra, chúng ta nên thêm vào một dấu thứ tự byte ( mặc dù tiêu chuẩn unicode không khuyến nghị nó ) bởi vì có thể có sự nhầm lẫn hợp pháp giữa UTF-8 và ASCII mà không có dấu thứ tự byte . Thật không may, iconvkhông trả trước một dấu thứ tự byte khi bạn chỉ định rõ ràng một endianness ( UTF-16BEhoặc UTF-16LE), vì vậy chúng tôi cần sử dụng UTF-16, sử dụng endianness dành riêng cho nền tảng, sau đó sử dụng file --mime-encodingđể khám phá độ bền thực sự iconvđược sử dụng.

(Tôi viết hoa tất cả các mã hóa của mình bởi vì khi bạn liệt kê tất cả các iconvmã hóa được hỗ trợ với iconv -lchúng đều là chữ hoa.)

# 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
++ cho các kỹ thuật hữu ích, đặc biệt là file -b --mime-encodingđể khám phá và báo cáo mã hóa của tệp. Tuy nhiên, có một số khía cạnh đáng để giải quyết, tuy nhiên, tôi sẽ làm trong các bình luận riêng biệt.
mkuity0

2
Tôi nghĩ rằng an toàn khi nói rằng thế giới Unix đã chấp nhận UTF-8 vào thời điểm này: LC_CTYPEgiá trị mặc định thường là <lang_region>.UTF-8, do đó, bất kỳ tệp nào không có BOM (dấu thứ tự byte) do đó được hiểu là tệp UTF-8. Chỉ có trong thế giới Windows , BOM giả 0xef 0xbb 0xff được sử dụng; theo định nghĩa, UTF-8 không cần BOM và không được khuyến nghị (như bạn nêu); bên ngoài thế giới Windows, BOM giả này khiến mọi thứ bị phá vỡ .
mkuity0

2
Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE): đó là theo thiết kế: nếu bạn xác định rõ ràng về tuổi thọ , thì không cần phải phản ánh nó thông qua BOM, vì vậy không có gì được thêm vào.
mkuity0

1
Re LC_*/ LANGbiến : bash, kshzsh(có thể là các biến khác, nhưng không dash tôn trọng mã hóa ký tự; xác minh trong các vỏ giống như POSIX với ngôn ngữ dựa trên UTF-8 với v='ä'; echo "${#v}": vỏ nhận biết UTF-8 phải báo cáo 1; tức là, cần nhận ra chuỗi đa byte ä( 0xc3 0xa4), như một single nhân vật. Có lẽ thậm chí quan trọng hơn, tuy nhiên: các tiện ích tiêu chuẩn ( sed, awk, cut, ...) cũng cần phải locale / mã hóa-aware, và trong khi hầu hết trong số họ về hiện đại Unix-like nền tảng là, có những trường hợp ngoại lệ, chẳng hạn như awktrên OSX, và cuttrên Linux.
mkuity0

1
Thật đáng khen ngợi khi filenhận ra BOM giả-UTF-8, nhưng vấn đề là hầu hết các tiện ích Unix xử lý tệp không , và thường bị hỏng hoặc ít nhất là hoạt động sai khi gặp phải. Nếu không có BOM, filexác định chính xác tệp byte 7 bit là ASCII và tệp có ký tự nhiều byte UTF-8 hợp lệ là UTF-8. Cái hay của UTF-8 là nó là siêu ký tự của ASCII: mọi tệp ASCII hợp lệ theo định nghĩa là tệp UTF-8 hợp lệ (nhưng không phải ngược lại); Thật an toàn khi coi tệp ASCII là UTF-8 (về mặt kỹ thuật, nó thực sự không chứa ký tự nhiều byte.)
mkuity0

2

Bạn chỉ cần đặt một lệnh iconv trước lệnh sed . Ví dụ với đầu vào file.txt:

iconv -f ISO-8859-1 -t tệp UTF8-MAC | sed 's / cái gì đó / àéèêçùû / g' | .....

Tùy chọn -f là tùy chọn 'từ' bộ mã và -t là chuyển đổi bộ mã 'thành'.

Hãy cẩn thận, các trang web thường hiển thị chữ thường như thế <charset = iso-8859-1 "/> và iconv sử dụng chữ hoa. Bạn có danh sách các bộ mã được hỗ trợ iconv trong hệ thống của bạn với lệnh iconv -l

UTF8-MAC là bộ mã hệ điều hành Mac hiện đại để chuyển đổi.


Cũng xem tên iconv và bộ ký tự trong danh sách gửi thư iconv.
jww

1

Có ai biết làm thế nào để có được sed để in vị trí của chuỗi byte bất hợp pháp? Hoặc có ai biết chuỗi byte bất hợp pháp là gì không?

$ 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

Tôi có một phần của cách trả lời ở trên chỉ bằng cách sử dụng tr .

Tôi có tệp .csv là bảng sao kê thẻ tín dụng và tôi đang cố gắng nhập tệp đó vào Gnucash. Tôi có trụ sở tại Thụy Sĩ nên tôi phải đối phó với những từ như Zürich. Nghi ngờ Gnucash không thích "" trong các trường số, tôi quyết định chỉ cần thay thế tất cả

; ;

với

;;

Đây là:

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

Tôi đã sử dụng od để làm sáng tỏ: Lưu ý rằng 374 giảm một nửa sản lượng od -c này

$ 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

Sau đó, tôi nghĩ rằng tôi có thể cố gắng thuyết phục tr để thay thế 374 cho dù mã byte chính xác là gì. Vì vậy, trước tiên tôi đã thử một cái gì đó đơn giản, không hiệu quả, nhưng có tác dụng phụ là chỉ cho tôi nơi byte rắc rối là:

$ 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

Bạn có thể thấy tr bails tại nhân vật 374.

Sử dụng perl dường như để tránh vấn đề này

$ 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

Cách giải quyết của tôi đã được sử dụng gnu sed. Làm việc tốt cho mục đích của tôi.


Thật vậy, GNU sed là một tùy chọn nếu bạn muốn bỏ qua các byte không hợp lệ trong luồng đầu vào (không cần LC_ALL=C sed ...giải pháp thay thế), vì GNU sedchỉ đơn giản chuyển các byte không hợp lệ qua thay vì báo cáo lỗi, nhưng lưu ý rằng nếu bạn muốn nhận ra và xử lý đúng tất cả các ký tự trong chuỗi đầu vào, không có cách nào thay đổi mã hóa của đầu vào (thông thường, bằng iconv).
mkuity0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.