RE错误:Mac OS X上的非法字节序列


184

我正在尝试替换Mac OS X上Makefile中的字符串以交叉编译到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位ascii输入期望utf-8的内容时得到的。
克拉斯·林德贝克(KlasLindbäck)2013年

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 2016年

1
再加上一个用于LANG的东西。令人难过的是,它晦涩难懂,不明显且难以研究。
Spudley

Answers:


298

出现症状的示例命令:sed 's/./@/' <<<$'\xfc'失败,因为byte 0xfc不是有效的UTF-8字符。
请注意,相比之下,GNU sed(Linux,但也可以在macOS上安装)只是通过无效字节,而不会报告错误。

如果您不介意失去对真实语言环境的支持,则可以使用以前接受的答案(如果您使用的是美国系统,并且您永远不需要处理外国字符,那可能就可以了。)

然而,同样的效果就可以了即席单个命令

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_*-category变量,如LC_CTYPE。因此,最可靠的方法是set LC_ALL

然而,(有效)的设置LC_CTYPE,以C对待字符串,就好像每个字节是它自己的特点没有进行基于编码规则的解释),有没有考虑对-多字节点播- UTF-8编码的是OS X采用默认,其中外来字符具有多字节编码

简而言之:设置LC_CTYPEC会使外壳程序和实用程序仅将基本的英语字母识别为字母(7位ASCII范围内的字母),以便使用外字符。不会被视为 letter,例如,导致大写/小写转换失败。

同样,如果您不需要匹配多字节编码的字符(例如)é,而只是想将这些字符传递通过,则可能会很好。

如果这还不够,并且/或者您想了解原始错误的原因(包括确定导致问题的输入字节)并按需执行编码转换,请阅读以下内容。


问题在于输入文件的编码与外壳程序的编码不匹配。
更具体地说,输入文件包含的字符编码方式在UTF-8中无效(如@KlasLindbäck在评论中所述),这就是sed错误消息试图通过的方式invalid byte sequence

您的输入文件最有可能使用单字节8位编码(例如)ISO-8859-1,经常用于编码“西欧”语言。

例:

重音字母à具有Unicode代码点0xE0(224)-与中的相同ISO-8859-1。然而,由于本质UTF-8编码,该单个码点被表示为2个字节- 0xC3 0xA0,而试图通过该单字节 0xE0无效下UTF-8。

这是使用编码为的字符串并用一个字节表示的问题演示(通过ANSI-C引用的bash字符串(),该字符串用于创建字节):voilàISO-8859-1à$'...'\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,将FROM转换为外壳中有效的编码(基于,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
我会说这是一个更好的选择。首先,我不想在所有Terminal中失去多语言支持。其次,被接受的答案感觉像是针对局部问题的全局解决方案,应避免。
Alex

我对此做了一些小调整。非常感谢您的反馈。stackoverflow.com/a/35046218/9636
希思·边界

LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'sed: RE error: illegal byte sequence在Sierra上为我打印。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}'”。对于我的那些不专心的忽略问题,
ahcox

142

将以下各行添加到您的~/.bash_profile~/.zshrc文件中。

export LC_CTYPE=C 
export LANG=C

29
它确实有效,但是请您解释一下原因?
藩范2014年

11
@HoangPham:设置LC_CTYPEC会导致字符串中的每个字节成为其自己的字符,而不应用任何编码规则。由于违反(UTF-8)编码规则导致了原始问题,因此使问题消失了。但是,您要付出的代价是外壳程序和实用程序仅将基本的英语字母(7位ASCII范围内的字母)识别为字母。看到我的答案更多。
mklement0 2014年

6
在shell的启动文件中永久设置此选项将禁用许多有用的行为。您只想将其用于绝对需要它的单个命令中。
三胞胎

4
太危险可能会导致意外后果。可以使用LC_CTYPE=C sed …,即只能在sed命令上使用。
Yongwei Wu,

2
这将完全禁用对Shell中的Unicode字符的支持。再见的表情符号,花哨的线条画字符,带有重音符号的字母,....最好仅将它设置为sed命令,如其他答案所述。
asmeurer

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-16BEUTF-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):这是设计使然:如果显式指定了字节序,则不需要通过BOM来反映它,因此不会添加。
mklement0

1
Re LC_*/ LANG变量:bashkshzsh(可能还有其他变量,但不是 dash)确实尊重字符编码;验证POSIX样与基于使用UTF-8贝壳v='ä'; echo "${#v}":一个UTF-8意识到外壳应报告1; 也就是说,它应该将多字节序列ä0xc3 0xa4)识别为单个字符。也许更重要的,但是:在标准工具sedawkcut,...)也需要区域/编码感知,虽然大部分对他们的现代类Unix平台的,也有例外,如awk在OSX,而cut在Linux上。
mklement0

1
可以file识别UTF-8伪BOM 是值得称赞的,但是问题是大多数处理文件的Unix实用程序没有,并且通常在遇到一个实用程序时会中断或至少表现异常。如果没有BOM,请file正确将全7位字节的文件标识为ASCII,并将具有有效UTF-8多字节字符的文件标识为UTF-8。UTF-8的优点在于它是ASCII 的超集:根据定义,任何有效的ASCII文件都是有效的UTF-8文件(反之亦然);完全可以安全地将ASCII文件视为UTF-8(从技术上讲,它恰好不包含多字节字符。)
mklement0

2

您只需要在sed命令之前通过管道传送iconv命令即可。例如,输入file.txt:

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

-F选项是“ from”代码集,-t选项是“ to”代码集转换。

请注意大小写,网页通常以小写形式显示,例如<charset = iso-8859-1“ />,而iconv使用大写形式。您可以在系统中使用iconv -l命令获得iconv支持的代码集的列表。

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

我使用od进行了一些说明:请注意od -c输出中的374。

$ 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

我的解决方法是使用gnu sed。为我的目的工作正常。


的确,如果您想忽略输入流中的无效字节(不需要变通方法),则GNU sed是一个选择,因为GNU 只是传递无效字节而不是报告错误,但是请注意,如果您想正确地识别并处理所有输入字符串中的字符,无法先更改输入的编码(通常使用)。LC_ALL=C sed ...sediconv
mklement0 '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.