比较两个数字并仅复制相似的部分 sed/grep/awk

比较两个数字并仅复制相似的部分 sed/grep/awk

假设我有一个名为的数组a。数组中有 2 个条目a[1],并且a[2]。因此每个元素都包含一个数值。这两个值的起始数字相似,但结尾不同。我要复制相似的部分并忽略其余部分。

因此,作为例子

$ echo ${a[1]}
.1.3.6.1.4.1.232.13600256

$ echo ${a[2]}
.1.3.6.1.4.1.232.13600276

我需要一些命令来比较这些元素,然后仅复制相似的部分直到第一个不匹配的字段. 例如,在这个例子中

输出

similar part is .1.3.6.1.4.1.232

另一个例子

$ echo ${a[1]}
.1.3.6.1.4.1.759.2344.454545

$ echo ${a[2]}
.1.3.6.1.4.1.759.3234.454545

本示例的输出

similar part is .1.3.6.1.4.1.759

答案1

堆栈溢出

在 sed 中,假设字符串不包含任何换行符:

string1="test toast"
string2="test test"
printf "%s\n%s\n" "$string1" "$string2" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/'

这假设字符串本身不包含换行符。

因此你可以这样做:

printf "%s\n" "${a[1]}" "${a[2]}" | sed -r 'N;s/^(.*)(\..*)?\n\1.*$/\1/'

(\..*) 应该.消除公共部分的尾随。


解决方案包括两部分:

  • 开始sed跨两行工作。这是使用 完成的N,如果某个字符肯定不在输入中,则可以避免这种情况。例如,由于元素中不存在空格,因此我们可以改用:

    printf "%s " "${a[1]}" "${a[2]}" | sed -r 's/^(.*)(\..*)? \1.*$/\1/'
    

    本质上,输出中分隔两个元素的字符或字符串应该%sprintf格式化字符串之后使用,\1在正则表达式之前使用。

  • 使用正则表达式查找重复字符串。此技巧众所周知,并且始终是以下变体:

    (.*)\1
    

    .*匹配任意字符集,并按()对其进行分组以供日后引用\1。因此(.*)\1是任意字符序列后跟其自身。

答案2

这是 Perl 的一种方式。其思路是将两个输入字符串拆分为单独的数组,然后对数组进行迭代,保存两个数组中相同的任何条目:

