【问题标题】:PERL to count non-printable charactersPERL 计算不可打印字符
【发布时间】:2012-11-09 02:46:27
【问题描述】:

我有 100,000 个文件要分析。具体来说,我想从任意大小的文件样本中计算可打印字符的百分比。其中一些文件来自大型机、Windows、Unix 等,因此很可能包含二进制和控制字符。

我从使用 Linux 的“文件”命令开始,但它没有为我的目的提供足够的详细信息。以下代码传达了我正在尝试做的事情,但并不总是有效。

    #!/usr/bin/perl -n

    use strict;
    use warnings;

    my $cnt_n_print = 0;
    my $cnt_print = 0;
    my $cnt_total = 0;
    my $prc_print = 0;

    #Count the number of non-printable characters
    while ($_ =~ m/[^[:print:]]/g) {$cnt_n_print++};

    #Count the number of printable characters
    while ($_ =~ m/[[:print:]]/g) {$cnt_print++};

    $cnt_total = $cnt_n_print + $cnt_print;
    $prc_print = $cnt_print/$cnt_total;

    #Print the # total number of bytes read followed by the % printable
    print "$cnt_total|$prc_print\n"

这是一个有效的测试调用:

    echo "test_string of characters" | /home/user/scripts/prl/s16_count_chars.pl

我打算这样称呼它,并且适用于一个文件:

    find /fct/inbound/trans/ -name "TRNST.20121115231358.xf2" -type f -print0 | xargs -0 head -c 2000 | /home/user/scripts/prl/s16_count_chars.pl

这不能正常工作:

    find /fct/inbound/trans/ -type f -print0 | xargs -0 head -c 2000 | /home/user/scripts/prl/s16_count_chars.pl

这也不行:

    find /fct/inbound/trans/ -type f -print0 | xargs -0 head -c 2000 | perl -0 /home/user/scripts/prl/s16_count_chars.pl

不是为查找返回的每一行执行一次脚本,而是为所有结果执行一次。

提前致谢。


研究至今:

管道和 XARGS 以及分隔符

http://help.lockergnome.com/linux/help-understand-pipe-xargs--ftopict549399.html

http://en.wikipedia.org/wiki/Xargs#The_separator_problem


澄清:
1.) 期望的输出:如果目录中有 932 个文件,则输出将是 932 行的文件名列表、从文件中读取的总字节数和可打印字符的百分比。
2.) 许多文件是二进制文件。脚本需要处理嵌入的二进制eoleof 序列。
3.)许多文件很大,所以我只想读取第一个/最后一个 xx 字节。我一直在尝试使用 head -c 256tail -c 128 分别读取前 256 个字节或最后 128 个字节。解决方案可以在管道中工作,也可以在 perl 脚本中限制字节。

