複数のセクションで構成されるテキストファイルがあり、各セクションには2つのヘッダー行とスペースで区切られた単語で構成される本文行があります。例は次のとおりです。
Shares for DED-SHD-ED-1:
[--- Listable Shares ---]
backup backup2
Shares for DED-SHD-ED-2:
[--- Listable Shares ---]
ConsoleSetup REMINST SCCMContentLib$ SCCMContentLibC$ SEFPKGC$ SEFPKGD$ SEFPKGE$ SEFSIG$ Source UpdateServicesPackages WsusContent backup backup2
Shares for DED-SHD-BE-03:
[--- Listable Shares ---]
backup backup2 print$
削除したいボディラインで見るとすべての単語が発生3回以上。
- 削除したいみんな「最初の2回以降のすべての発生」と同様に発生。
- 一致させるトークンは、スペースで区切られた「単語」です。つまり、
print$
英数字部分だけでなく全体ですprint
。 - 一致は「完全な単語」にのみ適用する必要があります。つまり、部分文字列の一致は適用されません。たとえば、すべての発生は
backup
削除にのみ含まれ、backup
削除には含まれませんbackup2
。 - ヘッダー行(
Shares for ...
および)は考慮されません。[--- Listable Shares ---]
上記の入力に必要な出力は次のとおりです。
Shares for DED-SHD-ED-1:
[--- Listable Shares ---]
Shares for DED-SHD-ED-2:
[--- Listable Shares ---]
ConsoleSetup REMINST SCCMContentLib$ SCCMContentLibC$ SEFPKGC$ SEFPKGD$ SEFPKGE$ SEFSIG$ Source UpdateServicesPackages WsusContent
Shares for DED-SHD-BE-03:
[--- Listable Shares ---]
print$
ご覧のように、3つの部分の本文行にbackup
andという単語だけがbackup2
表示されるため、削除されました。ただし、ヘッダー行は編集対象とは見なされないため、セクションヘッダー行の、およびShares
はfor
変更されていません。Listable
いくつかの注意:
- これらのファイルのサイズは100kBから1MBまでです。
- 同様の解決策が見つかり
awk '++A[$0] < 3'
ましたが、最初の2つの項目を維持し、行全体を見てみましょう。 - 私は特にAwkベースのソリューションを探しているのではなく、何でも(Perlを除く;)することができます。
答え1
最大1MBのファイルを処理する必要があるため、効率を向上させるには複数の配列の反転が必要です。単語を削除しているので、正確な間隔を維持することは重要ではないと思うので、代替行の各単語の前にTABが続きます。
これは、独自の awk プログラムを含む単一のシェル関数を含む Bash スクリプトです。入力ファイル引数を使用してstdoutに出力します。
結果をどのように確認したいのか分かりません。私は開発中に多くのデバッグをしました。たとえば、削除された単語とその頻度をstderrに書き込むのは簡単です。
#! /bin/bash
delByFreq () {
local Awk='
BEGIN { SEP = "|"; Freq = 3; }
#.. Store every input line.
{ Line[NR] = $0; }
#.. Do not look for words on header lines.
/^Shares for / { next; }
/--- Listable Shares ---/ { next; }
#.. Keep an index to row/column of every unique word.
#.. So like: Ref ["backup2"] = "|2|3|5|1|5|7";
function Refer (row, txt, Local, f) {
for (f = 1; f <= NF; ++f)
Ref[$(f)] = Ref[$(f)] SEP row SEP f;
}
{ Refer( NR, $0); }
#.. Rearrange field indexes by line.
#.. So like: Del[row] = "|3|7|11"; for field numbers.
function refByLine (Local, word, j, n, V) {
for (word in Ref) {
n = split (Ref[word], V, SEP);
if (n <= 2 * Freq) continue;
for (j = 2; j < n; j += 2)
Del[V[j]] = Del[V[j]] SEP (V[j+1]);
}
}
#.. For every line with deletions, cross off the frequent words.
function Deletions (Local, row, j, f, n, V, X) {
for (row in Del) {
split (Del[row], V, SEP);
split ("", X, FS); for (j = 2; j in V; ++j) X[V[j]];
#.. Rebuild the line in field order.
split (Line[row], V, FS); Line[row] = "";
for (j = 1; j in V; ++j)
if (! (j in X)) Line[row] = Line[row] "\t" V[j];
}
}
function Output (Local, r) {
for (r = 1; r in Line; ++r) printf ("%s\n", Line[r]);
}
END { refByLine( ); Deletions( ); Output( ); }
'
awk -f <( printf '%s' "${Awk}" ) "${1}"
}
delByFreq "${1}"
答え2
出力と同じ間隔を持つように、GNUをawk
4番目の引数として使用してsplit()
一致する文字列を保持します。FS
$ cat tst.awk
{ begFld = 1 }
/^Shares for/ { begFld = 3 }
/\[--- Listable Shares ---]/ { begFld = NF+1 }
NR == FNR {
for ( i=begFld; i<=NF; i++ ) {
cnt[$i]++
}
next
}
{
split($0,unused,FS,seps)
out = seps[0]
for ( i=1; i<=NF; i++ ) {
out = out ( (i >= begFld) && (cnt[$i] >= 3) ? "" : $i ) seps[i]
}
print out
}
$ awk -f tst.awk file file
Shares for DED-SHD-ED-1:
[--- Listable Shares ---]
Shares for DED-SHD-ED-2:
[--- Listable Shares ---]
ConsoleSetup REMINST SCCMContentLib$ SCCMContentLibC$ SEFPKGC$ SEFPKGD$ SEFPKGE$ SEFSIG$ Source UpdateServicesPackages WsusContent
Shares for DED-SHD-BE-03:
[--- Listable Shares ---]
print$
while ( match(...) )
代わりに、ループを使用してawkで同じことを実行できます。split(...); for (...)
コードを数行だけ追加する必要があります。たとえば、以下はすべてのawkで動作します。
$ cat tst.awk
{ begFld = 1 }
/^Shares for/ { begFld = 3 }
/\[--- Listable Shares ---]/ { begFld = NF+1 }
NR == FNR {
for ( i=begFld; i<=NF; i++ ) {
cnt[$i]++
}
next
}
{
i = 0
out = ""
while ( match($0,/[^ \t]+/) ) {
sep = substr($0,1,RSTART-1)
fld = substr($0,RSTART,RLENGTH)
out = out sep ( (++i >= begFld) && (cnt[fld] >= 3) ? "" : fld )
$0 = substr($0,RSTART+RLENGTH)
}
print out $0
}
$ awk -f tst.awk file file
Shares for DED-SHD-ED-1:
[--- Listable Shares ---]
Shares for DED-SHD-ED-2:
[--- Listable Shares ---]
ConsoleSetup REMINST SCCMContentLib$ SCCMContentLibC$ SEFPKGC$ SEFPKGD$ SEFPKGE$ SEFSIG$ Source UpdateServicesPackages WsusContent
Shares for DED-SHD-BE-03:
[--- Listable Shares ---]
print$
END
編集:@Paul_Pedantと私は、以下に示すように入力を配列に読み込み、そのセクションで処理することの長所と短所についてコメントしました。彼の台本上記のスクリプトと同様に、入力ファイルを2回読み取ったので、スクリプトをシェルスクリプトに入れ、bash shebangを追加しました。
#!/usr/bin/env bash
awk '
{ begFld = 1 }
...
print out
}
' "$1" "$1"
次に、次のように、OPs 9行入力ファイルの100万コピーである入力ファイルを作成します。
$ awk '{r=(NR>1 ? r ORS : "") $0} END{for (i=1; i<=1000000; i++) print r}' file > file1m
その後、私のスクリプトを定期的に実行します。
$ time ./tst_ed.sh file1m > ed.out
real 1m3.814s
user 0m57.781s
sys 0m0.265s
しかし、Paulsスクリプトを実行しようとすると、次のようになります。
$ time ./tst_paul.sh file1m > paul.out
ノートパソコンからヘリコプターが離陸する音が聞こえ始めて5分後に停止し、ノートパソコンが再び安定するまで3分ほど待ちました。
その後、100,000個のファイルに対して2つの方法を試しました。
$ awk '{r=(NR>1 ? r ORS : "") $0} END{for (i=1; i<=100000; i++) print r}' file > file100k
$ time ./tst_ed.sh file100k > ed.out
real 0m6.035s
user 0m5.875s
sys 0m0.031s
$ time ./tst_paul.sh file100k > paul.out
しかし、結局私はPaulsを邪魔しなければなりませんでした(10分間時間を与えました)。
その後、ファイルを使用して10,000回試しました。
$ awk '{r=(NR>1 ? r ORS : "") $0} END{for (i=1; i<=10000; i++) print r}' file > file10k
$ time ./tst_ed.sh file10k > ed.out
real 0m0.783s
user 0m0.609s
sys 0m0.045s
$ time ./tst_paul.sh file10k > paul.out
real 0m1.039s
user 0m0.921s
sys 0m0.031s
今回は両方の出力が得られたので、両方diff -b
で実行して出力が異なることを発見しました。
$ diff -b ed.out paul.out |head
1c1
< Shares for
---
> Shares for DED-SHD-ED-1:
4c4
< Shares for
---
> Shares for DED-SHD-ED-2:
7c7
< Shares for
私は行末から重複した値を削除しましたが、Shares for ...
Paulはそうではありませんでした。 IdkはこれがOPが望む動作であるかもしれませんし、重要であっても非現実的な入力かもしれません。
それから1,000回を試しました。
$ awk '{r=(NR>1 ? r ORS : "") $0} END{for (i=1; i<=1000; i++) print r}' file > file1k
$ time ./tst_ed.sh file1k > ed.out
real 0m0.133s
user 0m0.077s
sys 0m0.015s
$ time ./tst_paul.sh file1k > paul.out
real 0m0.133s
user 0m0.046s
sys 0m0.046s
そして100番:
$ awk '{r=(NR>1 ? r ORS : "") $0} END{for (i=1; i<=100; i++) print r}' file > file100
$ time ./tst_ed.sh file100 > ed.out
real 0m0.080s
user 0m0.000s
sys 0m0.015s
$ time ./tst_paul.sh file100 > paul.out
real 0m0.081s
user 0m0.000s
sys 0m0.000s
したがって、約1k以下のOPデータ冗長(つまり、入力ファイルの最大約10k行)の場合、データをメモリに保存してENDセクションから解析するか、入力ファイルを2回読み取るかは実行速度の問題です(10分1秒の実行時間に達すると誰が気になりますか?)約10,000回の繰り返し(約100,000の入力行)で2読み方が少し速いですが、どちらもすぐに約1秒の実行時間に行われます。ただし、入力ファイルのサイズがこれより大きくなると、実際にはそれをメモリに保存しようとしたくありません。