거대한 CSV에서 중복된 파일 이름을 찾는 스크립트 최적화

거대한 CSV에서 중복된 파일 이름을 찾는 스크립트 최적화

스크립트에 의해 생성된 1MB에서 6GB 크기의 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: 두 번째 블록 앞의 /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

나는 내가 할 수 있는 다른 일들이 많이 있다고 확신한다.

관련 정보