【问题讨论】:

  • while ($_ =~ m/[^[:print:]]/g) {$cnt_n_print++}; 最好是 $cnt_n_print += ( () = m/[^[:print:]]/g );(或者更好的是,使用 tr///,只是不支持 POSIX 类)
  • "Better" = 更快、更简洁,但使用更多内存。实际上,可能更多。 (每个匹配字符的整个字符串标量!)
  • 不!不是 -n 在 shebang 线上!

标签: perl ascii non-ascii-characters non-printable


【解决方案1】:

-n 选项将您的整个代码包装在 while(defined($_=<ARGV>) { ... } 块中。这意味着您的 my $cnt_print 和其他变量声明会针对每一行输入重复,实质上是重置所有变量值。

解决方法是使用全局变量(如果您想继续使用use strict,请使用our 声明它们),而不是将它们初始化为0,因为它们会为每一行输入重新初始化。你可以这样说

our $cnt_print //= 0;

如果您不希望 $cnt_print 及其朋友在第一行输入中未定义。

看到this recent question 有类似的问题。

【讨论】:

  • 感谢您的快速回复...至于“-n”选项,隐含的while循环是我想要的。如果我通过脚本 172 个文件,我想要 172 个不同的输出(每个文件一个)。是否有使用“-n”或显式“while”的最佳做法?
【解决方案2】:

您可以让find 一次向您传递一个参数。

find /fct/inbound/trans/ -type f -exec perl script.pl {} \;

但我会继续一次传递多个文件,要么通过xargs,要么使用GNU find-exec +

find /fct/inbound/trans/ -type f -exec perl script.pl {} +

以下代码 sn-ps 支持两者。

您可以一次继续阅读一行:

#!/usr/bin/perl

use strict;
use warnings;

my $cnt_total   = 0;
my $cnt_n_print = 0;

while (<>) {
    $cnt_total += length;
    ++$cnt_n_print while /[^[:print:]]/g;
} continue {
    if (eof) {
        my $cnt_print = $cnt_total - $cnt_n_print;
        my $prc_print = $cnt_print/$cnt_total;

        print "$ARGV: $cnt_total|$prc_print\n";

        $cnt_total   = 0;
        $cnt_n_print = 0;
    }
}

或者您可以一次读取整个文件:

#!/usr/bin/perl

use strict;
use warnings;

local $/;
while (<>) {
    my $cnt_n_print = 0;
    ++$cnt_n_print while /[^[:print:]]/g;

    my $cnt_total = length;
    my $cnt_print = $cnt_total - $cnt_n_print;
    my $prc_print = $cnt_print/$cnt_total;

    print "$ARGV: $cnt_total|$prc_print\n";
}

【讨论】:

  • 谢谢!!!真的很接近,但我认为它会阻塞二进制文件,我只需要读取前 X 个字节(参见上面的说明)。我也只能让 GNU -exec 工作。您能否帮助更新脚本,以便它可以使用 head/tail 命令在 linux 管道中工作,例如: a) find /fct/inbound/trans/ -name "TRNST.20121115231358.xf2" -type f -print0 | xargs -0 头 -c 2000 | /home/user/scripts/prl/s16_count_chars.pl 或类似的东西:b)find /path/to/analyze/ -type f -exec perl script.pl {} first 264 + c)find /path/to/analyze/ -type f -exec perl script.pl {} 最后 128 +
  • 好吧,readline (&lt;&gt;) 不太适合二进制文件。你会想要read。使用for (@ARGV) 遍历文件并自己打开它们。
  • 您能否提供一个适用于 find 命令输出的示例?我找到了这个参考,但仍然有问题Perl File Handling: open, read, write and close files
  • 抱歉,无法解决您未提及的问题。
  • 感谢您的帮助。我根据您的建议发布了我的工作解决方案。
【解决方案3】:

根据所提供的反馈,这是我的工作解决方案

如果对形式或更有效的方法有任何进一步的反馈,我将不胜感激:

    #!/usr/bin/perl

    use strict;
    use warnings;

    # This program receives a file path and name.
    # The program attempts to read the first 2000 bytes.
    # The output is a list of files, the number of bytes
    # actually read and the percent of tbe bytes that are
    # ASCII "printable" aka [\x20-\x7E].

    my ($data, $n_bytes, $file_name, $cnt_n_print, $cnt_print, $prc_print);

    # loop through each file
    foreach(@ARGV) {
       $file_name = shift or die "Pass the file name on the command line.\n";

       # open the file read only with "<" in "<$file_name"
       open(FILE, "<$file_name") or die "Can't open $file_name: $!";

       # open each file in binary mode to handle non-printable characters
       binmode FILE;

       # try to read 2000 bytes from FILE, save the results in $data and the
       # actual number of bytes read in $n_bytes
       $n_bytes = read FILE, $data, 2000;

       $cnt_n_print = 0;
       $cnt_print = 0;

       # count the number of non-printable characters
       ++$cnt_n_print while ($data =~ m/[^[:print:]]/g);

       $cnt_print = $n_bytes - $cnt_n_print;
       $prc_print = $cnt_print/$n_bytes;

       print "$file_name|$n_bytes|$prc_print\n";
       close(FILE);
    }

以下是如何调用上述脚本的示例:

    find /some/path/to/files/ -type f -exec perl this_script.pl {} +

以下是我认为有用的参考资料列表:

POSIX Bracket Expressions
Opening files in binmode
Read function
Open file read only

【讨论】:

  • @mob @ikegami 在进一步的测试中,我发现如果使用上面列出的find 命令调用,此解决方案会跳过目录中的一些文件。例如,在一个目录中有 39 个文件,但脚本仅输出 20 个文件的信息。如果我对每个文件单独运行脚本,对于使用 find 跳过的 19 个文件,它也可以正常工作。您对如何为目录中的所有文件运行脚本有什么想法吗?
  • @mob @ikegami @ysth 如果我从批处理脚本调用 Perl 脚本,它会针对所有文件运行:find /some/path/to/files/ -type f -print|while read filename do perl /path/to/this_script.pl $filename done 这样做的“正确”方式是什么?
猜你喜欢
  • 2014-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-08
  • 1970-01-01
  • 1970-01-01
  • 2011-05-05
  • 2018-04-28
相关资源
最近更新 更多