중복 디렉터리 찾기 및 나열

중복 디렉터리 찾기 및 나열

많은 하위 디렉터리가 있는 디렉터리가 있는데 중복된 디렉터리를 찾고 싶습니다. 폴더 구조는 다음과 같습니다.

└── Top_Dir
    └── Level_1_Dir
        ├── standard_cat
        │   ├── files.txt
        ├── standard_dog
        │   └── files.txt
        └── standard_snake
            └── files.txt
    └── Level_2_Dir
        ├── standard_moon
        │   ├── files.txt
        ├── standard_sun
        │   └── files.txt
        └── standard_cat
            └── files.txt
    └── Level_3_Dir
        ├── standard_man
        │   ├── files.txt
        ├── standard_woman
        │   └── files.txt
        └── standard_moon
            └── files.txt

위의 예를 사용하여 다음 출력을 보고 싶습니다.

/top_dir/Level_1_Dir/standard_cat
/top_dir/Level_2_Dir/standard_cat
/top_dir/Level_2_Dir/standard_moon
/top_dir/Level_3_Dir/standard_moon

나는 bash를 통해 이 작업을 수행하는 방법을 찾고 있었지만 아무것도 찾지 못했습니다. 이 작업을 수행하는 방법을 아는 사람이 있나요?

답변1

내 음악 컬렉션에도 같은 문제가 있습니다... 대부분의 도구/스크립트는 시끄럽거나(파일 이름 나열) 파일 내용에 대한 유효성 검사를 수행하는 데 너무 느립니다...

특수 문자, 공백 및 기호가 이를 어렵게 만듭니다. 전략은 MD5sum입니다.정렬됨문서이름그러면 스크립트는 상위 디렉터리와 함께 해시를 정렬하여 중복 항목을 찾을 수 있습니다. find가 서로 다른 두 디렉토리에 있는 파일의 순서를 보장할 수 없기 때문에 하위 파일 이름을 정렬해야 합니다.

Bash 스크립트(Debian 10):

#!/bin/bash

# usage: ./find_duplicates tunes_dir
# output: c547c3bcf85b9c578a1a52dd20665343 - /mnt/tunes/soul brothers/Motherlode
# MD5 is generated from all children filenames + album folder name
# sort list by MD5 then list duplicate (32bit hashes) representing albums
# Album/CD1/... Album/CD2/... will show (3) results if Album is duplicated
# CD1/2 example is indistinguishable from Discography/Album/Song.mp3

if [ $# -eq 0 ]; then
    echo "Please supply tunes directory as first arg"
    exit 1
fi

# Using absolute path of tunes_dir param
find $(readlink -f $1) -type d | while IFS= read -r line
do
    cd "$line"
    children=$(find ./ -type f | sort)
    base=$(basename "$line")
    sum=$(echo $children $base | md5sum)
    echo $sum $line
done | sort -n | uniq -D -w 32

디렉토리 구조:

user@pc:~/test# find . -type d
./super soul brothers
./super soul brothers/Stritch's Brew
./super soul brothers/Fireball!
./super soul brothers/Motherlode
./car_tunes
./car_tunes/Fireball!

출력 예:

user@pc:~# ./find_duplicates  test/
07b0f79429663685f4005486af20247a - /root/test/car_tunes/Fireball!
07b0f79429663685f4005486af20247a - /root/test/super soul brothers/Fireball!

답변2

bash버전 4 이상을 사용하세요 . macOS에서는 기본값이 bash너무 오래되었으므로 Homebrew 패키지 관리자를 통해 설치할 수 있습니다 .

# Make glob patterns disappear rather than remain unexpanded
# if the don't match (nullglob).
# Make glob patterns also match hidden names (dotglob).
shopt -s nullglob dotglob

# Create an associative array that hold the number of times
# a directory's name has been seen (the basename of the directory's
# pathname is the key into this array).
declare -A count

# Set the positional parameters ($1, $2, etc.) to the pathnames
# of the directories that we're interested in.
set -- Top_Dir/*/*/

# Loop over out directory paths,
# and count how many times each basename occurs.
for dirpath do
        name=$( basename "$dirpath" )
        count["$name"]=$(( count["$name"] + 1 ))
done

# Loop over the directory paths again, but this time
# output each directory whose basename occurs more than once.
for dirpath do
        name=$( basename "$dirpath" )
        [[ ${count["$name"]} -gt 1 ]] && printf '%s\n' "$dirpath"
done

시험:

$ tree -F
.
|-- Top_Dir/
|   |-- Level_1_Dir/
|   |   |-- standard_cat/
|   |   |-- standard_dog/
|   |   `-- standard_snake/
|   |-- Level_2_Dir/
|   |   |-- standard_cat/
|   |   |-- standard_moon/
|   |   `-- standard_sun/
|   `-- Level_3_Dir/
|       |-- standard_man/
|       |-- standard_moon/
|       `-- standard_woman/
`-- script

13 directories, 1 file
$ bash script
Top_Dir/Level_1_Dir/standard_cat/
Top_Dir/Level_2_Dir/standard_cat/
Top_Dir/Level_2_Dir/standard_moon/
Top_Dir/Level_3_Dir/standard_moon/

이전 버전을 지원하려면 bash선택적으로 디렉터리의 고유한 기본 이름과 각 기본 이름의 발생 횟수를 두 개의 개별 일반 배열에 저장할 수 있습니다. 이를 위해서는 각 루프에서 선형 검색이 필요합니다.

shopt -s nullglob dotglob

set -- Top_Dir/*/*/

names=()
counts=()
for dirpath do
        name=$( basename "$dirpath" )

        found=false
        for i in "${!names[@]}"; do
                if [[ ${names[i]} == "$name" ]]; then
                        found=true
                        break
                fi
        done

        if "$found"; then
                counts[i]=$(( counts[i] + 1 ))
        else
                names+=( "$name" )
                counts+=( 1 )
        fi
done

for dirpath do
        name=$( basename "$dirpath" )

        for i in "${!names[@]}"; do
                if [[ ${names[i]} == "$name" ]]; then
                        [[ ${counts[i]} -gt 1 ]] && printf '%s\n' "$dirpath"
                        break
                fi
        done
done

답변3

이는 bash를 사용하여 Ubuntu에서 수행할 수 있습니다. 트리의 깊이에 관계없이 중복 디렉터리만 일치합니다. $() 부분은 counted 마지막 열의 중복 항목에서 파이프로 구분된 디렉터리 이름 목록을 작성합니다 ls -l. 파이프로 구분된 이 목록은 모든 디렉터리 목록에서 grep을 사용하여 필터링됩니다. 또한 다른 파일은 고려되지 않습니다(전체 단어 일치 등이 사용되지 않음).

> ls -lR Top_Dir/ | grep -E $(ls -lR Top_Dir/ | grep ^d | rev | cut -d" " -f1 | rev | sort | uniq -d | head -c -1 | tr '\n' '|') | grep -v ^d | sed 's/://'

Top_Dir/Level_1_Dir/standard_cat

Top_Dir/Level_2_Dir/standard_cat

상위_방향/레벨_2_방향/표준_문

상위_방향/레벨_3_방향/표준_문

관련 정보