ファイルから同じテキスト項目を検索するシェルスクリプト

ファイルから同じテキスト項目を検索するシェルスクリプト

スクリプトを作成する必要があります。

  1. 複数のテキストファイルを含むディレクトリをインポートします。複数個であり、最大1000個まで可能です。
  2. すべてのファイルには、指定された行(常に同じ行)に識別子が含まれています。
  3. 一意でない識別子を持つファイル、つまりディレクトリ内の他のファイルに重複したファイルを識別します。
  4. 重複リストのエクスポートまたは保存

これは、一意である必要があるがユーザーエラーのため一意ではない可能性があるシステム生成ファイルの日常的な管理「クリーンアップ」に必要です。

答え1

上記のコメントに基づいて、私のテストデータが実際のデータと非常によく似ていることを確認して、次のことを確認できました。

grep -n '^ID.[^:-]*.[0-9][0-9]*$' |
sed -n 'h;s|\(.*\):6:\(ID.*\)|\2|p;g;s||\2:\1|p'
sort -u | 
sed 's|ID..*:||'

私のgrepフォルダには、一致するファイルが複数見つかり、一致する行番号を印刷するように要求IDするため、次に始まる行と残りの行が含まれています。-ngrep

[filename]:[matching line number]:[IDmatch]

sed前のバッファに行のコピーを保存するためにそれを渡し、文字列を確認し、h見つかっ:6:IDた場合はその行を削除しますID。次にp結果を印刷します。

次に、gバッファを返し(プロセスの最後の編集内容を上書きし)、grep一致するファイル名の位置に一致行を置き換えます。したがって、grep6行に一致する印刷されたすべての行をsed次のように置き換えます。

[IDmatch]
[IDmatch]:[filename]

このデータが渡されると、sortコレクション全体を整理しID、一意の結果のみを提供するように要求するため、-u重複IDmatch行を除くすべての行が削除され、次のIDmatch:filename行は保持されます。次sedのステートメントはそれをクリーンアップして次のようにレンダリングします。

ID00000000
ID00000000:file00
ID00000000:file10
...
ID00000000:file80
ID00000001
ID00000001:file01
ID00000002
ID00000002:file02
...

このように:

ID00000000
file00
file10
...
file80
ID00000001
file01
ID00000002
file02
...

しかし、その解決策〜するファイル名に\newline文字が含まれている場合は中断されますが、次はそうではありません。 2回接地する必要がないように、次をシェル関数に入れる方法を見つけました。すぐにここに貼り付けます。

for f in * ; do
    sed '5!d;s|^|: "${'$((i=i+1))'}" |;q' "$f"
done |
sort -t' ' -k3 |
uniq -D -f2 |
sh -cx "$(cat)" -- * 2>&1

これは可能でなければなりません。ステートメントのID行をに5置き換えます。sed私の考えでは - 私が間違っている場合はお知らせください - これはすべてのケースを処理します。

ディレクトリ内の各ファイルに対して数字を1ずつ増やし、文字列で始まる行を印刷します。

: "${[num]}" ...

...ここで[num]1だけ増加した実際の整数であり、...行の一意のIDです。

次に、その行を最初にパイプしてsort文字<space>を区切り文字として処理し、3番目のフィールドのデータのみを並べ替えます。|pipeline横にある続行は、入力を比較し、重複行のみを印刷しながら、入力の最初の2つのフィールドを分離してスキップしますuniq。次の部分は少し奇妙です。<space>-D

したがって、再度繰り返して、どのファイルが何であるかを調べるのではなく、前述の作業を実行し[num]ました。sh最終シェルプロセスが結果に合格すると、|pipelineこの数だけが受け取ります。ただし、すでに数値を増やしたときに繰り返されたのと同じグローバル変数に設定されている位置パラメータがあるため、その数値を評価するときに既に位置配列内のファイルに関連付けます。それがすべてです。

事実 – ほとんどそうしません。各位置パラメータの前には:NULLコマンドがあります。シェルプロセスが実行する唯一のことは、渡された変数を評価することです。 1行のコードも実行しません。しかし、デバッグモードに設定し、すべてのファイル名を印刷するようにリダイレクト-xしました。stderrstdout

これは、奇妙なファイル名が結果を台無しにすることを心配するよりもsort | uniqはるかに簡単なためです。そしてそれは素晴らしい作品です。

次の方法で生成されたデータセットを使用してこれをテストしました。

