231

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

4

7 回答 7

349

显示以下症状的示例命令:sed 's/./@/' <<<$'\xfc'失败,因为 byte0xfc不是有效的 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 ...通常也可以工作,但如果碰巧设置(设置为 以外的其他值),它将覆盖单个-category 变量,例如. 因此,最稳健的方法是设置.LC_ALLCLC_*LC_CTYPELC_ALL

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

简而言之:设置LC_CTYPEC使 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个字节 - ,而在 UTF-8 下0xC3 0xA0尝试传递单个字节 0xE0无效的。

下面是使用编码为的字符串的问题演示,表示为一个字节(通过用于创建字节的 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列出所有支持的。

例子:

在上面的示例的基础上,将 FROM 转换ISO-8859-1为 shell 中有效的编码(基于,默认情况下基于 -):LC_CTYPEUTF-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
于 2014-05-10T17:53:19.103 回答
164

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

export LC_CTYPE=C 
export LANG=C
于 2013-11-04T14:54:06.403 回答
18

我的解决方法是使用 Perl:

find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
于 2018-02-19T15:52:56.610 回答
4

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

bash使用时明确指定 的编码似乎是个好主意iconv。此外,我们应该在前面加上一个字节顺序标记(即使 unicode 标准不推荐它),因为没有字节顺序标记的 UTF-8 和 ASCII 之间可能存在合理的混淆。不幸的是,当您显式指定字节序(或)时,iconv不会预先添加字节顺序标记,因此我们需要使用,它使用特定于平台的字节序,然后用于发现所使用的真正字节序。UTF-16BEUTF-16LEUTF-16file --mime-encodingiconv

(我所有的编码都是大写的,因为当你列出所有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
于 2016-01-27T19:22:21.877 回答
4

您只需在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 代码集。

于 2019-05-10T17:26:27.093 回答
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阐明了一些观点:注意 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
于 2019-11-03T19:50:16.220 回答
0

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

于 2016-08-31T05:44:44.440 回答