テキストファイルから2つ以上の単語(スペースで区切られていない)を含む行を削除する方法は?
文書には、これらの単語の「シングルバージョン」もあります。
たとえば、
alpha
beta
gama
alphabeta
zeta
gamabeta
出力は次のようになります。
alpha
beta
gama
zeta
編集する:私のファイルには150万行が含まれています。
答え1
かなり短いファイルの場合、行にERE演算子が含まれていないと仮定すると、次のようになります。
$ LC_ALL=C grep -vxE "($(paste -sd '|' file)){2,}" file
alpha
beta
gama
zeta
2つ以上の行シーケンスを含まない行を返しますfile
。
どのように機能するかは、grep
次のコマンドを書くことです。
LC_ALL=C grep -vxE '(alpha|beta|gama|alphabeta|zeta|gamabeta){2,}' file
より大きなファイルの場合は、長さまたはパラメータ+環境(またはLinuxの単一パラメータ)制限に直面します。引数の代わりに標準入力を使用して正規表現を渡すことでこの問題を解決できますが-f -
、それでも正規表現のサイズに制限があります。
perl
代わりに、以下を使用してgrep
より大きな入力を処理できます。
perl -le '
chomp (@words = <>);
$re = "^(" . join("|", map {qr{\Q$_\E}} @words) . "){2,}\\z";
for (@words) {print unless m/$re/}' file
(これは上記の他の制限も防止します)。
各単語を他の単語と比較する必要があるため(おそらく2回以上)、とにかく時間がかかります。
答え2
これにより、ファイル内の2つの単語の組み合わせではなく、ファイル内のすべての単語が印刷されます。
$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file
alpha
beta
gama
zeta
コマンドを複数行に分けたい人のために:
awk '
{
one[NR]=$1
}
END{
for (i=1;i<=length(one);i++)
for (j=1;j<=length(one);j++)
two[one[i] one[j]]
for (i=1;i<=length(one);i++)
if (!(one[i] in two))
print one[i]
}' file
他の例
同様の単語を含むファイルを考えてみましょう。しかし、時には個々の単語の前に組み合わせが現れることがあります。
$ cat file2
alphabeta
alpha
gammaalpha
beta
gamma
同じコマンドを実行しても、正しい結果が生成されます。
$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file2
alpha
beta
gamma
どのように動作しますか?
one[NR]=$1
one
これにより、キーが行番号でNR
値がその行の単語である配列が生成されます。END{...}
中括弧内のコマンドは、ファイルの読み取りが完了した後に実行されます。このコマンドは2つのループで構成されています。最初のループは次のとおりです。
for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]
two
これにより、ファイル内の2つの単語のすべての組み合わせで構成されるキーを含む配列が作成されます。2番目のループは次のとおりです。
for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]
このループは、配列のキーとして表示されないファイル内のすべての単語を印刷します
two
。
短くてシンプルなバージョン
このバージョンはより短いコードを使用し、同じ単語を印刷します。欠点は、単語の順序が入力ファイルと同じであることは保証されないことです。
$ awk '{one[$1]} END{for (w1 in one) for (w2 in one) two[w1 w2]; for (w in one) if (!(w in two)) print w}' file1
gama
zeta
alpha
beta
メモリを節約する方法
大容量ファイルの場合、上記の方法でメモリオーバーフローが発生する可能性があります。このような場合は、次の点を考慮してください。
$ sort -u file | awk '{one[$1]} END{for (w1 in one) for (w2 in one) print w1 w2}' >doubles
$ grep -vxFf doubles file
alpha
beta
gama
zeta
これはsort -u
、file1から重複した単語を削除し、という名前の二重単語を含むことができるファイルを作成するために使用されますdoubles
。それから。grep
file
doubles
答え3
<file awk 'NF {print length "\t" $0}' | sort -k1n,1 | cut -f2- |
awk 'NR==1 {min=length}
(l=length) >= 2*min {
delete k; # clear k array
k[1];
while (length(k))
for (i in k) {
for (j=l-i+1; j>=min; --j)
if (substr($0,i,j) in seen) {
if (i+j-1==l)
next;
k[i+j];
}
delete k[i];
}
}
!seen[$0]++'
以前に見た行だけで構成された行は印刷されません。
すでに表示されている文字列に部分文字列があることを確認して動作します。
入力ファイルを行の長さに応じて、最短から最長までソートする必要があります。awk | sort | cut
この方法。
次のawk
プログラムは最初に最短行の長さを記録します(として保存されますmin
)。長さが次より小さい行は、対応する部分2*min
文字列をチェックする必要はありません。代わりに、seen
配列ハッシュに追加して印刷することができます(!seen[$0]++
重複していない項目を印刷するための条件として使用されます。awk '!a[$0]++' はどのように機能しますか?)。min
部分文字列を確認するときは、カットオフの長さとしても使用できます。
部分文字列の行をスキャンするときは、可能な新しい開始位置をすべて記録する必要があります。これはk
、これらのオフセットを格納する配列を使用して行われます。部分文字列を検索し、その文字列が配列のハッシュとして存在することを確認してくださいseen
。表示された文字列が見つかった場合:
- 部分文字列が行の末尾にある場合は、
next
入力行に移動します。その行は印刷されないか、表示配列に追加されません。 - それ以外の場合は、次の開始位置を追加し、
k
より多くの部分文字列を検索し続けます。 - 新しい開始位置を探している間も試してください(
while (length(k))
)。 - 上記のループが次の行に進まない場合は、その行が配列ハッシュに
seen
追加されます(またはまだ表示されていない場合は印刷されます)。
答え4
awk '{for (i in a) if (index($0,i)) next; print; a[$0]}' file