在文件中删除非ASCII字符

38

我怎样从文件中删除非ASCII字符?


4
这里有一个非常相似的帖子,询问如何在UNIX中查找文件中的非ASCII字符:https://dev59.com/43A75IYBdhLWcg3w-OVA - hotshot309
1
我认为上面的“重复”链接实际上与这个问题无关。那些链接是关于在非ASCII字符中进行grep,它要么显示整行,要么不显示。而这个问题是关于删除所有非ASCII字符的。我认为这个问题更相关:https://dev59.com/ZXA75IYBdhLWcg3wW3sZ - wisbucky
5个回答

64

如果您想使用Perl,可以按照以下方式进行:

perl -pi -e 's/[^[:ascii:]]//g' filename

详细解释

以下解释假定读者对解决方案中的任何部分都不熟悉...

  • perl

    运行 Perl 解释器。Perl 是一种编程语言,通常在所有类 Unix 系统上都可用。该命令需要在 shell 提示符下运行。

  • -p

    -p 标志告诉 perl 迭代处理输入文件中的每一行,在每一行上运行指定的命令(稍后描述),然后打印结果。它相当于将 perl 程序包装在 while(<>) { /* program... */; } continue { print; } 中。有一个类似的 -n 标志,它执行相同的操作,但省略了 continue { print; } 块,因此如果你想自己打印输出,则可以使用该标志。

  • -i

    -i 标志告诉 perl 输入文件将直接被编辑,并且输出应该返回到该文件中。这很重要,才能真正修改文件。如果省略此标志,输出将写入 STDOUT,然后可以将其重定向到新文件中。

    请注意,你不能省略 -i 并将 STDOUT 重定向到输入文件,因为这会在读入文件之前覆盖输入文件。这只是 shell 的工作原理,与 perl 没有任何关系。 -i 标志可以很明智地解决这个问题。

    Perl 和 shell 允许你将多个单字符参数组合成一个,这就是为什么我们可以使用 -pi 而不是 -p -i

    -i 标志接受一个单一的参数,即要使用的文件扩展名,如果要备份原始文件,则可以将其设置为 -i.bak,然后 perl 会在进行更改之前将输入文件复制到 filename.bak。在这个例子中,我省略了创建备份,因为我期望你会使用版本控制 :)

  • -e

    -e 标志告诉 perl 下一个参数是一个完整的 perl 程序,封装在一个字符串中。如果你有一个非常长的程序,则这不总是一个好主意,因为它可能会变得难以阅读,但对于我们这里的单个命令程序来说,它的简洁性可以提高可读性。

注意,我们不能将-e标志与-i标志组合使用,因为它们都接受单个参数,perl会认为第二个标志是参数。例如,如果我们使用了-ie <program> <filename>,perl会认为<program><filename>都是输入文件,并尝试创建<program>e<filename>e,并假设e是要用于备份的扩展名。这将失败,因为<program>实际上不是一个文件。反过来(-ei)也不起作用,因为perl会尝试将i作为程序执行,这将导致编译失败。

s/.../.../

这是perl基于正则表达式的替换运算符。它需要四个参数。第一个在运算符之前,如果没有指定,则使用默认值$_。第二个和第三个在/ 符号之间。第四个在最后一个/ 之后,而在本例中为g

  • $_在我们的代码中,第一个参数是$_,它是perl中默认的循环变量。如上所述,-p标志将我们的程序包装在while(<>)中,它创建一个while循环,逐行从输入读取(<>)。它隐式地将此行分配给$_,如果未指定所有只需要单个参数的命令都将使用这个变量(例如:只调用print;实际上会转换为print $_;)。因此,在我们的代码中,s/.../.../运算符对输入文件的每一行操作一次。
  • 第二个参数是要在输入字符串中搜索的模式。该模式是一个正则表达式,因此任何在[]中间的内容都是括号表达式。这部分可能是这个示例中最复杂的部分,我们将在最后详细讨论它。
  • 第三个参数是替换字符串,在我们的情况下为空字符串,因为我们想删除所有非ASCII字符。

g 是替换操作符的第四个参数,它是一个修饰符标志。 g 标志指定替换应在输入中的所有匹配项上全局进行。 如果没有此标志,则仅替换第一个实例。 其他可能的标志是用于不区分大小写的匹配的i,只与多行字符串有关的sm(我们这里只有单行字符串),指定模式应预编译的o(对于长文件可能会有用),以及指定模式可以包括空格和注释以使其更易读的x(但如果情况如此,我们不应该将程序编写为单行)。

filename

这是包含非 ASCII 字符的输入文件,我们希望去除它们。

[^[:ascii:]]

现在让我们更详细地讨论 [^[:ascii:]]

如上所述,正则表达式中的 [] 指定一个方括号表达式,它告诉正则表达式引擎在输入中匹配一个字符,该字符匹配表达式内字符集合中的任何一个字符。 因此,例如,[abc] 将匹配 abc 中的任何一个,并且只会匹配一个字符。 使用 ^ 作为第一个字符可以反转匹配,因此 [^abc] 将匹配任何不是 abc 的单个字符。

那么方括号表达式中的 [:ascii:] 是什么意思呢?

如果您有一个基于 unix 的系统,请在命令行上运行 man 7 re_format 来阅读 man 页面。 如果没有,请阅读在线版本

[:ascii:] 是表示整个 ascii 字符集的字符类,但这种字符类只能在方括号表达式内使用。 使用它的正确方式是 [[:ascii:]],并且可以像上面的 abc 案例一样否定它,或者与其他字符组合在方括号表达式中,因此例如,[éç[:ascii:]] 将匹配所有 ascii 字符以及非 ascii 的 éç,而 [^éç[:ascii:]] 将匹配所有不是 ascii 且也不是 éç 的字符。


刚刚在看到你的评论之前完成了这个。 - janar
@bluesmoon,你能把它分解开来并解释一下发生了什么吗? - Joshua Robinson
4
@JoshuaRobinson 我已经编辑了答案并提供了详细的解释。 - bluesmoon
1
有几个人尝试编辑这个答案,将 ^ 移动到内部的 [] 中,这是不正确的。[:ascii:] 标记是一个完整的标记,不能以任何方式修改。 - bluesmoon

11
tr -dc [:graph:][:cntrl:] < input-file > cleaned-file

假设你想保留“控制字符”和“可打印字符”,就像你想要的那样。根据需要进行微调。


5
perl -pe's/[[:^ascii:]]//g' < input.txt > output.txt

1
这正是我所做的来解决问题。 - janar

3

您可以编写如下的C程序:

#include <stdio.h>
#include <ctype.h>

int main(int argc, char **argv)
{
   FILE *fin = fopen("source_file", "rb");
   FILE *fout = fopen("target_file", "w");
   int c;
   while ((c = fgetc(fin)) != EOF) {
       if (isprint(c))
          fputc(c, fout);
   }
   fclose(fin);
   fclose(fout);
   return 0;
}

注意:为了简单起见,错误检查被省略。
使用以下命令进行编译:
$ gcc -W source_code.c -o convert

使用以下命令运行:

$ ./convert

1

我的个人意见:它可能不能解决你的问题,但是它可能会给你一些提示。

file 命令告诉你文件编码,即 UTF, ASCII 等,而 iconv 可以在不同的编码之间转换文件。


2
iconv 竟然在 XML 文件中剥离了一些其他的东西.. 我使用了 iconv -f ascii -t ascii -c。 - janar

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