bash中域名的最长通用后缀

时间:2019-04-08 09:56:13

标签: bash

原始问题:我有一个域的子域列表,例如a.domain.frb.domain.fr等。域本身可以在列表中。

我想从此域列表中找到domain.fr,该域正在查找最长的公共后缀,该后缀不以点.开头。

域列表是一个bash字符串,并且域之间用一个空格分隔。

我读了Longest common prefix of two strings in bash,但没有设法将其转换为后缀:

echo $domains | tr ' ' '\n' | sed -e 'N;s/^.*\(.*\)\n.*\1$/\1/'

...打印一堆空行,然后:

echo $domains | tr ' ' '\n' | sed -e 'N;s/^.*\.\(.*\)\n.*\.\1$/\1/'

...打印一堆fr


我不是在寻求极高的可移植性,无需额外安装即可在任何Linux发行版上运行的东西对我来说都是可以的。

我正在寻找一种可以找到子域作为通用“域”的解决方案,例如以下列表:

a.d.domain.fr b.d.domain.fr c.d.domain.fr

...公共域应为d.domain.fr,但是如果您有一个仅适用于顶级域的有效解决方案(例如,上面的列表将返回domain.fr),我就是也有兴趣。


样本字符串(每行一个样本):

a.domain.fr domain.fr b.a.domain.fr b.domain.fr u.domain.fr
domain.fr
a.domain.fr
a.domain.fr b.domain.fr domain.fr
a.d.domain.fr b.d.domain.fr c.d.domain.fr

5 个答案:

答案 0 :(得分:3)

You can use awk to compare each part of the domain one by one and keep track of the number of common parts:

# on the first line
NR == 1 {
  # split first domain into "parts" for comparison with rest
  n = split($1, parts, /\./)
  # initialise result
  c = n
}

# on every line
{ 
  for (i = 1; i <= NF; ++i) {
    # split current record into "s"
    m = split($i, s, /\./)

    # increment j as long as the last elements of "parts" match "s"
    for (j = 0; j < c && parts[n-j] == s[m-j]; ++j);

    # update count if lower
    if (j < c) c = j
  } 
}

# print the result, joining the parts with a "." and ending with a newline
END { for (i = 1; i <= c; ++i) printf "%s%s", parts[n-c+i], (i < c ? "." : ORS) }

Save the script and run it like awk -f script.awk file.

答案 1 :(得分:2)

将最长的结尾字符串与sed匹配有点困难,因为第一个.*将吞噬输入中的所有字符。但是我们可以只是个虚拟对象,而只需rev就可以输入字符串。我还添加了\.来与sed内部匹配,因此domain.frnot_in_domain.fr不会导致domain.fr,而会导致fr

printf "%s\n" a.domain.fr b.domain.fr | rev | sed -e 'N;s/^\(.*\)\..*\n\1\..*$/\1/' | rev

将输出:

domain.fr

由于此sed一次只能处理两个字符串,因此对于更多的表达式,我们必须将其“折叠”:

printf "%s\n" a.a.domain.fr b.a.domain.fr b.not_in_a.domain.fr | 
rev | 
{ 
    # the function
    f() { 
       printf "%s.\n" "$@" | 
       sed -e 'N;s/^\(.*\)\..*\n\1\..*$/\1/'; 
    }; 
    # load initial
    IFS= read -r res; 
    # for each line
    while IFS= read -r line; do
         # right fold it
         res=$(f "$res" "$line");
    done; 
    printf "%s\n" "$res"; 
} | rev

@edit通过将其包含在sed中来固定匹配的前导点

答案 2 :(得分:1)

这一行awk可以满足您的期望:

awk '{d=$1; for(i=2;i<=NF;++i) while(d && ! match($i,d"$")) sub(/[^.]*./,"",d); print d}'
  • 您知道列表中的第一个域是最大的解决方案。
  • 如果该解决方案与下一个字段不匹配,请删除开头的条目以解构该解决方案。
  • 继续这样做,直到找到匹配项或整个域都被删除为止。

上述解决方案仅会打印匹配的域部分。

如果要使其更坚固,则必须对其进行一些更正,因为: * match将匹配一个正则表达式,而.将匹配任何字符 *您必须确保ere.开头 在第一个示例中没有处理这些事情。

更正在这里:

awk '{d=$1; gsub(/[.]/,"\\.",d); for(i=2;i<=NF;++i) while(d && ! match($i,"(^|[.])"d"$")) { sub(/[^.]*([.]|$)/,"",d)}; gsub(/[\\][.]/,".",d);print d}'

答案 3 :(得分:1)

这是一个纯Bash程序,包含可能的解决方案:

#! /bin/bash -p

# A space-separated list of domains
domainlist=$1

longest=
longest_rx='\.([^ ]*) .*\.\1$'
for domain in $domainlist ; do
    if [[ -z $longest ]] ; then
        longest=$domain
    elif [[ ".$longest .$domain" =~ $longest_rx ]] ; then
        longest=${BASH_REMATCH[1]}
    else
        longest=
        break
    fi
done

printf "longest='%s'\n" "$longest"
  • 示例用法是:./progname 'a.d.domain.fr b.d.domain.fr c.d.domain.fr'(输出longest='d.domain.fr')。
  • 它不会尝试检查错误的输入(以点开头的域,包含glob元字符的域等)。
  • 这取决于支持反向引用(\1)的Bash正则表达式。对于Linux(在具有10年以上历史的系统上使用Bash 3进行测试),这应该是可以的,但对于某些其他系统(包括某些Unix系统)则不是。
  • 我尚未进行重大的性能测试,但问题中给出的示例输入没有明显的性能问题(程序在几毫秒内完成)。

答案 4 :(得分:0)

我喜欢编写awk程序来解决此问题。 该程序使用提供的字符串列表查找最长的后缀字符串和最短的后缀字符串。

最长的公共后缀可以更长,而最短的公共后缀是1个字符。

匹配算法找到与提供的字符串右侧匹配的地方。

script-1.awk:

{
    # read the fields into a unique array
    for(i = 1; i <= NF; i++){
        if ($i in uniquenessArr == 0) { #accept a field into arr only if not in the uniquenessArr
            uniquenessArr[$i] = 1;
            arr[++arrLen] = $i;
        }
    }
    # arrLen is count of fields to compute
    minLen = 9999999; # initial length of minimal matched string
    for(currStr in arr){ # for each string in arr
        len = length(arr[currStr]);
        # print currStr ") " arr[currStr] "   (" len ")";
        for(targetStr in arr) { # match each string against longer strings in arr
            if ( (len < length(arr[targetStr])) && match(arr[targetStr], arr[currStr]"$") ) {
                # currStr is matched into a longer string
                if (maxLen <= RLENGTH ) {
                    maxLen = RLENGTH;
                    maxMatch = arr[currStr];
                }
                if (minLen >= RLENGTH ) {
                    minLen = RLENGTH;
                    minMatch = arr[currStr];
                }
            }
        }
    }
    printf("maxMatch = %s \t minMatch = %s\n", maxMatch, minMatch);
}

输入测试文件:

a.domain.fr domain.fr b.a.domain.fr b.domain.fr u.domain.fr
domain.fr
a.domain.fr
a.domain.fr b.domain.fr domain.fr
a.d.domain.fr b.d.domain.fr c.d.domain.fr d.domain.fr c.b.d.domain.fr b.c.d.domain.fr

执行命令:

awk -f script-1.awk input

一些注意事项:

第一个for循环将所有字段读入一组(无重复) 逻辑是针对较长的字符串扫描每个字符串。 如果找到匹配项,则标记最长和最短的匹配字符串。