我有几个由脚本生成的 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
为我们提供了基本名称。最后的sort
withuniq -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.sh
,isaac.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
我很确定我还有很多其他事情可以做