【问题标题】:RE error: illegal byte sequence on Mac OS XRE 错误:Mac OS X 上的非法字节序列
【发布时间】:2013-10-15 01:20:10
【问题描述】:

我正在尝试替换 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 的东西。天哪,这是晦涩难懂、不明显且难以研究的。

标签: regex macos bash sed


【解决方案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

您可以在 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

【讨论】:

    【解决方案2】:

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

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

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

    注意大小写,网页一般都是小写的iconv 使用大写。 您的系统中有 iconv 支持的代码集列表,使用命令 iconv -l

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

    【讨论】:

    【解决方案3】:

    出现以下症状的示例命令: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
    

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

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

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

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

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


    问题是输入文件的编码与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,而试图传递 单一字节 0xE0 在 UTF-8 下无效

    这是一个问题演示,使用编码为 ISO-8859-1 的字符串 voilàà 表示为 one 字节(通过 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/.*/&amp;/' &lt;&lt;&lt;$'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/.*/&/' pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
    【解决方案4】:

    我的解决方法是使用 Perl:

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

    【讨论】:

    • 这个很好用。与其他人不同,我在转义特殊字符时没有任何错误。以前的问题给了我诸如“sed:RE错误:非法字节序列”或sed:1:“path_to_file”:无效命令代码之类的问题。
    • 简单,无需配置等。喜欢它。
    【解决方案5】:

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

    【讨论】:

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

    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。不幸的是,iconv 在显式指定字节序(UTF-16BEUTF-16LE)时不会预先添加字节顺序标记,因此我们需要使用UTF-16,它使用特定于平台的字节序,然后使用@ 987654330@ 发现真正的字节序 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 用于发现和报告文件的编码。但是,有些方面值得讨论,我将在单独的 cmets 中进行。
    • 我认为可以肯定地说 Unix 世界在这一点上已经接受了 UTF-8:默认的LC_CTYPE 值通常是&lt;lang_region&gt;.UTF-8,所以任何文件没有因此,BOM(字节顺序标记)被解释为 UTF-8 文件。仅在 Windows 世界中使用 pseudo-BOM 0xef 0xbb 0xff;根据定义,UTF-8 不需要 BOM 并且不推荐(如您所说);在 Windows 世界之外,这种伪 BOM 会导致事物中断
    • Re Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE):这是设计使然:如果您明确指定字节顺序,则无需通过 BOM 也反映它,因此不会添加任何内容。跨度>
    • Re LC_* / LANG 变量:bashkshzsh(可能是其他人,但 不是 dash)确实尊重字符编码;在类似 POSIX 的 shell 中使用基于 UTF-8 的语言环境和v='ä'; echo "${#v}" 进行验证:支持 UTF-8 的 shell 应该报告 1;即,它应该将多字节序列ä (0xc3 0xa4) 识别为 single 字符。然而,也许更重要的是:标准实用程序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 是完全安全的(从技术上讲,它恰好不包含多字节字符。)
    【解决方案7】:

    将以下行添加到您的 ~/.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 命令设置这个,如其他答案中所述。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-28
    • 2013-01-07
    • 2019-04-01
    • 2011-10-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多