巨大なCSVで重複したファイル名を見つけるためのスクリプトの最適化

巨大なCSVで重複したファイル名を見つけるためのスクリプトの最適化

スクリプトによって生成された1 MBから6 GBのサイズのCSVファイルが複数あり、inotifyイベントリストの形式は次のとおりです
timestamp;fullpath;event;size

これらのファイルの形式は次のとおりです。

timestamp;fullpath;event;size
1521540649.02;/home/workdir/ScienceXMLIn/config.cfg;IN_OPEN;2324
1521540649.02;/home/workdir/ScienceXMLIn/config.cfg;IN_ACCESS;2324
1521540649.02;/home/workdir/ScienceXMLIn/config.cfg;IN_CLOSE_NOWRITE;2324
1521540649.02;/home/workdir/quad_list_14.json;IN_OPEN;2160
1521540649.03;/home/workdir/quad_list_14.json;IN_ACCESS;2160
1521540649.03;/home/workdir/quad_list_14.json;IN_CLOSE_NOWRITE;2160
1521540649.03;/home/workdir/ScienceXMLIn/masterbias_list.asc;IN_OPEN;70
1521540649.03;/home/workdir/ScienceXMLIn/masterbias_list.asc.1;IN_OPEN;80
1521540649.03;/home/workdir/ScienceXMLIn/masterbias_list.asc.2;IN_OPEN;70
1521540649.03;/home/workdir/otherfolder/quad_list_14.json;IN_OPEN;2160
1521540649.03;/home/workdir/otherfolder/quad_list_14.json;IN_CLOSE_NOWRITE;2160

私の目標は、別のフォルダに表示される同じ名前のファイルを識別することです。
この例では、ファイルがおよびquad_list_14.jsonすべてに表示されます。/home/workdir/otherfolder/home/workdir/

私が望む出力は簡単です。複数のフォルダに表示されるファイルのリストです。この場合、次のようになります。

quad_list_14.json

そのために、私は次のような小さなコードを書いた。

#this line cut the file to only get unique filepath
PATHLIST=$(cut -d';' -f 2 ${1} | sort -u)
FILENAMELIST=""

#this loop build a list of basename from the list of filepath
for path in ${PATHLIST}
do
    FILENAMELIST="$(basename "${path}")
${FILENAMELIST}"
done

#once the list is build, I simply find the duplicates with uniq -d as the list is already sorted
echo "${FILENAMELIST}" | sort | uniq -d

自宅ではこのコードを使用しないでください。残念です。このスクリプトを次のオンラインユーザーに置き換える必要があります。

#this get all file path, sort them and only keep unique entry then
#remove the path to get the basename of the file 
#and finally sort and output duplicates entry.
cut -d';' -f 2 ${1} | sort -u |  grep -o '[^/]*$' | sort | uniq -d

私の問題はまだ残っています。ファイル数が多いため、SSDでは少なくとも0.5秒かかりますが、別のフォルダで重複したファイル名を見つけるには最大45秒(私の本番ディスクはそれほど高速ではありません)がかかります。

このコードをより効率的にするために改善する必要があります。私の唯一の制限は、ファイルをRAMに完全にロードできないことです。

答え1

次のAWKスクリプトは、あまりにも多くのメモリを使用せずにこの問題を解決する必要があります。

#!/usr/bin/awk -f

BEGIN {
    FS = ";"
}

{
    idx = match($2, "/[^/]+$")
    if (idx > 0) {
        path = substr($2, 1, idx)
        name = substr($2, idx + 1)
        if (paths[name] && paths[name] != path && !output[name]) {
            print name
            output[name] = 1
        }
        paths[name] = path
    }
}

各ファイルのパスと名前を抽出し、各名前の最後のパスを保存します。以前に別のパスを見た場合、すでに出力されていない場合はその名前を出力します。

答え2

コードの主な問題は、変数のすべてのパス名を収集してからそれを繰り返し呼び出すことですbasename。これにより速度が遅くなります。

ループは引用符なしの変数拡張でも機能します${PATHLIST}が、パス名にスペースまたはシェルワイルドカードが含まれている場合は賢明ではありません。 (またはそれをサポートする他のシェル)では、代わりにbash配列を使用します。

提案:

$ sed -e '1d' -e 's/^[^;]*;//' -e 's/;.*//' file.csv | sort -u | sed 's#.*/##' | sort | uniq -d
quad_list_14.json

最初はsedパス名を選択し、ヘッダー行を削除します。これはawk -F';' 'NR > 1 { print $2 }' file.csv、またはで書くこともできますtail -n +2 file.csv | cut -d ';' -f 2

sort -u一意のパス名が与えられ、デフォルトsed名は次のとおりです。最後のsort記号は、uniq -dどの基本名が重複しているかを示します。

sed 's#.*/##'デフォルト名を提供する最後の関数は、パラメータ拡張を${pathname##*/}連想させます。これにより、文字列から最後の項目までのすべての項目が$( basename "$pathname" )削除されます。/

basenameコードとの主な違いは、複数の呼び出しにループを使用するのではなく、単一のsedループを使用してパス名のリストから基本名を生成することです。


アイテムだけを見るIN_OPENのではなく:

sed -e '/;IN_OPEN;/!d' -e 's/^[^;]*;//' -e 's/;.*//' file.csv | sort -u | sed 's#.*/##' | sort | uniq -d

答え3

回答をいただいた皆さんに感謝し、コメントをいただいたIsaacにも感謝します。
すべてのコードをインポートしてスクリプトに入れた後、次のようなstephen.awk kusa.sh小さなisaac.shベンチマークを実行しました。

for i in $(ls *.csv)
do
    script.sh $1
done

コマンドを使用してtime比較しましたが、結果は次のとおりです。

stephen.awk

real    2m35,049s
user    2m26,278s
sys     0m8,495s

stephen.awk:2番目のブロックの前の/IN_OPEN/に更新

real    0m35,749s
user    0m15,711s
sys     0m4,915s

kusa.sh

real    8m55,754s
user    8m48,924s
sys     0m21,307s

フィルタで更新IN_OPEN:

real    0m37,463s
user    0m9,340s
sys     0m4,778s

注:
正確ですが、空行をたくさん出力しましたが、sedあなたのスクリプトはこのような唯一のものです。

isaac.sh

grep -oP '^[^;]*;\K[^;]*' file.csv | sort -u | grep -oP '.*/\K.*' | sort | uniq -d
real    7m2,715s
user    6m56,009s
sys     0m18,385s

フィルタがオンのときIN_OPEN

real    0m32,785s
user    0m8,775s
sys     0m4,202s

私のスクリプト

real    6m27,645s
user    6m13,742s
sys     0m20,570s

@Stephenあなたは時間を2.5倍短くして確かに勝ちました。これは印象的です。

しかし、しばらく考えた後に別のアイデアが浮かび上がりました。 OPENファイルイベントだけを見ると、複雑さが減り、最初にファイルを開かずにファイルにアクセスしたり、ファイルに書き込めなくてはならないということでした。それでこうしました。完了:

#see I add grep "IN_OPEN" to reduce complexity
PATHLIST=$(grep "IN_OPEN" "${1}" | cut -d';' -f 2 | sort -u)
FILENAMELIST=""
for path in ${PATHLIST}
do
    FILENAMELIST="$(basename "${path}")
${FILENAMELIST}"
done
echo "${FILENAMELIST}" | sort | uniq -d

この修正だけで同じ結果が得られ、最終的には次のtime値になります。

real    0m56,412s
user    0m27,439s
sys     0m9,928s

私はできることがたくさんあると確信しています。

関連情報