단일 (특정) 파일이 있는 모든 빈 디렉터리 및 디렉터리 찾기

단일 (특정) 파일이 있는 모든 빈 디렉터리 및 디렉터리 찾기

모든 빈 디렉토리와 .DS_StoreMac 생성 파일만 포함된 디렉토리를 삭제하는 쉘 스크립트를 작성하려고 합니다. 나는 전자를 쉽게 할 수 있다

find -depth -type d -empty

하지만 다음이 포함된 디렉토리를 찾는 방법을 모르겠습니다.오직 .DS_Store.

자체 재귀 검색 기능을 작성하지 않고도 이를 수행할 수 있는 쉬운 방법이 있습니까?

답변1

POSIX sh + 찾기

이는 POSIX find 및 POSIX sh에만 의존하는 솔루션입니다. 모든 디렉터리를 나열한 다음 이름이 지정된 항목만 포함하는 디렉터리를 필터링합니다 .DS_Store.

find . -type d -exec sh -c '
    cd "$0" &&
    for x in * .[!.]* ..?*; do
      if [ "$x" = ".DS_Store" ]; then continue; fi;
      if [ -e "$x" ] || [ -L "$x" ]; then exit 1; fi;
    done' {} \; -print
  • 나는 find모든 디렉토리를 재귀적으로 열거하는 데 사용합니다.
  • sh각 디렉터리에서 일부 셸 코드를 실행하는 호출을 수행합니다 .
  • for루프는 디렉터리의 모든 파일을 열거합니다.
  • 루프 본문을 건너뜁니다 .DS_Store.
  • 이 세 가지 패턴 각각이 어떤 파일과도 일치하지 않으면 변경되지 않은 상태로 유지됩니다. [ -e "$x" ] || [ -L "$x" ]깨진 심볼릭 링크를 포함하여 모든 파일을 캡처합니다. 일치하지 않는 유일한 이유는 패턴이 동일하게 유지되기 때문입니다.
  • exit 1따라서 파일이 아닌 다른 파일이 있으면 쉘 조각이 실행되고 .DS_Store, 그렇지 않으면 성공 시 0이 반환됩니다.
  • 이름 인쇄 이외의 작업을 수행하려면 -print다음으로 변경하세요.-exec …

지쉬

zsh의 솔루션은 다음과 같습니다. echo실행하려는 명령으로 변경하십시오 .

