2つのファイルがある次の状況があります。
ファイル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など)が必要な操作です。
$ 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
分割機能の詳細についてはを参照してください。
特定のキーがファイルに複数回表示されると、最後に発生した値が値出力になります。最初のイベントを維持して後続のイベントを無視するには、次の行を追加します。今後銀行$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
一部のフィールドが含まれているがすべてではないファイルは、そのフィールドには正しい値を印刷し、欠落しているフィールドにはNULL値を印刷します。たとえば、含まれているファイルのみが出力ラインとして# var1=1
印刷されます。1,,
このファイルの出力をスキップするには:
next unless (@{$vars{$f}}{@fields}); # skip output for files missing ANY key
答え2
awk
すべてのシェルで使用:
#!/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
すべてのファイルのすべてのレコードに同じ3つの変数が同じ順序で割り当てられているとします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