awk/perl 语句验证并返回文件开头的固定结构数据行

awk/perl 语句验证并返回文件开头的固定结构数据行

我有一个像这样开头的文件

## CONFIG-PARAMS-START ##
##
## text1 text2 NNNNNNNNN (arbitrary_comment) ##
## text1 text2 NNNNNNNNN (arbitrary_comment) ##
## text1 text2 NNNNNNNNN (arbitrary_comment) ##
##
## CONFIG-PARAMS-END ##
<arbitrary rest of file>

输出:
我想用awk或来验证文件perl,以检查它是否以这种方式启动。

如果是,则仅输出数据行(而不是开始/结束或“裸”行,或本节之后的任何内容),如果否,则返回非零 rc [$?] 或其他一些易于测试的条件,例如 [空字符串]。

文件规范:
在现代(PRCE)正则表达式中,数据行格式为:

^##[[:space:]]*                    - starts with ## and optional spaces
  (([a-zA-Z0-9_-]+\.)+)            - >=1 repetition of [text_string][dot] (no spaces)
    [[:space:]]+                   - spaces
      ([^[:space:]]+)              - block of non-spaces
        [[:space:]]+               - spaces
          ([0-9]+)                 - block of digits
            [[:space:]]            - spaces
              \(.*                 - '(' + any text
                ##[[:space:]]*$    - 2 hashes, optional spaces + line end

(因此典型的线路可能是## abc.3ef. w;4o8c-uy3tu!ae 9938 (good luck!)##  )

在第一行之前或数据块中的其他任何地方都不能有任何其他行(包括空行/空白行)。在每一行中,连续的空白实际上充当单个分隔符。第一个 ## 之后的空白以及最后一个 ## 之前和之后的空白都是可选的。该部分通常有不到 15 行,因此大小/速度/效率可以忽略不计。

(倒数第二行的贪婪捕获不是问题,它会进行最少的回溯以匹配最后一行的“##”)

兼容性:
广泛的兼容性很重要,因为代码最终需要能够在不同的 Linux、FreeBSD + 其他 BSD 的默认/标准版本上运行,甚至可能是其他现代 *nix 平台。(它是广泛使用的开源软件包补丁的一部分)。也许基本的 POSIX 会提供一个级别字段,而不是只假设某些特定的awk/perl变体?出于同样的原因,可维护性/易于理解也很有用。非常希望避免使用 perl ;-) 最后删除了这一项,看评论

我还没有掌握使用任何文本处理方法进行这种前向和后向引用和检查的技巧,甚至不知道如何管理兼容性/实现中的细微差别。

拥有 Awk/perl 技能的人将非常感激能够获得此代码片段的可用版本!

答案1

这是一个简单的脚本,应该很容易理解,并且可以轻松移植到其他语言,如 AWK、Python、Bash...用法:perl validate.pl input.txt

use strict;
use warnings;

my @data;
my $a = 0;
my ($filename) = @ARGV;
my $expr = '^##[[:space:]]*([a-zA-Z0-9_-]+\.)+[[:space:]]+[^[:space:]]+[[:space:]]+[0-9]+[[:space:]]+(\(.*)##[[:space:]]*$';
open my $fh, "<:encoding(utf8)", $filename or die "Could not open $filename: $!";

while( my $line = <$fh>)  {
    chomp $line;
    if ($. == 1 and $line ne '## CONFIG-PARAMS-START ##') {
        exit 1;
    }
    if ($. == 2 and $line ne '##') {
        exit 1;
    }
    if ($a == 1 and $line eq '##') {
        $a = 2;
        next;
    }
    if ($. > 2 and $a < 2) {
        if ($line =~ /$expr/) {
            push @data, $line;
            $a = 1;
            next;
        } else {
            exit 1;
        }
    }
    if ($a == 2) {
        if ($line eq '## CONFIG-PARAMS-END ##') {
            print join("\n", @data), "\n";
            exit 0;
        } else {
            exit 1;
        }
    }
}

我还写了一个略有不同的、感觉更原生的版本:

use strict;
use warnings;

my @data, my ($filename) = @ARGV, my $expr = '^##\s*([\w-]+\.)+\s+\S+\s+\d+\s+\(.*##\s*$';
open my $fh, "<:encoding(utf8)", $filename or die "Could not open $filename: $!";

while(<$fh>)  {
    chomp;
    if ($. == 1 and !/^## CONFIG-PARAMS-START ##$/) {exit 1}
    if ($. == 2 and !/^##$/) {exit 1}
    if ($. > 2) {
        if (/^##$/ and scalar @data == 0) {exit 1}
        if (/^##$/ and scalar @data  > 0) {
            if (<$fh> =~ /^## CONFIG-PARAMS-END ##$/) {
                print join("\n",@data), "\n"; exit 0;
            } else {exit 1;}
        }
        if (/$expr/) {push @data, $_;} else {exit 1}
    }
}

解释:

  • 正则表达式利用了 Perl 特有的简写方式,让我更容易阅读:

    • \d对于任何数字 ( [0-9])
    • \w表示单词字符 ( [a-zA-Z0-9_])
    • \s为空格 ( [\r\n\t\f\v ])
    • \S对于非空间 ( [^\r\n\t\f\v ])
  • <$fh>$fh从文件句柄读取一行

  • chomp\n删除当前行的尾部
  • $_表示当前元素(行)。
    如果缺失则隐含,因此 egif (/^##$/)实际上表示if($_ =~ /^##$/)。
  • $.包含当前行号
  • scalar @data@data是数组中元素的数量

相关内容