setopt extended_glob
echo **/*(/DNe\''a=($REPLY/^.DS_Store(DNY1)); ((!#a))'\')
  • **/*모든 파일을 재귀적으로 열거합니다.
  • 와 함께글로벌 예선 /, **/*(/)모든 디렉토리를 재귀적으로 열거합니다.
  • Nglob 한정자는 일치하는 항목이 없는 경우 빈 목록을 얻도록 보장합니다(zsh는 기본적으로 오류 신호를 보냅니다).
  • glob 한정자는 D도트 파일을 포함시킵니다.
  • glob 한정자는 일치하는 각 파일 이름에 대해 작동하며 일치 항목을 성공적인 파일 이름으로 제한합니다. 변수를 사용하여 파일 이름을 참조할 수 있습니다.e\''CODE'\'CODECODECODE$REPLY
  • ^.DS_Store호출되지 않은 파일과 일치합니다 .DS_Store.
  • 그게 다야암호.DS_Store0이 아닌 파일 개수와 일치하는 항목 으로 일치를 제한합니다 .
  • glob 한정자는 Y1일치 항목 수를 하나로 제한합니다(이는 효율성 향상일 뿐입니다).

파이썬

다음은 Python의 솔루션입니다(2와 3 모두에서 작동함). 한 줄로 압축되어 있음에도 불구하고 구조는 매우 명확합니다.

python -c 'import os; print("\n".join([path for path, dirs, files in os.walk(".") if dirs == [] and files in ([], [".DS_Store"])]))'
  • os.walk해당 인수 아래에 디렉터리 목록을 반복적으로 반환합니다. 각 디렉터리에 대해 path(디렉터리 경로), dirs(하위 디렉터리 목록) 및 files(디렉터리 자체가 디렉터리가 아닌 디렉터리의 파일 목록)을 포함하는 트리플을 생성합니다 .
  • [… for … in os.walk(…) if …]os.walk필터링 된 결과
  • if이 절은 하위 디렉토리가 없고 외부에 파일이 없는 경우에만 요소를 보존합니다 .DS_Store.
  • 스크립트는 중간 줄 바꿈과 마지막 줄 바꿈 사이에 줄 바꿈이 포함된 허용된 요소를 인쇄합니다.

답변2

간단한 해결 방법: 먼저 해당 파일을 모두 삭제합니다.

find <path> -type f -name "*.DS_Store" -delete

그런 다음 빈 디렉토리를 삭제하십시오.

의견을 기반으로 업데이트: 이러한 파일만 포함된 디렉토리만 삭제하려면 다음과 같은 것이 필요합니다(경고: 전혀 테스트되지 않았으므로 더 많은 참여가 필요하더라도 놀라지 않을 것입니다).

find <path> -type d | while read dir; do
    if ! ls --ignore=*.DS_Store $dir; then
        rm -rf $dir
    fi
done

복잡한 부분에 대한 설명:

ls --ignore=*.DS_Store $dir

.DS_Store로 끝나지 않는 $dir의 파일은 인쇄되어야 합니다. 그렇지 않은 경우 표현식은 False이고 if 블록이 실행됩니다.

답변3

맥의 경우:

find . -type d | while read dir; do
    
    # Found empty dir if it only has:
    #   ., .. 
    #   ., .., .DS_Store
    
    if [ "$(ls -am "$dir" | sed -E 's/^., ..$|^., .., .DS_Store$//')" == "" ]; then
        echo rm -R "\"$dir\""
    fi
done

답변4

더 많은 보호 기능을 갖춘 또 다른 POSIX 변형:

find . -type d -exec sh -c '
  for dir do
    contents=$(ls -AFq "$dir") &&
      case $contents in
        ("" | .[dD][sS]_[sS][tT][oO][rR][eE]) printf "%s\n" "$dir"
      esac
  done' sh {} +

여기:

  • ls디렉터리 내용을 나열할 수 없거나 디렉터리 내용을 찾을 수 없는 경우 문제를 해결할 수 있습니다.
  • 를 사용함으로써 ls -F우리는 디렉토리가.DS_Store정기적인파일( -F다른 유형의 파일에 접미사 추가)
  • 예를 들어 를 사용하면 -q파일 이름에 대한 오탐을 방지할 수 있습니다.$'.DS_Store\n\n'
  • Macos에서는 파일 이름이 대소문자를 구분하지 않는다고 생각하기 때문에 대소문자를 구분하지 않고 일치시킵니다.

이에 상응하는 내용은 zsh다음과 같습니다.

empty() {
  set -o localoptions -o extendedglob
  ERRNO=0
  () ((!ERRNO && !$#)) $REPLY/(#i){^.ds_store(NDY1),.ds_store(N^.)}
}
print -rC1 -- **/*(ND/+empty)

이제 이러한 디렉터리를 삭제하려는 경우 해당 디렉터리 dirA에 파일만 포함되어 dirB있고 dirA/dirB비어 있거나 .DS_Store파일만 포함되어 있는 경우가 있습니다. 그러면 삭제 후 비어 있어도 dirA신고하지 않습니다.dirA/dirB

이 문제를 해결하려면 여기를 사용할 수 있지만 빈 디렉토리는 발견되는 즉시 삭제되기 때문에 -depth사용할 수 없습니다. 이는 중요하므로 다음을 사용합니다 .-exec ... {} +-exec ... {} ';'

find . -depth -type d -exec sh -c '
  contents=$(ls -AFq "$1") &&
    case $contents in
      ("" | .[dD][sS]_[sS][tT][oO][rR][eE]) exec rm -rf "$1"
    esac' sh {} ';'

관련 정보