一个展示这种症状的样例命令:sed 's/./@/' <<<$'\xfc'
失败了,因为字节 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,所以LC_CTYPE=C sed...通常也会起作用,但如果LC_ALL被设置(而不是C),它将覆盖个别的LC_*类别变量,例如LC_CTYPE。因此,最健壮的方法是设置LC_ALL。
然而,将LC_CTYPE有效地设置为C会把字符串视为每个字节都是自己的字符(不执行基于编码规则的解释),而不考虑OS X默认使用的多字节按需UTF-8编码,其中外来字符具有多字节编码。
简而言之:将
LC_CTYPE
设置为
C
会导致shell和实用程序仅将基本英文字母识别为字母(在7位ASCII范围内的字母),因此
外文字符将不被视为字母,例如大小写转换将失败。
如果您不需要匹配多字节编码字符,例如é
,并且只想通过这样的字符,则可能可以接受这种情况。
如果这不足够,或者您想要了解原始错误的原因(包括确定哪些输入字节引起了问题)并且按需执行编码转换,请继续阅读下文。
问题在于输入文件的编码与shell不匹配。
更具体地说,输入文件包含以UTF-8格式无效的字符编码(如@Klas Lindbäck在评论中所述) - 这就是sed错误消息通过“无效字节序列”试图表达的内容。
很可能,您的输入文件使用单字节8位编码,例如ISO-8859-1,常用于编码“西欧”语言。
示例:
重音字母à的Unicode代码点为0xE0(224) - 与ISO-8859-1相同。但是,由于UTF-8编码的特性,这个单一代码点被表示为2个字节 - 0xC3 0xA0,而尝试传递单个字节0xE0在UTF-8下是无效的。
这是一个问题的演示,使用字符串
voilà
编码为
ISO-8859-1
,其中
à
表示为
一个字节(通过使用
\x{e0}
创建字节的ANSI-C引用bash字符串(
$'...'
)):
请注意,sed
命令实际上是一个无操作命令,只是将输入传递,但我们需要它来引发错误:
sed 's/.*/&/' <<<$'voil\x{e0}'
为了简单地忽略这个问题,可以使用上述的
LCTYPE=C
方法:
LC_CTYPE=C sed 's/.*/&/' <<<$'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
),以下是在上面示例的基础上构建的:
# 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}')"
在处理后将输入转换回ISO-8859-1
,只需将结果传输到另一个iconv
命令即可:
sed 's/à/ü/' <<<"$(iconv -f ISO-8859-1 <<<$'voil\x{e0}')" | iconv -t ISO-8859-1
LC_CTYPE=C && LANG=C && sed 命令
- anubhavased
(在OS X上也使用)需要-i''
(单独的空字符串选项参数)才能进行原地更新而不创建备份文件;而对于GNU的sed
,仅使用-i
本身就可以实现 - 参见https://dev59.com/3pzha4cB1Zd3GeqPKMZr#40777793。 - mklement0