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

(\..*) すべき.共通セクションから末尾を削除します。


解決策は 2 つの部分から構成されます。

  • sed2 行にまたがって作業します。これは を使用して行われますがN、文字が入力に含まれないことが保証されている場合は回避できます。たとえば、指定された要素にはスペースが存在しないため、代わりに次を使用できます。

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

    基本的に、出力内の 2 つの要素を区切る文字または文字列は、%s書式設定文字列の後printf\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: 新しいものを追加lの各呼び出しに を付加しprint、 で指定されたスクリプトを実行します-e
  • @A=split(//,$ARGV[0]): は、$ARGV[0]コマンド ラインで指定された最初の引数です。これにより、引数が分割され、各文字が配列の要素になります@A
  • @B=split(//,$ARGV[1]);: 2 番目の引数と配列以外は上記と同じです@B
  • for $i (0..$#A): for ループ。これは を 0 に設定し、配列( )$iの要素数の値になるまで 1 ずつ増加します。これは、、 、... 、となるため、配列内のすべての要素を反復処理する簡単な方法です。@A$#A$A[$i]$A[0]$A[1]$A[$#A]
  • $A[$i] eq $B[$i] ? push @S,$A[$i] : last: これは C スタイルの省略記法です。一般的な形式は で、 はfoo ? bar : bazfooが true の場合は を実行しbar、そうでない場合は を実行する」という意味です。ここでは、配列 の番目 (この場合は 番目) の要素が配列 の対応​​する要素と同じかどうかをbazテストしています。同じであれば、それを 3 番目の配列 に追加します。同じでなければ、 でループを終了します。n$i@A@B@Slast
  • print @S: 配列の@S共有要素を出力します。

2 つのソリューションは非常に似ていますが、唯一の違いは、 は@A=split(/\./,$ARGV[0])で を分割し.、結果の配列から を削除し、の間にが含まれるprint join ".", @Sのすべての要素を印刷することです。@S.

答え3

質問の下のコメントで述べたように、私はかなり簡単なawk解決策を見つけました。2 つの数字を連結して 1 つの長い文字列を作成し、すべてのドットをスペースに置き換え (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 には、最初の文字 (x) から末尾 (Y) まで文字列を切り取る、つまり「切り取る」ことができる非常に便利な関数がありますsubstr(string,X,Y)。このことを前提として、2 つの数値を 1 つの文字列の 2 つのフィールドとして取り、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}'

substr関数の使用を提案してくれたterdonに感謝します。以前はその存在すら知りませんでした。

答え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リストの 2 番目の要素の部分が含まれますa

  • first次に、とを反復処理してsecond、各要素が同じインデックスの対応する要素と等しいかどうかをチェックします。同じ場合は、そのうちの 1 つが別のリストに保存されます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

関連情報