ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在尝试替换 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 打印非法字节序列的位置?或者有谁知道非法字节序列是什么?

非法字节序列听起来像是将 8 位 ascii 提供给需要 utf-8 的东西时得到的东西。
你能试试吗:LC_CTYPE=C && LANG=C && sed command
谢谢各位。它是 LANG 的事情。叹....
@user2719058:BSD sed(也用于 OS X)需要 -i ''(单独的空字符串选项参数)用于在没有备份文件的情况下进行就地更新;使用 GNU sed,只有 -i 本身有效 - 请参阅 stackoverflow.com/a/40777793/45375
加一个用于 LANG 的东西。天哪,这是晦涩难懂的,不明显的,而且研究起来非常困难。

m
mklement0

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

如果您不介意失去对您的真实语言环境的支持,则可以使用 formerly accepted answer .)

但是,仅对单个命令可以产生相同的效果:

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

注意:重要的是 C有效 LC_CTYPE 设置,因此 LC_CTYPE=C sed ...通常 也可以工作,但如果 LC_ALL 恰好被设置(到C 以外的东西),它将覆盖各个 LC_* 类别变量,例如 LC_CTYPE。因此,最稳健的方法是设置 LC_ALL

但是,(有效地)将 LC_CTYPE 设置为 C 会将字符串视为每个字节都是其自己的字符执行基于编码规则的解释),其中 < strong>不考虑 - multibyte-on-demand - OS X 默认采用 UTF-8 编码,其中 外来字符 具有 multibyte编码

简而言之:LC_CTYPE 设置为 C 会导致 shell 和实用程序仅将基本英文字母识别为字母(7 位 ASCII 范围内的字母),因此 foreign字符。不会被视为字母,从而导致例如大写/小写转换失败。

同样,如果您不需要匹配多字节编码的字符(例如 é),而只是想通过这些字符,这可能没问题。

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

问题是输入文件的编码与 shell 的编码不匹配。
更具体地说,输入文件包含以 UTF-8 无效的方式编码的字符(如@Klas Lindbäck 所述在评论中)- 这就是 invalid byte sequencesed 错误消息试图表达的内容。

您的输入文件很可能使用了单字节 8 位编码,例如 ISO-8859-1,经常用于对“西欧”语言进行编码。

例子:

重音字母 à 具有 Unicode 代码点 0xE0 (224) - 与 ISO-8859-1 中的相同。然而,由于 UTF-8 编码的性质,这个单一的代码点被表示为 2 字节 - 0xC3 0xA0,而试图传递 单一字节< /em> 0xE0 在 UTF-8 下无效

