我正在尝试替换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打印非法字节序列的位置吗?或者有人知道非法字节序列是什么吗?
- 非法字节序列听起来像是将8位ASCII提供给需要UTF-8的东西时得到的。
- 你能试试吗:LC_CTYPE=C && LANG=C && sed command。
- 谢谢大家。这就是江户十一〔一〕事。叹息…
- 有人知道如何确定被标记为无效的序列的开始吗?sed -v导致命令出错,man页没有讨论该主题。
- 有人能告诉我,在-i后面有一个空参数,显示的命令行是如何有效的吗?
- @用户2719058:bsd sed(在OS X上也使用)要求-i ''(单独的空字符串选项参数)在没有备份文件的情况下进行就地更新;使用gnu sed时,只有-i本身起作用-请参阅stackoverflow.com/a/40777793/45375
- 再加一个朗格的。好的悲伤,这是模糊的,不明显的,令人惊讶的难以研究。
显示症状的示例命令: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 |
- +1.详细解释。
- 我认为这是一个更好的选择。首先,我不想在所有终端中失去多语言支持。第二,公认的答案似乎是解决当地问题的全球性解决方案——这是一个需要避免的问题。
- 我对此做了一些小调整。我很感谢你的反馈。stackoverflow.com/a/35046218/9636
- LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'在Sierra为我打印了sed: RE error: illegal byte sequence。echo $LC_ALL输出en_US.UTF-8fwiw。
- @阿考克斯:是的,因为设置LC_ALL会覆盖所有其他LC_*变量,包括LC_CTYPE变量,如答案中所述。
- @mklement0酷,这项工作:"lc撘all=c sed's/*/&;/'<<<$'voilx e0'"。这里为我的同伴解释的优先顺序忽视:pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html
将以下行添加到您的~/.bash_profile或~/.zshrc文件中。
1 2
| export LC_CTYPE=C
export LANG=C |
- 它确实有效,但你能解释一下为什么吗?
- 我尝试将这些变量都设置为en_GB.UTF-8(这是我已经在.bash_配置文件中导出到lang的内容),并得到相同的错误。这里的"C"是什么?
- 以下是我能找到的关于LC_CTYPE的最佳文档:delorie.com/gnu/docs/gawk/gawk_.html
- 这似乎也解决了命令pgrep -f 的问题。
- @hoangpham:将LC_CTYPE设置为C会使字符串中的每个字节成为自己的字符,而不应用任何编码规则。由于违反(UTF-8)编码规则导致了原始问题,这使问题消失。但是,您所支付的价格是,shell和实用程序只将基本的英文字母(7位ASCII范围内的字母)识别为字母。更多信息请参阅我的答案。
- 在shell的启动文件中永久设置此选项将禁用许多有用的行为。您只想将它用于绝对需要它的单个命令。
- 太危险可能会导致意想不到的后果。一个人可以使用LC_CTYPE=C sed …,即只在sed命令上使用。
- 这将完全禁用shell中对Unicode字符的支持。再见,emoji,花式线条绘制字符,带重音的字母,…如其他答案中所述,最好只为sed命令设置此选项。
mklement0的回答很好,但我有一些小的调整。
在使用iconv时显式指定bash的编码似乎是一个好主意。另外,我们应该预先准备一个字节顺序标记(即使Unicode标准不建议这样做),因为在没有字节顺序标记的情况下,UTF-8和ASCII之间可能存在合法的混淆。不幸的是,当您显式地指定一个endianness(UTF-16BE或UTF-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 |
- ++对于有用的技术,尤其是用于发现和报告文件编码的file -b --mime-encoding。但是,有一些方面值得讨论,我将在单独的评论中讨论。
- 我认为现在可以肯定地说,Unix世界已经采用了utf-8:默认的LC_CTYPE值通常是.UTF-8,因此任何没有bom(字节顺序标记)的文件都被解释为utf-8文件。只有在Windows世界中才使用伪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):也就是说,如果您明确地指定了endianness,就不需要通过BOM来反映它,所以不需要添加任何内容。
- 吹毛求疵:它不是bash的编码;它是与当前区域设置相关的编码,基于环境变量,因此与外壳无关;实用工具locale将向您显示有效的区域设置,用LANG和LC_*环境变量(和locale charmap表示,如您所演示的,will打印有效的字符编码)。
- 我运行的是OSX 10.10.5和10.11.3,对于这两个版本,file返回纯文本的ASCII,但是如果在文件中放一个emoji,则返回UTF-8。但是,如果我添加一个BOM,file总是返回utf-8。
- 我没有意识到其他外壳使用了LC变量。
- re-LC_*/LANG变量:bash、ksh和zsh(可能是其他变量,但不包括dash)尊重字符编码;使用v='ä'; echo"${#v}"基于utf-8的区域在posix类shell中验证:支持utf-8的shell应报告1;即应识别多字节序列ä作为单个字符。然而,也许更重要的是:标准实用程序(sed、awk、cut…)也需要有区域/编码意识,虽然它们大多数都在现代类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是完全安全的(从技术上讲,它恰好不包含多字节字符)。
我的解决方法是使用Perl:
1
| find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g' |
我的解决方法是使用GNU sed。为了我的目的工作得很好。
- 实际上,如果您想忽略输入流中的无效字节(不需要LC_ALL=C sed ...解决方法),GNU sed是一个选项,因为GNU sed只通过无效字节而不报告错误,但请注意,如果您想正确地识别和处理输入字符串中的所有字符,就没有办法ARound首先更改输入的编码(通常使用iconv)。