You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:23:33