Compare 2 numerais e copie apenas a parte semelhante sed/grep/awk

Compare 2 numerais e copie apenas a parte semelhante sed/grep/awk

Supondo que eu tenha um array chamado a. Existem 2 entradas em uma matriz a[1]e a[2]. Portanto, cada elemento contém um valor numérico. Ambos os valores têm números iniciais semelhantes, mas têm finais diferentes. Devo copiar a parte semelhante e ignorar o resto.

Então, como umexemplo

$ echo ${a[1]}
.1.3.6.1.4.1.232.13600256

$ echo ${a[2]}
.1.3.6.1.4.1.232.13600276

Preciso de algum comando para comparar esses elementos e depois copiar apenas a parte semelhanteaté o primeiro campo não correspondente. ou seja, neste exemplo

SAÍDA

similar part is .1.3.6.1.4.1.232

Outro exemplo

$ echo ${a[1]}
.1.3.6.1.4.1.759.2344.454545

$ echo ${a[2]}
.1.3.6.1.4.1.759.3234.454545

SAÍDA para este exemplo

similar part is .1.3.6.1.4.1.759

Responder1

DeEstouro de pilha:

No sed, assumindo que as strings não contêm caracteres de nova linha:

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

Isso pressupõe que as próprias strings não contenham novas linhas.

Portanto você pode fazer:

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

O(\..*) deveeliminar um rastro .da seção comum.


A solução envolve duas partes:

  • Começando seda trabalhar em duas linhas. Isso é feito usando Ne pode ser evitado se for garantido que um caractere não esteja na entrada. Por exemplo, como os espaços não estão presentes nos elementos fornecidos, podemos usar:

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

    Essencialmente, o caractere ou string que separa os dois elementos na saída deve ser usado depois %sda printfstring de formatação e antes \1da expressão regular.

  • Encontrando uma string repetida usando regex. O truque para isso é bem conhecido e é sempre uma variação de:

    (.*)\1
    

    .*corresponde a qualquer conjunto de caracteres e ()os agrupa para referência posterior, por \1. Assim (.*)\1é qualquer sequência de caracteres seguida por si mesma.

Responder2

Aqui está uma maneira Perl. A ideia é dividir ambas as strings de entrada em arrays separados e iterar sobre os arrays, salvando quaisquer entradas que sejam idênticas em ambos:

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.

Isso, no entanto, inclui o final .. Sua saída não (apesar de ser a mesma em ambas as variáveis), então se você quiser removê-la, use isto:

$ 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

Explicação

  • -le: adicione um novoeuine para cada chamada de printe execute o script fornecido por -e.
  • @A=split(//,$ARGV[0]): $ARGV[0]é o primeiro argumento fornecido na linha de comando. Isso irá dividi-lo, tornando cada caractere um elemento em array @A.
  • @B=split(//,$ARGV[1]);: o mesmo que acima, mas para o segundo argumento e array @B.
  • for $i (0..$#A): um loop for. Isso é definido $icomo 0 e incrementado em um até obter o valor do número de elementos no array @A( $#A). Esta é uma maneira simples de iterar todos os elementos de uma matriz, pois $A[$i]será $A[0], $A[1], ... , $A[$#A].
  • $A[$i] eq $B[$i] ? push @S,$A[$i] : last: esta é uma notação abreviada no estilo C. O formato geral é foo ? bar : baze significa "se foofor verdadeiro, do bar, else do baz. Aqui, estamos testando se o n-ésimo (ou $io -ésimo, neste caso) elemento de array @Aé o mesmo que o correspondente de array @B. Se for, nós adicione-o ao terceiro array, @Sse não estiver, saímos do loop com last.
  • print @S: imprima o array @S, os elementos compartilhados.

As duas soluções são muito semelhantes, a única diferença é que @A=split(/\./,$ARGV[0])irá dividir on ., removendo-os do array resultante e print join ".", @Sirá imprimir todos os elementos de @Scom a .entre eles.

Responder3

Como mencionei nos comentários abaixo da pergunta, encontrei uma awksolução um tanto simples: concatenar os dois números para criar uma string longa, substituir todos os pontos por espaço (para permitir o uso de espaço como separador de campo padrão no awk) e percorra a string comparando o campo com arquivo + metade.

Comando básico

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

Eu testei isso com gawk e mawk, funcionou em ambos.

Aqui está a saída com o primeiro exemplo ( .1.3.6.1.4.1.232.13600256 e .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

Comparações múltiplas

Se você quiser comparar várias strings ao mesmo tempo, concatine-as e separe-as com nova linha em printf e adicione printf no final do comando awk assim:

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"}'

Saída:

$ 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]

Limitando a saída

Agora, o comentário de kos notou apropriadamente que o OP deseja que apenas 7 números sejam exibidos. Para esse propósito, você pode adicionar pipe ao cut -d'.' -f1-8comando. Igual a:

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

Aqui está um exemplo de saída do meu terminal:

$ 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

Simplificando ainda mais

Novamente, tudo pode ser colocado em um script awk

#!/usr/bin/awk -f

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

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

Execução de amostra:

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

Comparação até o primeiro número diferente

O Awk possui uma função muito útil substr(string,X,Y)que permite cortar ou "cortar" uma string, do primeiro caractere (x) ao final (Y). Sabendo disso, vamos considerar os dois números como dois campos de uma string e executá-los no loop while. Continuaremos aumentando o comprimento da substring (do início ao fim) até que elas não sejam mais iguais. Assim que encontrarmos as substrings desiguais, saímos e imprimimos a última substring igual conhecida.

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}'

Agradecimentos especiais a terdon por sugerir o uso da função substr, que eu nem sabia que existia anteriormente

Responder4

Você pode definir uma pequena pythonfunção que pode fazer o trabalho:

#!/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)
  • Precisamos fornecer uma lista contendo as strings que queremos verificar como entrada para a função

  • firstvariável conterá as partes do primeiro elemento da lista de entrada dividida em .( a[0].split). Da mesma forma secondconterá as partes do segundo elemento da lista a.

  • Em seguida, iteramos firste secondverificamos a igualdade de cada elemento com sua mesma contraparte indexada; se eles forem iguais, um deles será salvo em uma lista separada result. Sempre que encontramos a primeira diferença, saímos do loop.

  • Finalmente imprimimos nosso resultado desejado juntando os campos com .s( '.'.join(result))

Teste :

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

informação relacionada