如何在Bash中用printf读写字面字符并过滤无效XML字符?
解决XML无效字符过滤及字符复制丢失问题
首先来说说你当前脚本的问题:你用read -r -n1 char逐个读取字符,但read命令默认会把回车符(\r,也就是XML里的#xD)当作行结束标记,即使加了-n1,遇到\r时它也会终止当前读取操作,导致这个字符没被捕获到,自然输出到文件里就为空了。另外,read的-n1是按字节读取,不是按字符,如果文件里有UTF-8多字节字符(比如中文、Emoji),会被拆成单个字节处理,最后肯定会乱码。
推荐解决方案:用Perl快速过滤XML允许字符
Perl对Unicode字符的处理非常原生,一行命令就能搞定过滤需求,还能保证所有有效字符都被正确复制:
perl -CSAD -pe 's/[^\x09\x0A\x0D\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]//g' "$1" > "$2"
命令解释:
-CSAD:强制Perl以UTF-8编码处理输入、输出和命令行参数,确保多字节字符不会乱码-pe:循环读取输入的每一行,执行替换操作后直接输出- 正则表达式部分:
[^\x09\x0A\x0D\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]匹配所有不在XML允许范围内的字符,然后用空字符串替换(也就是删除这些无效字符)
备选方案:用AWK处理
如果你更习惯用awk,也可以用下面的脚本,它同样能正确处理Unicode字符和特殊控制符:
awk ' BEGIN { # 设置按字符分割输入,强制UTF-8编码 PROCINFO["FS"] = "" PROCINFO["encoding"] = "utf-8" } { # 遍历当前行的每个字符 for (i = 1; i <= length($0); i++) { c = substr($0, i, 1) # 获取字符的Unicode码点 code = ord(c) # 检查是否符合XML允许的字符规则 if (code == 0x09 || code == 0x0A || code == 0x0D || (code >= 0x20 && code <= 0xD7FF) || (code >= 0xE000 && code <= 0xFFFD) || (code >= 0x10000 && code <= 0x10FFFF)) { printf "%s", c } } } # 自定义函数:获取字符的Unicode码点 function ord(c, hex) { if (length(c) == 1) { return sprintf("%d", c) } else { # 处理多字节UTF-8字符,转换为Unicode码点 hex = sprintf("%02x%02x%02x%02x", ord(substr(c,1,1)), ord(substr(c,2,1)), ord(substr(c,3,1)), ord(substr(c,4,1))) return strtonum("0x" hex) } }' "$1" > "$2"
为什么不用bash的read?
除了之前说的回车符丢失问题,bash的read本身就不适合处理包含特殊控制字符或多字节Unicode的文件,它是为处理文本行设计的,不是逐字符处理二进制/复杂文本的工具。如果非要用bash,可能需要结合dd逐个字节读取,但处理多字节字符会非常繁琐,完全没必要。
内容的提问来源于stack exchange,提问作者Timothy Swan
相关产品推荐
相关产品推荐

