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

5

Supondo que eu tenha uma matriz chamada a . Há duas entradas em uma matriz a[1] e a[2] .Para cada elemento contém um valor numeral. Ambos os valores têm números iniciais semelhantes, mas possuem finais diferentes. Eu devo copiar a parte similar e ignorar o resto.

Então, como um exemplo

$ echo ${a[1]}
.1.3.6.1.4.1.232.13600256

$ echo ${a[2]}
.1.3.6.1.4.1.232.13600276

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

OUTPUT

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

OUTPUT para este exemplo

similar part is .1.3.6.1.4.1.759
    
por SamFlynn 22.06.2015 / 12:07

5 respostas

7

De estouro de pilha :

  

No sed, supondo que as strings não contenham nenhum caractere de nova linha:

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

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

Portanto, você pode fazer:

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

O (\..*) deve eliminar um . da seção comum.

A solução envolve duas partes:

  • Obtendo sed para trabalhar em duas linhas. Isso é feito usando N e 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 como fornecidos, podemos usar:

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

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

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

    (.*)
    

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

por muru 22.06.2015 / 14:19
4

Aqui está um jeito Perl. A ideia é dividir as duas strings de entrada em matrizes separadas e iterar sobre as matrizes, salvando quaisquer entradas que sejam idênticas em ambas:

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 . à direita. Sua saída não (apesar de ser a mesma em ambas as variáveis), então se você quiser removê-la, use isso:

$ 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 : inclua um novo l ine em cada chamada de print e execute o script fornecido por -e .
  • @A=split(//,$ARGV[0]) : $ARGV[0] é o primeiro argumento dado na linha de comando. Isso irá dividi-lo, tornando cada caractere um elemento na matriz @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 define $i para 0 e incrementa um por um até que tenha o valor do número de elementos na matriz @A ( $#A ). Essa é uma maneira simples de iterar todos os elementos em 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 de estilo C. O formato geral é foo ? bar : baz e significa "se foo for true, do bar , senão baz . Aqui, estamos testando se o n th (ou $i th, neste caso) elemento da matriz @A é o mesmo que o da matriz @B Se for, nós adicionamos à terceira matriz, @S Se não for, sairemos do loop com last .
  • print @S : imprima a matriz @S , os elementos compartilhados.

As duas soluções são muito semelhantes, a única diferença é que @A=split(/\./,$ARGV[0]) se dividirá em . , removendo-as da matriz resultante e print join ".", @S imprimirá todos os elementos de @S com . entre eles .

    
por terdon 22.06.2015 / 19:08
3

Como mencionei nos comentários abaixo da pergunta, encontrei uma solução awk um tanto simples: concatenar os dois números para criar uma cadeia longa, substituir todos os pontos pelo espaço (para permitir o uso do espaço como campo padrão separador no awk), e passar pela string comparando campo com arquivo + meio.

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, trabalhei 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, concatene-as juntas e separe com newline no printf, então adicione printf no final do comando awk da seguinte forma:

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 observou adequadamente que o OP quer que apenas 7 números sejam exibidos. Para esse propósito, você pode adicionar pipe ao comando cut -d'.' -f1-8 . Assim:

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

Veja o 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 da 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 não igual

O Awk tem uma função muito útil substr(string,X,Y) que permite cortar ou "cortar" uma string, do primeiro caractere (x) até o final (Y). Então, sabendo disso, vamos pegar os dois números como dois campos de uma string e executá-los através do loop while. Nós vamos continuar aumentando o comprimento da subcadeia (início ao final) até que eles não sejam mais iguais. Quando encontramos as substrings desiguais, fechamos 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,i)==substr(,1,i)){var=substr(,1,i);i++};} END{print var}'

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

    
por Sergiy Kolodyazhnyy 22.06.2015 / 17:08
3

Usando awk ( gawk ):

awk -F. '{printf "%s","similar part is "} {for(i=1;i<=NF;i++) {first[i-1]=$i} getline; for(i=1;i<=NF;i++) {second[i-1]=$i} for(i=0;i<length(first);i++) {if(first[i] == second[i]) {result=result first[i]"."}} printf "%s",substr($result,0,length(result)-1)"\n"}' <<< "$a"$'\n'"$b"

Ou mais legível:

awk -F. '{
    printf "%s","similar part is "
  }
  {
    for(i=1;i<=NF;i++) {
      first[i-1]=$i
    }

    getline;

    for(i=1;i<=NF;i++) {
      second[i-1]=$i
    }

    for(i=0;i<length(first);i++) {
      if(first[i] == second[i]) {
        result=result first[i]"."
      }
    }
    printf "%s",substr($result,0,length(result)-1)"\n"
}'

Exemplo

$ a=".1.3.6.1.4.1.759.2344.454545"
$ b=".1.3.6.1.4.1.759.3234.433226"

$ awk -F. '{printf "%s","similar part is "} {for(i=1;i<=NF;i++) {first[i-1]=$i} getline; for(i=1;i<=NF;i++) {second[i-1]=$i} for(i=0;i<length(first);i++) {if(first[i] == second[i]) {result=result first[i]"."}} printf "%s",substr($result,0,length(result)-1)"\n"}' <<< "$a"$'\n'"$b"
similar part is .1.3.6.1.4.1.759

UPDATE Nos comentários, o OP quer corresponder apenas até o primeiro não-jogo:

awk -F. '{printf "%s","similar part is "} {for(i=1;i<=NF;i++) {first[i-1]=$i} getline; for(i=1;i<=NF;i++) {second[i-1]=$i} for(i=0;i<length(first);i++) {if(first[i] == second[i]) {result=result first[i]"."} else {i=length(first)}} printf "%s",substr($result,0,length(result)-1)"\n"}' <<< "$a"$'\n'"$b"

Exemplo

$ a=".1.3.6.1.4.1.232.13600256.2"
$ b=".1.3.6.1.4.1.232.13600276.2"
$ awk -F. '{printf "%s","similar part is "} {for(i=1;i<=NF;i++) {first[i-1]=$i} getline; for(i=1;i<=NF;i++) {second[i-1]=$i} for(i=0;i<length(first);i++) {if(first[i] == second[i]) {result=result first[i]"."} else {i=length(first)}} printf "%s",substr($result,0,length(result)-1)"\n"}' <<< "$a"$'\n'"$b"
similar part is .1.3.6.1.4.1.232

Testado com o GNU Awk 4.1.1, API: 1.1 (GNU MPFR 3.1.2-p11, GNU MP 6.0.0)

    
por A.B. 22.06.2015 / 13:49
1

Você pode definir uma pequena função python 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

  • A variável
  • first conterá as partes do primeiro elemento da lista de entrada dividida em . ( a[0].split ). Da mesma forma, second conterá as partes do segundo elemento da lista a .

  • Em seguida, temos iterado sobre first e second e verificamos a igualdade de cada elemento com a mesma contraparte indexada, se forem iguais, então um deles é salvo em uma lista separada result . Sempre que encontramos a primeira diferença, quebramos o ciclo.

  • 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
    
por heemayl 22.06.2015 / 23:11

Tags