我正在尝试替换 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
打印非法字节序列的位置?或者有谁知道非法字节序列是什么?
LC_CTYPE=C && LANG=C && sed command
LANG
的事情。叹....
sed
(也用于 OS X)需要 -i ''
(单独的空字符串选项参数)用于在没有备份文件的情况下进行就地更新;使用 GNU sed
,只有 -i
本身有效 - 请参阅 stackoverflow.com/a/40777793/45375
显示以下症状的示例命令: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
注意:重要的是 C
的 有效 LC_CTYPE
设置,因此 LC_CTYPE=C sed ...
通常 也可以工作,但如果 LC_ALL
恰好被设置(到C
以外的东西),它将覆盖各个 LC_*
类别变量,例如 LC_CTYPE
。因此,最稳健的方法是设置 LC_ALL
。
但是,(有效地)将 LC_CTYPE
设置为 C
会将字符串视为每个字节都是其自己的字符(不执行基于编码规则的解释),其中 < strong>不考虑 - multibyte-on-demand - OS X 默认采用 UTF-8 编码,其中 外来字符 具有 multibyte编码。
简而言之:将 LC_CTYPE
设置为 C
会导致 shell 和实用程序仅将基本英文字母识别为字母(7 位 ASCII 范围内的字母),因此 foreign字符。不会被视为字母,从而导致例如大写/小写转换失败。
同样,如果您不需要匹配多字节编码的字符(例如 é
),而只是想通过这些字符,这可能没问题。
如果这还不够和/或您想了解原始错误的原因(包括确定导致问题的输入字节)并按需执行编码转换,请继续阅读下文。
问题是输入文件的编码与 shell 的编码不匹配。
更具体地说,输入文件包含以 UTF-8 无效的方式编码的字符(如@Klas Lindbäck 所述在评论中)- 这就是 invalid byte sequence
的 sed
错误消息试图表达的内容。
您的输入文件很可能使用了单字节 8 位编码,例如 ISO-8859-1
,经常用于对“西欧”语言进行编码。
例子:
重音字母 à
具有 Unicode 代码点 0xE0
(224) - 与 ISO-8859-1
中的相同。然而,由于 UTF-8 编码的性质,这个单一的代码点被表示为 2 字节 - 0xC3 0xA0
,而试图传递 单一字节< /em> 0xE0
在 UTF-8 下无效。
这是一个问题演示,使用编码为 ISO-8859-1
的字符串 voilà
,其中 à
表示为 一个 字节(通过 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
将以下行添加到您的 ~/.bash_profile
或 ~/.zshrc
文件中。
export LC_CTYPE=C
export LANG=C
LC_CTYPE
设置为 C
会使字符串中的每个字节成为自己的字符,而不应用任何编码规则。由于违反(UTF-8)编码规则导致了原始问题,这使得问题消失了。但是,您付出的代价是 shell 和实用程序只能将基本的英文字母(7 位 ASCII 范围内的那些)识别为字母。有关更多信息,请参阅我的答案。
LC_CTYPE=C sed …
,即仅在 sed 命令上。
我的解决方法是使用 Perl:
find . -type f -print0 | xargs -0 perl -pi -e 's/was/now/g'
您只需在 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 代码集。
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。不幸的是,当您明确指定字节顺序(UTF-16BE
或 UTF-16LE
)时,iconv
不会预先添加字节顺序标记,因此我们需要使用 UTF-16
,它使用特定于平台的字节顺序,然后使用 { 9} 发现使用的真正字节顺序 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
。但是,有一些方面值得讨论,我将在单独的评论中进行说明。
LC_CTYPE
值通常是 <lang_region>.UTF-8
,所以任何文件没有 BOM(字节顺序标记)因此被解释为 UTF-8 文件。仅在 Windows 世界中使用 pseudo-BOM 0xef 0xbb 0xff
;根据定义,UTF-8 不需要 BOM 并且不推荐(如您所说);在 Windows 世界之外,这种伪 BOM 会导致事物中断。
Unfortunately, iconv doesn't prepend a byte-order mark when you explicitly specify an endianness (UTF-16BE or UTF-16LE)
:这是设计使然:如果您明确指定字节顺序,则无需通过 BOM 来反映它,因此不会添加任何内容。
LC_*
/ LANG
变量:bash
、ksh
和 zsh
(可能还有其他,但 not dash
)确实尊重字符编码;在类似 POSIX 的 shell 中使用基于 UTF-8 的语言环境和 v='ä'; echo "${#v}"
进行验证:支持 UTF-8 的 shell 应该报告 1
;即,它应该将多字节序列ä
(0xc3 0xa4
) 识别为单个 字符。然而,也许更重要的是:标准实用程序(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 是完全安全的(从技术上讲,它恰好不包含多字节字符。)
我的解决方法是使用 gnu sed
。为我的目的工作得很好。
LC_ALL=C sed ...
解决方法),GNU sed
是一个选项,因为 GNU sed
只是通过传递无效字节而不是报告错误,但请注意,如果您想正确识别和处理输入字符串中的所有字符,则无法先更改输入的编码(通常,使用iconv
)。
有谁知道如何让 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
不定期副业成功案例分享
LC_CTYPE=C sed 's/.*/&/' <<<$'voil\x{e0}'
在 Sierra 上为我打印sed: RE error: illegal byte sequence
。echo $LC_ALL
输出en_US.UTF-8
FWIW。LC_ALL
覆盖 所有其他LC_*
变量,包括LC_CTYPE
,如答案中所述。