5

Supposing I have a array called a. There are 2 entries in a array a[1] and a[2].So each element contains a numeral value. Both these values have a similar starting numbers however they have different endings. I am to copy the similar part and ignore the rest.

So as an example

$ echo ${a[1]}
.1.3.6.1.4.1.232.13600256

$ echo ${a[2]}
.1.3.6.1.4.1.232.13600276

I need some command to compare these elements and then copy only the similar part until the first non matching field. ie., in this example

OUTPUT

similar part is .1.3.6.1.4.1.232

Another example

$ 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 for this example

similar part is .1.3.6.1.4.1.759
10
  • a[1] and a[2] are arrays or entries in the array?
    – Maythux
    Jun 22, 2015 at 10:18
  • a is my array name and 1,2 are index positions in them where these numerals are stored
    – SamFlynn
    Jun 22, 2015 at 10:19
  • 1
    Ok this is better
    – Maythux
    Jun 22, 2015 at 10:23
  • 1
    @SamFlynn I have an idea with awk but need to test it. I'm on mobile right now. Will be on my computer in two hours. Basic idea: printf ${array [1]} ${array [2]} | awk '{ for ( x=1; x <=NF/2; x+=NF/2) { if ( $x == $(x + NF/2) ) print $x;}}; I think iy either needs -F'.' flag or gsub ('.',' ') in the code to remove all dots and use space as separator Jun 22, 2015 at 11:26
  • 1
    I assume that in case of e.g. ${a[1]}=".1.3.6.1.4.1.232.13600256.885" and b="${.1.3.6.1.4.1.232.13600256.885}" you want .1.3.6.1.4.1.232 and not .1.3.6.1.4.1.232.885, correct?
    – kos
    Jun 22, 2015 at 12:15

5 Answers 5

7

From Stack Overflow:

In sed, assuming the strings don't contain any newline characters:

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

This assumes that the strings themselves don't contain newlines.

Therefore you can do:

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

The (\..*) should eliminate a trailing . from the common section.


The solution involves two parts:

  • Getting sed to work across two lines. This is done using N, and can be avoided if a character is guaranteed to be not in the input. For example, because spaces are not present in the elements as given, we can instead use:

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

    Essentially, the character or string separating the two elements in the output should be used after %s in the printf formatting string, and before \1 in the regular expression.

  • Finding a repeating string using regex. The trick for this is well-known, and is always a variation of:

    (.*)\1
    

    .* matches any set of characters, and () groups them for later reference, by \1. Thus (.*)\1 is any sequence of characters followed by itself.

4
  • o_O to short :P +1
    – A.B.
    Jun 22, 2015 at 12:20
  • There is a trailing .: .1.3.6.1.4.1.759.
    – A.B.
    Jun 22, 2015 at 12:22
  • @A.B. Try update.
    – muru
    Jun 22, 2015 at 12:26
  • now it's perfect
    – A.B.
    Jun 22, 2015 at 12:27
4

Here's a Perl way. The idea is to split both input strings into separate arrays and iterate over the arrays, saving any entries that are identical in both:

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.

That, however, includes the trailing .. Your output doesn't (despite its being the same in both variables), so if you want to remove it, use this instead:

$ 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

Explanation

  • -le : add a newline to each call of print and run the script given by -e.
  • @A=split(//,$ARGV[0]) : $ARGV[0] is the first argument given on the command line. This will split it, making each character an element in array @A.
  • @B=split(//,$ARGV[1]); : same as above but for the 2nd argument and array @B.
  • for $i (0..$#A) : a for loop. This sets $i to 0 and increments it by one until it has the value of the number of elements in array @A ($#A). This is a simple way of iterating over all elements in an array since $A[$i] will be $A[0], $A[1], ... , $A[$#A].
  • $A[$i] eq $B[$i] ? push @S,$A[$i] : last : this is a C-style shorthand notation. The general format is foo ? bar : baz and means "if foo is true, do bar, else do baz. Here, we are testing whether the nth (or $ith, in this case) element of array @A is the same as the corresponding one from array @B. If it is, we add it to the third array, @S. If it isn't, we exit the loop with last.
  • print @S : print the array @S, the shared elements.

The two solutions are very similar, the only difference is that @A=split(/\./,$ARGV[0]) will split on ., removing them from the resulting array and print join ".", @S will print all elements of @S with a . between them.

1
  • Master of perl =)
    – A.B.
    Jun 22, 2015 at 19:28
3

As I've mentioned in the comments bellow the question, I've found a somewhat simple awk solution: concatenate the two numberals to create one long string, replace all dots with space ( to allow using space as default field separator in awk), and go through the string comparing field with file+half.

Basic command

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

I've tested this with gawk and mawk, worked in both.

Here's output with first example ( .1.3.6.1.4.1.232.13600256 and .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

Multiple comparisons

If you want to compare multiple strings at the same time, concatinate them together and separate with newline in printf, then add printf at the end of awk command like so:

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

Output:

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

Limiting the output

Now, kos's comment appropriately noticed that OP wants only 7 numbers to be displayed. For that purpose you can add pipe to the cut -d'.' -f1-8 command. Like so:

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

Here's sample output from my 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

Simplifying even further

Again, everything can be put in an awk script

#!/usr/bin/awk -f

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

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

Sample run:

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

Comparison up to the first not-equal number

Awk has a very useful function substr(string,X,Y) which allows cutting or "cropping" a string, from first character (x) to the ending (Y). So knowing that, let's take the two numbers as two fields of one string, and run them through while loop. We're gonna keep increasing the substring length (start to ending) until they're no longer equal. Once we encounter the unequal substrings, we quit, and print the last known equal substring.

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

Special thanks to terdon for suggesting use of substr function, which I previously didn't know even existed

1
  • Great, I like half=NF/2 idea =)
    – A.B.
    Jun 22, 2015 at 15:20
3

Using 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"

Or more readable:

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

Example

$ 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 In the comments, OP wants to match only until the first non-match:

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"

Example

$ 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

Tested with GNU Awk 4.1.1, API: 1.1 (GNU MPFR 3.1.2-p11, GNU MP 6.0.0)

2
  • It gives me error awk: line 1: illegal reference to array first
    – SamFlynn
    Jun 22, 2015 at 11:54
  • 2
    I'm done, please check the current version ;)
    – A.B.
    Jun 22, 2015 at 12:24
1

You can define a little python function that can do the job:

#!/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)
  • We need to provide a list containing the strings we want to check as input to the function

  • first variable will contain the parts of the first element of the input list splitted on . (a[0].split). Similarly second will contain the parts of the second element of list a.

  • Then we have iterated over first and second and check the equality of each element with its same indexed counterpart, if they are same then one of them is saved in a separate list result. Whenever we have encountered the first difference we have broken out of the loop.

  • Finally we have printed our desired result with joining the fields with .s ('.'.join(result))

Test :

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .