根据 bash 中的匹配模式分割行

根据 bash 中的匹配模式分割行

我有以下情况,有两个文件。

文件1

not relevant = does not matter
some stuff
# var1=1
# var 2 = 2
# var3 = 3
some stuff

文件2

some other stuff
# does not matter either
# var1=a
# var 2 = b
# var3 = c
some other stuff

我想使用 bash 脚本从以相应变量及其值开头#并包含=相应变量及其值的所有行中提取,收集所有内容并将其写入新文件

var1,var 2,var3
1,2,3
a,b,c

变量名称可以包含空格。前后也=可能出现空格。右侧的值不包含空格。如果出现空格,两个文件中都会出现空格。

答案1

这是一项需要文本处理语言(例如 awk 或 perl)的任务,而不是 shell 脚本。

$ cat vars2csv.pl 
#!/usr/bin/perl

use strict;

# %vars is a Hash-of-Hashes (HoH) where the primary keys
# are the filenames, and each element is a hash containing
# each "variable" name found in in the input and its
# corresponding value.  See man pages for perldata and perldsc.
my %vars;

# Array @fields and hash %seen to keep track of new
# "variable" names in the order we see them.
my @fields;
my %seen;

# Keep a copy of the arguments so we can output the data in
# the same order we read them.
my @files = @ARGV;

while (<>) {
  chomp;
  next unless /^#.*=/;
  s/^#\s*//;

  my ($key,$val) = split /\s*=\s*/, $_, 2;

  if (!defined($seen{$key})) {
    push @fields, $key;
    $seen{$key} = 1;
  };

  # $ARGV is the name of the current file being read
  # by the `while(<>)` loop.
  $vars{$ARGV}{$key} = $val;
};

print join(",", @fields), "\n";

foreach my $f (@files) {
  next unless -r $f;  # skip output for filenames that weren't readable
  print join(",", @{$vars{$f}}{@fields}), "\n";
};

该脚本会跟踪读取文件的顺序以及查看字段名称的顺序,因为 Perl 哈希本质上是无序的(这对于大多数语言中关联数组的大多数实现来说很常见)。我可以编写它来在输出阶段对键进行排序(perl 有一个非常有用的内置sort函数),所以至少它们会以可预测的顺序输出,但我认为最好使用一些变量来记住原始订单。

它适用于任意数量的输出字段,并且不关心字段名称或值是什么。在匹配行中,任何前导空格之后和第一个=符号之前的所有内容都是“键”,第一个=符号之后的所有内容都是值。周围的空格=不包含在键或值中(该行是在 上分割的,\s*=\s*而不仅仅是=)。perldoc -f split有关 split 函数的详细信息,请参阅。

如果给定的键在文件中出现多次,则最后一次出现的值将是输出的值。如果您希望它保留第一个并忽略任何后续事件,请添加以下行$vars{$ARGV}{$key} = $val;行:

next if (defined($vars{$ARGV}{$key}));

示例运行:

$ chmod +x ./vars2csv.pl

$ ./vars2csv.pl file1 file2 
var1,var 2,var3
1,2,3
a,b,c

值得注意的是:此脚本会忽略所有不以 a 开头#且包含=.这意味着它处理全部与该条件匹配的行 - 包括任何碰巧包含=您不打算定义变量的注释行。根据输入文件中的具体内容,这可能是一个需要修复的错误(通过找出一种模式来排除这些不需要的行,或者通过设计一个更好的模式来仅匹配想要的行)。


顺便说一句,我next unless -r $f;在脚本中添加了该行,因为我使用不存在的文件名参数和阻止读取它们的权限测试了脚本。发生此类错误时,Perl 会打印一条警告消息,但脚本会打印一行,其中包含用逗号分隔的空字段。该行阻止该输出。

该脚本还将打印一行用逗号分隔的空字段可读的不包含任何var=value注释的文件。如果您也想阻止这些文件的输出,请添加以下内容线print join...

  next unless (keys %{ $vars{$f} }); # skip output for files with NO key=val comments

包含某些但不是所有字段的文件将打印其所拥有字段的正确值和任何缺失字段的空值。例如,仅包含的文件# var1=1将打印1,,为输出行。如果您想跳过这些文件的输出:

  next unless (@{$vars{$f}}{@fields}); # skip output for files missing ANY key

答案2

awk与任何 shell 中的any 一起使用:

#!/usr/bin/awk -f
BEGIN {FS = " ?= ?" ; OFS="," ;}
NF == 2 && /^#/ {
    sub(/^# /, "", $1)

    if (FILENAME != oldFileName) {
        files[filesCnt++] = FILENAME
        oldFileName = FILENAME
    }

    hdrYetFoundIdx = -1
    for (i = 0; i < hdrCnt; i++) {
        if (hdr[i] == $1) {
            hdrYetFoundIdx = i
            break
        }
    }
    if (hdrYetFoundIdx == -1) hdr[hdrCnt++] = $1
    val[files[filesCnt-1],$1] = $2
}

END {
    for (i = 0; i < hdrCnt; i++) 
        printf "%s%s", hdr[i], ((i<hdrCnt-1)?OFS:ORS)

    for (i = 0; i < filesCnt; i++)
        for (j = 0; j < hdrCnt; j++)
            printf "%s%s", val[files[i],hdr[j]], ((j<hdrCnt-1)?OFS:ORS)
}

请注意,如果行首之间可以有空格,#awk处理代码的条件需要变为 :NF == 2 && /^ *#/并且调用sub变为 :sub(/^ *#/, "", $1)

答案3

假设第一个文件定义了要获取的变量集:

#!/usr/bin/perl

use strict;
$/=undef;                                  # no input register separator
$,="|";                                    # ouput field separator
$\="\n";                                   # ouput register separa
my (%pair,@var);

while(<>){                                 # for each file
   my %pair= (m/#\s*(\S.*?)\s*=\s*(.*)/g); # get the (var->value) pairs
   if(not @var){
      @var = keys(%pair);                  # get and print schema 
      print( @var );
   }
   print( @pair{@var} );                   # print the values
}

答案4

假设所有文件的所有记录都以相同的顺序为相同的三个变量分配三个变量,其中pcregrep

assign='#.*?=\h*(.*?)\h*'
pcregrep -hMo1 -o2 -o3 --om-separator=, "^$assign\n$assign\n$assign\$" file1 file2 

会给你价值观。对于标头,您可以从第一个文件的第一条记录中提取它:

assign='#\h*(.*?)\h*=.*'
pcregrep -Mo1 -o2 -o3 --om-separator=, "^$assign\n$assign\n$assign\$" file1 | head -n1

相关内容