这是一个问题演示,使用编码为 ISO-8859-1 的字符串 voilà,其中 à 表示为 一个 字节(通过 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 列出所有支持的。

例子:

在上述示例的基础上,将 FROM ISO-8859-1 转换为 shell 中有效的编码(基于 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

我会说这是一个更好的选择。首先,我不想失去所有终端的多语言支持。其次,接受的答案感觉像是针对局部问题的全局解决方案——这是需要避免的。
我对此进行了一些小调整。我很感激反馈。 stackoverflow.com/a/35046218/9636
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}' 在 Sierra 上为我打印 sed: RE error: illegal byte sequenceecho $LC_ALL 输出 en_US.UTF-8 FWIW。
@ahcox:是的,因为设置 LC_ALL 覆盖 所有其他 LC_* 变量,包括 LC_CTYPE,如答案中所述。
@mklement0 酷,这有效:“LC_ALL=C sed 's/.*/&/' <<<$'voil\x{e0}'”。此处为我的粗心无知的同伴解释了优先顺序:pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
b
binarytemple_picsolve

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

export LC_CTYPE=C 
export LANG=C

它确实有效,但你能解释一下为什么吗?
@HoangPham:将 LC_CTYPE 设置为 C 会使字符串中的每个字节成为自己的字符,而不应用任何编码规则。由于违反(UTF-8)编码规则导致了原始问题,这使得问题消失了。但是,您付出的代价是 shell 和实用程序只能将基本的英文字母(7 位 ASCII 范围内的那些)识别为字母。有关更多信息,请参阅我的答案。
在 shell 的启动文件中永久设置它会禁用许多有用的行为。您只想将其用于绝对需要它的单个命令。
太危险的罐头可能会导致意想不到的后果。可以使用 LC_CTYPE=C sed …,即仅在 sed 命令上。
这将完全禁用 shell 中对 Unicode 字符的支持。再见表情符号,花哨的线条画字符,带重音符号的字母,.... 最好只为 sed 命令设置它,如其他答案中所述。
V
Vitaly Zdanevich

我的解决方法是使用 Perl:

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

这个效果很好。与其他人不同,我在转义特殊字符时没有任何错误。以前的问题给了我诸如 "sed: RE error: invalid byte sequence" 或 sed: 1: "path_to_file": invalid command code 之类的问题。
简单,无需配置等。喜欢它。
D
Denis from Val Thorens

您只需在 sed 命令之前通过管道传输 iconv 命令。例如带有 file.txt 输入:

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

-f 选项是“从”代码集,-t 选项是“到”代码集转换。

注意大小写,网页通常显示像 < charset=iso-8859-1"/> 这样的小写字母,而 iconv 使用大写字母。您的系统中有 iconv 支持的代码集列表,使用命令 iconv -l

UTF8-MAC 是用于转换的现代 OS Mac 代码集。


另请参阅 iconv 邮件列表中的 iconv and charset names
C
Community

mklement0's answer 很棒,但我做了一些小调整。

使用 iconv 时明确指定 bash 的编码似乎是个好主意。此外,我们应该在前面加上一个字节顺序标记 (even though the unicode standard doesn't recommend it),因为 there can be legitimate confusions between UTF-8 and ASCII without a byte-order mark。不幸的是,当您明确指定字节顺序(UTF-16BEUTF-16LE)时,iconv 不会预先添加字节顺序标记,因此我们需要使用 UTF-16,它使用特定于平台的字节顺序,然后使用 { 9} 发现使用的真正字节顺序 iconv

(我将所有编码都大写,因为当您使用 iconv -l 列出所有 iconv 支持的编码时,它们都是大写的。)

# 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

++ 提供有用的技术,尤其是用于发现和报告文件编码的 file -b --mime-encoding。但是,有一些方面值得讨论,我将在单独的评论中进行说明。
我认为可以肯定地说 Unix 世界在这一点上已经接受了 UTF-8:默认的 LC_CTYPE 值通常是 <lang_region>.UTF-8,所以任何文件没有 BOM(字节顺序标记)因此被解释为 UTF-8 文件。仅在 Windows 世界中使用 pseudo-BOM 0xef 0xbb 0xff;根据定义,UTF-8 不需要 BOM 并且不推荐(如您所说);在 Windows 世界之外,这种伪 BOM 会导致事物中断
关于 Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE):这是设计使然:如果您明确指定字节顺序,则无需通过 BOM 来反映它,因此不会添加任何内容。
关于 LC_* / LANG 变量:bashkshzsh(可能还有其他,但 not dash)确实尊重字符编码;在类似 POSIX 的 shell 中使用基于 UTF-8 的语言环境和 v='ä'; echo "${#v}" 进行验证:支持 UTF-8 的 shell 应该报告 1;即,它应该将多字节序列ä (0xc3 0xa4) 识别为单个 字符。然而,也许更重要的是:标准实用程序sedawkcut、...)还需要支持区域设置/编码,而 大多数 在现代类 Unix 平台上,也有例外,例如 OSX 上的 awk 和 Linux 上的 cut
值得称赞的是,file 可以识别 UTF-8 伪 BOM,但问题是大多数处理文件的 Unix 实用程序,并且在遇到文件时通常会中断或至少行为不端。如果没有 BOM,file 会正确地将全 7 位字节文件标识为 ASCII,并将具有有效 UTF-8 多字节字符的文件正确标识为 UTF-8。 UTF-8 的美妙之处在于它是 ASCII 的超集:根据定义,任何有效的 ASCII 文件都是有效的 UTF-8 文件(但反之则不然);将 ASCII 文件视为 UTF-8 是完全安全的(从技术上讲,它恰好不包含多字节字符。)
l
lu_zero

我的解决方法是使用 gnu sed。为我的目的工作得很好。


实际上,如果您想 忽略 输入流中的无效字节(不需要 LC_ALL=C sed ... 解决方法),GNU sed 是一个选项,因为 GNU sed只是通过传递无效字节而不是报告错误,但请注意,如果您想正确识别和处理输入字符串中的所有字符,则无法先更改输入的编码(通常,使用iconv)。
M
Magic Thighs

有谁知道如何让 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 阐明了一些观点:注意 374 在这个 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

然后我想我可能会尝试说服 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 字符处看到 tr 保释。

使用 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