perl -le '@A=split(//,$ARGV[0]);@B=split(//,$ARGV[1]); 
          for $i (0..$#A){$A[$i] eq $B[$i] ? push @S,$A[$i] : last} 
          print @S' "${a[0]}" "${a[1]}"
.1.3.6.1.4.1.759.

但是,这包括尾随的.。您的输出不包括(尽管两个变量中的输出相同),因此如果您想删除它,请改用以下命令:

$ perl -le '@A=split(/\./,$ARGV[0]);@B=split(/\./,$ARGV[1]); 
            for $i (0..$#A){$A[$i] eq $B[$i] ? push @S,$A[$i] : last} 
            print join ".",@S' "${a[0]}" "${a[1]}"
.1.3.6.1.4.1.759

解释

  • -le:添加新的每次调用print并运行 给出的脚本-e
  • @A=split(//,$ARGV[0]):$ARGV[0]是命令行中给出的第一个参数。这将拆分它,使每个字符成为数组中的一个元素@A
  • @B=split(//,$ARGV[1]);:与上面相同,但针对的是第二个参数和数组@B
  • for $i (0..$#A):for 循环。这将设置$i为 0,并将其加一,直到其值为数组中元素的数量@A( $#A)。这是一种迭代数组中所有元素的简单方法,因为$A[$i]将是$A[0]$A[1]、 ... 、$A[$#A]
  • $A[$i] eq $B[$i] ? push @S,$A[$i] : last:这是 C 风格的简写符号。一般格式为,foo ? bar : baz表示“如果foo为真,则执行bar,否则执行baz”。在这里,我们测试数组的n第 th 个(或$i本例中的第 th 个)元素@A是否与数组中的对应元素相同@B。如果是,我们将它添加到第三个数组中@S。如果不是,我们用 退出循环last
  • print @S:打印数组@S,共享元素。

这两个解决方案非常相似,唯一的区别是@A=split(/\./,$ARGV[0])将在 上进行拆分.,将它们从结果数组中删除,并将打印出它们之间print join ".", @S的 所有元素。@S.

答案3

正如我在问题下面的评论中提到的,我找到了一个相当简单的awk解决方案:连接两个数字以创建一个长字符串,用空格替换所有点(以允许在 awk 中使用空格作为默认字段分隔符),并通过文件 + 一半来比较字符串字段。

基本命令

printf ${a[1]}${a[2]} | awk '{gsub("\\."," "); half=NF/2}; { for ( x=1; x<=half; x++ ) { if ( $x==$(x + half) ) printf "."$x };}'

我已经使用 gawk 和 mawk 对此进行了测试,两者都有效。

下面是第一个示例( .1.3.6.1.4.1.232.13600256 和 .1.3.6.1.4.1.232.13600276 )的输出:

$ printf ${a[1]}${a[2]} | awk '{gsub("\\."," "); half=NF/2}; { for ( x=1; x<=half; x++ ) { if ( $x==$(x + half) ) printf "."$x };}'
.1.3.6.1.4.1.232

多重比较

如果要同时比较多个字符串,请将它们连接在一起并在 printf 中用换行符分隔,然后在 awk 命令末尾添加 printf,如下所示:

printf "${a[1]}${a[2]}\n${a[3]}${a[4]}" | awk '{gsub("\\."," "); half=NF/2}; { for ( x=1; x<=half; x++ ) { if ( $x==$(x + half) ) printf "."$x }; printf "\n"}'

输出:

$ printf "${a[1]}${a[2]}\n${a[3]}${a[4]}" | awk '{gsub("\\."," "); half=NF/2}; { for ( x=1; x<=half; x++ ) { if ( $x==$(x + half) ) printf "."$x }; printf "\n"}'
.1.3.6.1.4.1.232 # same for a[1] and a[2]
.1.3.6.1.4.1.759 # same for a[3] and a[4]

限制输出

现在,kos 的评论恰当地指出 OP 只想显示 7 个数字。为此,您可以将管道添加到命令中cut -d'.' -f1-8。如下所示:

printf "${a[5]}${a[6]}" | mawk '{gsub("\\."," "); half=NF/2}; { for ( x=1; x<=half; x++ ) { if ( $x==$(x + half) ) printf "."$x }; printf "\n"}' | cut -d'.' -f1-8

以下是我的终端的示例输出:

$ a[5]=.1.3.6.1.4.1.232.13600256.885


$ a[6]=.1.3.6.1.4.1.232.13600256.885


$ printf "${a[5]}${a[6]}" | mawk '{gsub("\\."," "); half=NF/2}; { for ( x=1; x<=half; x++ ) { if ( $x==$(x + half) ) printf "."$x }; printf "\n"}' | cut -d'.' -f1-8
.1.3.6.1.4.1.232.13600256.885


 half) ) printf "."$x }; printf "\n"}' | cut -d'.' -f1-8                      <
.1.3.6.1.4.1.232

进一步简化

再次强调,所有内容都可以放入 awk 脚本中

#!/usr/bin/awk -f

{
 gsub("\\."," "); 
 half=NF/2
}; 

{ 
 for ( x=1; x<=half; x++ ) { 
    if ( $x==$(x + half) ) printf "."$x 
  }; 
  printf "\n"
}

示例运行:

$ printf "${a[5]}${a[6]}" | num-comp.awk | cut -d'.' -f1-8                     
.1.3.6.1.4.1.232

比较至第一个不等的数字

Awk 有一个非常有用的函数substr(string,X,Y),它允许剪切或“裁剪”字符串,从第一个字符 (x) 到结尾 (Y)。知道这一点后,让我们将两个数字作为一个字符串的两个字段,并通过 while 循环运行它们。我们将继续增加子字符串的长度(从开始到结束),直到它们不再相等。一旦遇到不相等的子字符串,我们就退出,并打印最后一个已知的相等子字符串。

echo ".1.3.6.1.4.1.232.13600256\t.1.3.6.1.4.1.232.13600276" | awk 'BEGIN{i=1}{ while(substr($1,1,i)==substr($2,1,i)){var=substr($1,1,i);i++};} END{print var}'

特别感谢 terdon 建议使用 substr 函数,我之前甚至不知道这个函数的存在

答案4

您可以定义一个python可以完成此项工作的小函数:

#!/usr/bin/env python2
import itertools
def common_portion(a):
    first = a[0].split('.')
    second = a[1].split('.')
    result = []
    for (i, j) in itertools.izip(first, second):
        if i == j:
            result.append(i)
        else:
            break
    return 'Similar part is ' + '.'.join(result)
  • 我们需要提供一个列表,其中包含我们想要检查的字符串作为函数的输入

  • first.变量将包含按( )拆分的输入列表的第一个元素的部分a[0].split。同样second将包含列表的第二个元素的部分a

  • 然后我们迭代firstsecond检查每个元素与其相同索引对应元素的相等性,如果它们相同,则其中一个元素将保存在单独的列表中result。每当我们遇到第一个差异时,我们就会跳出循环。

  • .最后我们用s ( '.'.join(result))连接字段并打印出我们想要的结果

测试 :

print common_portion(['.1.3.6.1.4.1.232.13600256', '.1.3.6.1.4.1.232.13600276'])

Similar part is .1.3.6.1.4.1.232


print common_portion(['.1.3.6.1.4.1.759.2344.454545', '.1.3.6.1.4.1.759.3234.454545'])

Similar part is .1.3.6.1.4.1.759

相关内容