Сравнить 2 цифры и скопировать только похожую часть sed/grep/awk

Сравнить 2 цифры и скопировать только похожую часть 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/'

The(\..*) долженисключить завершающую часть .из общей секции.


Решение состоит из двух частей:

  • Работаем sedнад двумя строками. Это делается с помощью N, и этого можно избежать, если гарантированно нет символа во входных данных. Например, поскольку пробелы отсутствуют в элементах, как указано, мы можем вместо этого использовать:

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

    По сути, символ или строка, разделяющая два элемента в выходных данных, должны использоваться после строки %sформатирования printfи перед \1регулярным выражением.

  • Поиск повторяющейся строки с помощью regex. Этот трюк хорошо известен и всегда является вариацией:

    (.*)\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: добавить новыйлine для каждого вызова printи запускает скрипт, заданный -e.
  • @A=split(//,$ARGV[0]): $ARGV[0]— первый аргумент, заданный в командной строке. Это разделит ее, сделав каждый символ элементом в массиве @A.
  • @B=split(//,$ARGV[1]);: то же, что и выше, но для 2-го аргумента и массива @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. Здесь мы проверяем, совпадает ли nth (или $ith, в данном случае) элемент массива @Aс соответствующим элементом из массива @B. Если это так, мы добавляем его в третий массив . @SЕсли нет, мы выходим из цикла с помощью last.
  • print @S: распечатать массив @S, общие элементы.

Оба решения очень похожи, единственное отличие в том, что @A=split(/\./,$ARGV[0])будет выполнено разделение по ., удаление их из результирующего массива и print join ".", @Sвывод всех элементов с @Sпромежутком .между ними.

решение3

Как я уже упоминал в комментариях под вопросом, я нашел довольно простое awkрешение: объединить два числа, чтобы создать одну длинную строку, заменить все точки пробелами (чтобы разрешить использовать пробел в качестве разделителя полей по умолчанию в awk) и выполнить сравнение строк field с file+half.

Основная команда

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, затем добавьте printf в конец команды awk, например так:

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команде pipe. Вот так:

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.

  • Затем мы итерируем firstи secondпроверяем равенство каждого элемента с его тем же индексированным аналогом, если они одинаковы, то один из них сохраняется в отдельном списке 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

Связанный контент