我有一个域列表,示例是:
account.google.com
drive.google.com
google.com
bgoogle.com
yahoo.co.uk
stats.wikipedia.org
media.wikipedia.org
files.media.wikipedia.org
bible.com
我想删除现有域的所有子域。例如,对于上面的列表,由于google.com
和media.wikipedia.org
具有子域,因此应删除这些子域。
所以上面的列表应该导致:
google.com
bgoogle.com
yahoo.co.uk
stats.wikipedia.org
media.wikipedia.org
bible.com
我尝试用Python编写一些代码,但是需要很长时间才能完成。我怎样才能以最快的方式执行这个任务?
答案1
听起来你的问题可以重新表述为“只保留最长的非.
后跟 a ,直到行尾为止.
不保留其他;使这些唯一”。.
如果是这样,您可以执行以下操作:
$ awk -F"." -v OFS="." '{print $(NF-1),$(NF)}' file | sort -u
bgoogle.com
bible.com
google.com
wikipedia.org
或者,完成整个事情awk
:
$ awk -F"." '{ k[$(NF-1)"."$(NF)]++}END{for (i in k){print i}}' file
bgoogle.com
bible.com
wikipedia.org
google.com
或者 GNU grep
:
$ grep -oP '[^.]+\.[^.]+$' file | sort -u
bgoogle.com
bible.com
google.com
wikipedia.org
或者 perl 和 GNU 排序
$ perl -pe 's/.*?([^.]+\.[^.]+$)/$1/' file | sort -u
bgoogle.com
bible.com
google.com
wikipedia.org
或者只是 perl
$ perl -ne '$k{$1}++ for s/.*?([^.]+\.[^.]+$)/$1/; END{ print keys(%k) }' file
bible.com
bgoogle.com
wikipedia.org
google.com
或者只是 perl
$ perl -ne 's/.*?([^.]+\.[^.]+)$/$1/; next if ++$k{$1}>1; print' file
google.com
bgoogle.com
wikipedia.org
bible.com
或者 sed 并排序
$ sed -E 's/.*\.([^.]+\.[^.]+)$/\1/' file | sort -u
bgoogle.com
bible.com
google.com
wikipedia.org
答案2
$ sed -e 's/\./\\./g' -e 's,.*,/\\.&$/d,' file | sed -f /dev/stdin file
google.com
bgoogle.com
yahoo.co.uk
stats.wikipedia.org
media.wikipedia.org
bible.com
这首先用于从输入文件sed
创建编辑脚本。sed
该sed
命令首先转义当前行上的每个点,然后将修改后的行转换为d
具有特定正则表达式触发器的命令。
对于问题中提供的文件,该编辑脚本将如下所示:
/\.account\.google\.com$/d
/\.drive\.google\.com$/d
/\.google\.com$/d
/\.bgoogle\.com$/d
/\.yahoo\.co\.uk$/d
/\.stats\.wikipedia\.org$/d
/\.media\.wikipedia\.org$/d
/\.files\.media\.wikipedia\.org$/d
/\.bible\.com$/d
此脚本将删除包含以 结尾的主机名的任何输入行,.
后跟原始数据中的任何主机名。例如,这将删除数据中的aaa.bbb.ccc
if ,因为它与正则表达式 匹配。bbb.ccc
aaa.bbb.ccc
\.bbb\.ccc$
然后,通过第二次调用将该脚本应用于原始数据sed
,通过其标准输入流读取编辑脚本。
通过对初始命令进行一些小的修改,我们可以用以下sed
命令进行消除:grep
sed
$ sed -e 's/\./\\./g' -e 's/.*/\\.&$/' file | grep -v -f /dev/stdin file
google.com
bgoogle.com
yahoo.co.uk
stats.wikipedia.org
media.wikipedia.org
bible.com
这将创建一组裸露的正则表达式以供grep
读取-f
:
\.account\.google\.com$
\.drive\.google\.com$
\.google\.com$
\.bgoogle\.com$
\.yahoo\.co\.uk$
\.stats\.wikipedia\.org$
\.media\.wikipedia\.org$
\.files\.media\.wikipedia\.org$
\.bible\.com$
该grep
实用程序用于-v
从输入中删除任何匹配的行。
答案3
使用乐(以前称为 Perl_6)
...翻译@terdon 优秀的 Perl 解决方案:
~$ raku -ne 'BEGIN my %k; %k{$0}++ for s/ .*? (<-[.]>+ \. <-[.]>+ $)/$0/; END .key.put for %k;' file
#OR:
~$ raku -ne 'BEGIN my %k; s/ .*? (<-[.]>+ \. <-[.]>+ $) /$0/; next if ++%k{$0} > 1; .put;' file
以下是用 Raku(Perl 编程语言家族的成员)编写的解决方案。 Raku 具有不变的“sigils”以及许多不错的默认设置,例如扩展正则表达式的能力(/x
Perl 中的 -option)。自定义字符类适用<+[…]>
于正字符类和<-[…]>
负字符类。 Raku 中的捕获从 开始,与$0
Perl 不同,Perl 从 开始$1
。
输入示例:
account.google.com
drive.google.com
google.com
bgoogle.com
stats.wikipedia.org
media.wikipedia.org
files.media.wikipedia.org
bible.com
示例输出:
google.com
bgoogle.com
wikipedia.org
bible.com
答案4
这可能是您想要使用任何 awk 执行的操作:
$ cat tst.sh
#!/usr/bin/env bash
awk '
{ doms[$0] }
END {
for ( dom in doms ) {
n = gsub(/[.]/,"&",dom)
parent = dom
foundParent = 0
for ( i=1; i<n; i++ ) {
sub(/[^.]+\./,"",parent)
if ( parent in doms ) {
foundParent = 1
break
}
}
if ( !foundParent ) {
print dom
}
}
}
' "${@:--}"
$ ./tst.sh file
media.wikipedia.org
bgoogle.com
bible.com
google.com
stats.wikipedia.org
或者,如果您不想将整个文件存储在 awk 的内存中:
$ cat tst.sh
#!/usr/bin/env bash
awk '
BEGIN { FS=OFS="." }
{
for ( i=NF; i>=1; i-- ) {
printf "%s%s", $i, (i>1 ? OFS : ORS)
}
}
' "${@:--}" |
sort -u |
awk '
BEGIN { FS=OFS="." }
index($0,base".") != 1 {
base = $0
for ( i=NF; i>=1; i-- ) {
printf "%s%s", $i, (i>1 ? OFS : ORS)
}
}
'
$ ./tst.sh file
bgoogle.com
bible.com
google.com
media.wikipedia.org
stats.wikipedia.org
上面的第一个脚本将所有域读取到数组中的内存中doms[]
,然后循环遍历它们,将“.”之间的前导字符串剥离到只剩下 2 个的位置(因此files.media.wikipedia.org
变为media.wikipedia.org
和 then fwikipedia.org
),同时检查该父字符串是否也存在存在于doms[]
数组中,如果存在,那么我们知道当前域是该父域的子域,该父域也存在于输入中,因此我们不打印它。
第二个脚本使用 awk 反向打印每个域名,因此files.media.wikipedia.org
打印为org.wikipedia.media.files
,然后将整个列表通过管道传输到该列表,该列表sort -u
将丢弃重复项(可选),结果是较短的(即父)域名将立即打印在前面他们的子域。然后接下来的awk
只是检查当前反向域名是否是前一个域名开头的子字符串,而该域名本身不是子域 - 如果是,则该域是子域,所以跳过它,如果不是,则打印它(重新反向它恢复到原来的顺序)并记住它作为下一个要比较的域的新基本父级。如果不清楚,请单独运行每个命令以查看它们的输出内容。