脚本优化以在巨大的 CSV 中查找重复的文件名

脚本优化以在巨大的 CSV 中查找重复的文件名

我有几个由脚本生成的 CSV 文件,大小从 1MB 到 6GB,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.这使得它变慢。

该循环还对未加引号的变量expansion 运行${PATHLIST},如果路径名包含空格或shell 通配符,则这是不明智的。在bash(或其他支持它的 shell)中,人们会使用数组来代替。

建议:

$ 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, 或 as tail -n +2 file.csv | cut -d ';' -f 2

sort -u我们提供了唯一的路径名,下面sed为我们提供了基本名称。最后的sortwithuniq -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

感谢你们两位的回答,也感谢艾萨克的评论。
我已经获取了您的所有代码并将它们放入脚本中stephen.awk kusa.shisaac.sh然后我运行了一个小型基准测试,如下所示:

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

我使用命令对time它们进行比较,结果如下:

斯蒂芬.awk

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

斯蒂芬.awk:在第二个块之前用/IN_OPEN/更新

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

库萨.sh

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

使用过滤器更新IN_OPEN

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

边注:
虽然正确,但我输出了很多空行sed,但您的脚本是唯一这样的。

艾萨克.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

我很确定我还有很多其他事情可以做

相关内容