使用sed去除十六进制字节 - 无匹配

10

我有一个包含两个非ASCII字节(0xFF和0xFE)的文本文件:

??58832520.3,ABC
348384,DEF

该文件的十六进制表示为:

FF FE 35 38 38 33 32 35 32 30 2E 33 2C 41 42 43 0A 33 34 38 33 38 34 2C 44 45 46

巧合的是,FF和FE恰好是主要的字节(它们存在于我的文件中,尽管似乎总是在一行的开头)。

我试图使用sed删除这些字节,但是我所做的一切似乎都无法匹配它们。

$ sed 's/[^a-zA-Z0-9\,]//g' test.csv 
??588325203,ABC
348384,DEF

$ sed 's/[a-zA-Z0-9\,]//g' test.csv 
??.

主要问题:我该如何去除这些字节?
奖励问题:上面的两个正则表达式是直接取反的,因此其中一个逻辑上必须过滤掉这些字节,对吗?为什么这两个正则表达式都匹配0xFF和0xFE字节?

更新:直接删除一系列十六进制字节的方法(下面两个答案中建议的方法)似乎会删除每行的第一个“合法”字节,并保留我想要摆脱的字节:

$sed 's/[\x80-\xff]//' test.csv
??8832520.3,ABC
48384,DEF

FF FE 38 38 33 32 35 32 30 2E 33 2C 41 42 43 0A 34 38 33 38 34 2C 44 45 46 0A

注意每行开头缺少的"5"和"3",以及文件末尾新增的0A。

更新:这个问题似乎是系统特定的。该问题在OSX系统上被观察到,但建议(包括我上面的原始sed语句)在NetBSD上按照预期工作。

解决方案:同样的任务通过Perl似乎很容易:

$ perl -pe 's/^\xFF\xFE//' test.csv
58832520.3,ABC
348384,DEF

然而,我将保留这个问题的开放状态,因为这只是一个解决方法,而不能解释sed存在的问题。


你的Perl示例给了我巨大的帮助,谢谢。 - JulianHarty
7个回答

6
sed 's/[^ -~]//g'

或者正如另一个答案所暗示的那样。
sed 's/[\x80-\xff]//g'

请参阅sed信息页面的第3.9节。标题为转义。
对于OSX,请编辑本地语言设置为en_US.UTF-8。
尝试。
LANG='' sed 's/[^ -~]//g' myfile

这在我的OSX机器上有效,我不完全确定为什么它在UTF-8时无法工作。

1
谢谢 - 但这对我似乎不起作用。当我在测试文件上运行它时,唯一的变化是在文件末尾附加了一个回车符(x0A)。 - G__
最后一条评论是关于第一种方法的。第二种方法剥离了第一个合法字符(5),但保留了FF和FE字节。我不明白为什么这样做... - G__
哦,你是将sed的结果输出到一个新文件中,即sed 's/[^ -~]//g' test.csv > test1.csv。sed本身不会更改文件,它会将更改后的版本输出到标准输出(stdout)。 - deinst
是的,我只是为了在这里发布而进行内联处理。 - G__
@Greg 你用的是哪个版本的OSX?你有替换原始的sed吗? - deinst
显示剩余5条评论

4
这将删除所有以特定字节FF FE开头的行。
sed -e 's/\xff\xfe//g' hexquestion.txt

你的否定正则表达式不能正常工作的原因是 [] 指定了一个字符类。sed 假设一个特定的字符集,可能是 ASCII。你文件中的这些字符不是7位ASCII字符,因为它们都以 F 开头。sed 不知道如何处理这些字符。上面的解决方案不使用字符类,因此在平台和字符集之间应该更可移植。

谢谢你提供这个信息 - 我之前不知道[]的这个用法。不幸的是,它似乎并不能解决我的特定问题。 - G__
我重新阅读了你的问题,并更新了我的答案以捕捉到所有这种模式的出现。此外,事实证明,这个解决方案在cygwin、Redhat linux 4.8上对我有效,但在旧版Redhat系统和Solaris 9上失败了。较旧版本的sed可能无法处理非ASCII字符。 - Gary

3
在您的文件开头的FFFE字节是所谓的“字节顺序标记(BOM)”。它可以出现在Unicode文本流的开头,以指示文本的字节序。 FF FE表示小端UTF-16。以下是常见问题解答中的一段摘录:

问:我应该如何处理BOM?

答:以下是一些应遵循的指南:

  1. 某些协议(例如Microsoft约定用于 .txt 文件)可能要求在特定的Unicode数据流上(例如文件)使用BOM。当需要符合这样的协议时,请使用BOM。
  2. 对于未标记文本的情况,某些协议允许可选的BOM。在这些情况下,
    • 如果已知文本数据流为纯文本但编码未知,则可以使用BOM作为签名。如果没有BOM,则编码可以是任何内容。
    • 如果已知文本数据流为纯Unicode文本(但不知道大小端),则可以使用BOM作为签名。如果没有BOM,则应将文本解释为大端。
  3. 某些面向字节的协议期望在文件开头有ASCII字符。如果与这些协议一起使用UTF-8,则应避免使用BOM作为编码格式签名。
  4. 如果已知数据流的精确类型(例如Unicode大端或Unicode小端),则不应使用BOM。特别地,在声明数据流为UTF-16BE、UTF-16LE、UTF-32BE或UTF-32LE时,不得使用BOM。

参考资料

另请参阅

相关问题


2
为了证明这不是Unicode BOM的问题,而是八位与七位字符之间的问题,并与语言环境相关,请尝试以下操作:
显示所有字节:
$ printf '123 abc\xff\xfe\x7f\x80' | hexdump -C
00000000  31 32 33 20 61 62 63 ff  fe 7f 80                 |123 abc....|

sed删除用户区域设置中不是字母数字的字符。注意空格和0x7f被删除:

$ printf '123 abc\xff\xfe\x7f\x80'|sed 's/[^[:alnum:]]//g' | hexdump -C
00000000  31 32 33 61 62 63 ff fe  80                       |123abc...|

使用sed在C语言环境中删除非字母数字字符。请注意,只有"123abc"保留:

$ printf '123 abc\xff\xfe\x7f\x80'|LANG=C sed 's/[^[:alnum:]]//g' | hexdump -C
00000000  31 32 33 61 62 63                                 |123abc|

谢谢 - 我一直在尝试从文件中删除特定的字节(0xC3),但是一直没有成功,直到我使用了 LANG=C sed -b -i'' 's/[\xc3]//g' mytext.txt - sdbbs
@sdbbs:你不需要使用方括号,因为你只是选择一个字符而不是多个字符中的任意一个。 - Dennis Williamson

1
在 OS X 上,字节顺序标记可能被读取为单个单词。根据大小端尝试使用 sed 's/^\xfffe//g'sed 's/^\xfeff//g'

0

作为替代方案,你可以使用 ed(1):

printf '%s\n' H $'g/[\xff\xfe]/s///g' ',p' | ed -s test.csv

printf '%s\n' H $'g/[\xff\xfe]/s///g' wq | ed -s test.csv  # in-place edit

0

你可以使用 \xff \xfE 获取十六进制代码,并将其替换为空。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接