tr -dc '[:graph:]' </dev/urandom |
dd ibs=100 cbs=10 conv=unblock count=91 |
split -b110 --filter='
{   c=${FILE##%%*0} ; c=${c#file}
    sed "5cID000000${c:-00}"
} >$FILE' -ed - file ; rm *90*

rm上記の文字列を参照してください。ちょっと眠いので、なぜ102バイトだけが生成され、残りの110バイトは生成されないのかを確認したくなかったので、file8990に四捨五入してrm編集しました。上記のコマンドを実行すると、rmファイル名が現在のディレクトリのglobと一致し、file00その中のすべてのファイルが上書きされます。file89ただし、委任されたテストディレクトリ内で使用することは完全に安全です。

...何よりも...誰にも効果があります。

file[0-8][1-9]これにより、それぞれ1-4,6-10 10バイトのランダムなデータ行と呼ばれる90個のファイルが作成され、各ファイルの行5に一意のIDがあります。また、file[0-8]0常に行5を生成しますID00000000

このデータセットで実行される最上位の小さな関数の出力は次のとおりです。

+ : file10 ID00000000
+ : file00 ID00000000
+ : file20 ID00000000
+ : file30 ID00000000
+ : file40 ID00000000
+ : file50 ID00000000
+ : file60 ID00000000
+ : file70 ID00000000
+ : file80 ID00000000

+何らかの理由で出力のシンボルが気に入らない場合は、$PS4最後のシェルプロセスを変更してください。最後の行の先頭に次を追加すると、この問題を処理できます。

PS4= sh ...

ただし、必要に応じてシェルスクリプトの実行可能ビットまで任意の文字列に設定することもでき、必要に応じてファイル名を区別します。デフォルトでは、ヒントを自動区切り記号として自由に使用できます。最後のシェルプロセスにはまだ配列のファイル名が含まれています。必要に応じてデータを操作するコマンドを追加できます。

答え2

ファイル名にスペースや改行文字がなく、uniqこのオプションをサポートするGNUを使用できると仮定すると、これは非常に簡単です(識別子の行を変更するには、-D次の数字を変更してください)。FNR==

awk 'FNR==2 { print FILENAME,$0 }' * | sort -k 2 | uniq -Df 1 | cut -d ' ' -f 1

-Dオプションがないと、状況がより複雑になる可能性があります。これを行う1つの方法は、uniq次を使用して結果を逆にすることです。uniq -ucomm

awk 'FNR==2 { print FILENAME,$0 }' * | sort >/tmp/sorted_keys
sort -k 2 /tmp/sorted_keys |
  uniq -uf 1 | sort | comm -23 /tmp/sorted_keys - | cut -d ' ' -f 1

任意の名前のファイルに対してこれを行うには、おそらく最良の方法は次のとおりです(識別子の行を変更するには、perl行1の後の数字を変更してください)。$.==

perl -ne 'push(@{$table{$_}}, $ARGV) if $.==2;
  $.=0 if eof;
  END {
    for my $val (values %table) {
      print join( "\n", @{$val} ) . "\n" if @{$val} > 1;
    }
  }' *

アイデアは、ファイル内の識別子で各ファイル名を索引付けして、各識別子を使用してファイル名の配列を取得できるようにすることです。これにより、複数の要素を含む各配列を簡単に印刷できます。

修正する

実際、上記と同じアプローチを使用できますawk

awk 'FNR==2 {
  i=table_sizes[$0]++;
  table[$0,i]=FILENAME
  }
  END {
    for (key in table_sizes) {
      if (table_sizes[key] > 1) {
        for (long_key in table) {
          if ( index(long_key, key SUBSEP) == 1 ) {
            print table[long_key]
            delete table[long_key]  # speed up next search
          }
        }
      }
    }
  }' *

唯一の質問は、値がSUBSEP識別子に表示されるかどうかです。通常はSUBSEP印刷されない文字(0x1c)なので、ほとんどのテキストファイルでは問題になりません。必要に応じて変更することができ、それをサポートする実際の多次元配列に例を適用できます(例:array[x][y]代わりにarray[x,y])。awkgawk

答え3

形式を説明するとより具体的な情報を提供できますが、議論のために識別子が各ファイルの3行目のスペースで区切られた最初の単語であるとします。その場合は、次のようにできます。

for f in *; do printf "%s\t%s\n" "$f" $(awk 'NR==3{print $1}' "$f"); done |
 perl -F"\t" -lane '$k{$F[1]}{$F[0]}++; 
  END{
   foreach (keys(%k)){
     print "$_ : ", join ",",keys(%{$k{$_}}) if scalar (keys(%{$k{$_}})) > 0 }
  }'

説明する

  • for f in *; do printf "%s\t%s\n" "$f" $(awk 'NR==3{print $1}' "$f"); done:現在のディレクトリ(およびサブディレクトリ)のすべてのファイルを繰り返し、ファイル名、タブ文字(\t)、および3行目の最初のフィールド(コマンドawk)を印刷します。

  • perl -F"\t" -lane:この-aフラグは、入力行を与えられた文字に基づいてフィールドに自動的に分割し、そのフィールドを配列に保存するなどのperl操作を実行します。各入力行から末尾の改行を削除し、実行する必要があるスクリプトである各呼び出しに改行を追加します。awk-F@F-lprint-e

  • $k{$F[1]}{$F[0]}++:ハッシュハッシュにファイル名/識別子のペアを保存します。ここで、識別子は最初のハッシュのキー、ファイル名は2番目のハッシュのキーです。結果の構造は次のとおりです。

    $k{identifier1}{filename1}
    $k{identifier1}{filename2}
    $k{identifier1}{filenameN}
    
  • このEND{}ブロックは入力全体を読み込んだ後に実行されます。

  • このforeachループはハッシュ%k(ファイル名)の各キーを繰り返し、識別子($_、キー)と子ハッシュ()のキーリストを印刷しますkeys(%{$k{$_}}

次のコマンドで生成されたファイルセットをテストしました。

for i in {1..5}; do echo -e "$RANDOM\nbar\n$i" | tee file$i > file${i}d; done

上記のコードは、同じ3行目を使用して5組のファイル(file1 / file1d〜file5 / file5d)を生成します。これらのファイルに対して上記のコマンドを実行すると、次のようになります。

id2 : file2d,file2
id4 : file4,file4d
id5 : file5d,file5
id1 : file1,file1d
id3 : file3,file3d

関連情報