关于正则表达式:RE错误:Mac OS X上的非法字节序列

RE error: illegal byte sequence on Mac OS X

我正在尝试替换mac os x上makefile中的字符串,以便交叉编译到i os。字符串中嵌入了双引号。命令是:

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

错误是:

1
sed: RE error: illegal byte sequence

我试着不高兴地避开双引号、逗号、破折号和冒号。例如:

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

我正在调试这个问题。有人知道如何让sed打印非法字节序列的位置吗?或者有人知道非法字节序列是什么吗?


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

如果您不介意失去对您的真实语言环境的支持,可以选择使用以前接受的答案(如果您使用的是美国系统,并且不需要处理外来字符,那么这可能很好。)

但是,对于单个命令也可以有同样的效果:

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

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

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

同样,如果您不需要匹配多字节编码的字符(如é)并且只想通过这些字符,那么这可能很好。

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

问题是输入文件的编码与shell的不匹配。更具体地说,输入文件包含的字符编码方式在UTF-8中无效(如@klas lindb?)CK在一条评论中指出),这正是sed错误消息试图由invalid byte sequence说的。

最有可能的是,输入文件使用单字节8位编码,如ISO-8859-1,通常用于编码"西欧"语言。

例子:

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

下面是使用编码为ISO-8859-1的字符串voilà的问题演示,其中à表示为一个字节(通过使用\x{e0}创建字节的ansi-c-quoted bash字符串($'...'):

注意,sed命令实际上是一个no-op,它只是简单地传递输入,但我们需要它来引发错误:

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

为了简单地忽略这个问题,可以使用上述LCTYPE=C方法:

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

如果要确定输入的哪些部分导致了问题,请尝试以下操作:

1
2
3
  # 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转换为shell中有效的编码(基于LC_CTYPE,默认为UTF-8),基于上述示例:

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

请注意,此转换允许您正确匹配外部字符:

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

要在处理后将输入转换回ISO-8859-1,只需将结果传输到另一个iconv命令:

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


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

1
2
export LC_CTYPE=C
export LANG=C


mklement0的回答很好,但我有一些小的调整。

在使用iconv时显式指定bash的编码似乎是一个好主意。另外,我们应该预先准备一个字节顺序标记(即使Unicode标准不建议这样做),因为在没有字节顺序标记的情况下,UTF-8和ASCII之间可能存在合法的混淆。不幸的是,当您显式地指定一个endianness(UTF-16BEUTF-16LE时,iconv并没有加上一个字节顺序标记,所以我们需要使用UTF-16,它使用平台特定的endianness,然后使用file --mime-encoding来发现所使用的真正endianness iconv

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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


我的解决方法是使用Perl:

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

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