디렉터리당 특정 파일 형식에 대한 디스크 사용량을 측정합니다("du --include"의 시연으로 반복적으로).

디렉터리당 특정 파일 형식에 대한 디스크 사용량을 측정합니다("du --include"의 시연으로 반복적으로).

이것은 내 작업 코드이지만 최적화되지 않았다고 생각합니다. 이보다 더 빨리 작업을 완료할 수 있는 방법이 있어야 합니다.

find . -type f -iname '*.py' -printf '%h\0' |
  sort -z -u |
  xargs -r -0 -I{} sh -c '
    find "{}" -maxdepth 1 -type f -iname "*.py" -print0 |
      xargs -r -0 du -sch |
      tail -1 |
      cut -f1 |
      tr "\n" " "
    echo -e "{}"' |
  sort -k1 -hr |
  head -50

목표는 포함된 모든 디렉터리를 재귀적으로 검색한 *.py다음 각 디렉터리의 이름으로 모든 파일의 전체 크기를 인쇄하고 *.py크기의 역순으로 정렬한 다음 처음 50개만 표시하는 것입니다.

이 코드를 성능 측면에서 개선하면서도 동일한 출력을 유지할 수 있는 방법에 대한 아이디어가 있습니까?

편집하다:

다음 예에서 귀하의 제안을 테스트했습니다. 47GB total: 5805 files 불행하게도 모든 제안이 동일한 지침을 따르지는 않기 때문에 처음부터 끝까지 비교할 수 없습니다. 전체 크기는 디스크 사용량이어야 하고 구분 기호는 공백이어야 합니다. 형식은 다음과 같아야 합니다.numfmt --to=iec-i --suffix=B

다음 4개는 정렬된 출력이지만 David는 실제 디스크 사용량이 아닌 파일의 누적 크기를 보여줍니다. 그러나 그의 향상은 눈에 띄게 향상되었습니다. 9.5배 이상 빨라졌습니다. Stéphane과 Isaac의 코드는 참조 코드보다 약 32배 빠르기 때문에 매우 가까운 승자입니다.

$ time madjoe.sh
real    0m2,752s
user    0m3,022s
sys     0m0,785s

$ time david.sh 
real    0m0,289s
user    0m0,206s
sys     0m0,131s

$ time isaac.sh 
real    0m0,087s
user    0m0,032s
sys     0m0,032s

$ time stephane.sh 
real    0m0,086s
user    0m0,013s
sys     0m0,047s

불행하게도 다음 코드는 최대 50개의 결과를 정렬하거나 표시하지 않습니다. 또한 이전에 Isaac의 코드와 비교할 때 다음 코드는 Isaac의 개선 사항보다 약 6배 느립니다.

$ time hauke.sh 
real    0m0,567s
user    0m0,609s
sys     0m0,122s

답변1

배열의 모든 디렉토리 합계를 수집하고 마지막에 모두 인쇄하여 @HaukeLaging의 솔루션을 단순화했습니다(GNU awk 사용). 또한 numfmt(결국) 한 번의 호출만 필요합니다 .

#!/bin/sh

find . -type f -iname '*.py' -printf '%s %h\0' |
    awk 'BEGIN { RS="\0"; };

         { gsub(/\\/,"&&"); gsub(/\n/,"\\n");
           size=$1; sub("[^ ]* ",""); dirsize[$0]+=size }

         END {   PROCINFO["sorted_in"] = "@val_num_desc";
                 i=0;
                 for ( dir in dirsize ) { if(++i<=50) 
                     { print dirsize[dir], dir; }else{ exit } 
                 }
             }        ' | numfmt --to=iec-i --suffix=B

이는 디스크 사용량이 아닌 py 파일의 누적 겉보기 크기를 생성하고 디렉토리의 하위 디렉토리에 있는 파일을 합산하는 것을 방지합니다.

답변2

%b겉보기 크기의 합이 아닌 디스크 사용량을 계산하려면 대신 ¹를 사용해야 하며 %s각 파일을 한 번만 계산해야 합니다. 즉, 다음과 같습니다.

LC_ALL=C find . -iname '*.py' -type f -printf '%D:%i\0%b\0%h\0' |
  gawk -v 'RS=\0' -v OFS='\t' -v max=50 '
    {
      inum = $0
      getline du
      getline dir
    }
    ! seen[inum]++ {
      gsub(/\\/, "&&", dir)
      gsub(/\n/, "\\n", dir)
      sum[dir] += du
    }
    END {
      n = 0
      PROCINFO["sorted_in"] = "@val_num_desc"
      for (dir in sum) {
        print sum[dir] * 512, dir
        if (++n >= max) break
      }
    }' | numfmt --to=iec-i --suffix=B --delimiter=$'\t'

디렉터리 이름의 줄 바꿈은 으로 렌더링되고 \n백슬래시(최소한 현재 로케일에서 디코딩됨)는 으로 렌더링됩니다 \\.

파일이 둘 이상의 디렉터리에서 발견되면 파일은 발견된 첫 번째 디렉터리에 따라(순서가 정해지지 않음) 계산됩니다.

POSIXLY_CORRECT환경에 변수가 없다고 가정합니다(변수가 있는 경우 설정이 PROCINFO["sorted_in"]적용되지 않으므로 gawk목록이 정렬되지 않습니다). 이를 보장할 수 없는 경우 언제든지 시작할 수 있습니다 gawk( env -u POSIXLY_CORRECT gawk ...GNU env또는 호환 가능 또는 (unset -v POSIXLT_CORRECT; gawk ...)).

귀하의 접근 방식에는 몇 가지 다른 문제가 있습니다.

  • 그렇지 않은 경우 LC_ALL=CGNU는 find이름이 로케일에서 유효한 문자를 구성하지 않는 파일을 보고하지 않으므로 일부 파일이 누락될 수 있습니다.
  • 임베디드 {}코드는 sh임의 코드 주입 취약점을 구성합니다. 예를 들어 이라는 파일을 생각해 보세요 $(reboot).py. 절대 이렇게 하면 안 됩니다. 파일 경로는 추가 매개변수로 전달되어야 하며 위치 매개변수를 사용하여 코드에서 참조되어야 합니다.
  • echo-e임의의 데이터(특히 여기서 의미가 없는 데이터) 를 표시하는 데 사용할 수 없습니다 . 대신 사용하십시오 printf.
  • 파일 목록이 큰 경우 여러 번 호출될 수 있으며, 이 경우 마지막 줄에는 마지막 실행의 총계만 포함됩니다 xargs -r0 du -sch.du

1디스크 %b사용량은 512바이트 단위로 보고됩니다. 512바이트는 기존 섹터의 크기이므로 디스크 할당의 최소 단위입니다. %k이라는 것도 있지만 int(%b / 2)이는 512바이트 블록이 있는 파일 시스템에서 잘못된 결과를 제공합니다(파일 시스템 블록은 일반적으로 2의 거듭제곱이고 크기는 최소 512바이트입니다).

²Gawk LC_ALL=C에서도 사용하면 좀 더 효율적이지만 백슬래시도 인코딩되므로 BIG5 또는 GB18030 문자 집합을 사용하는 로케일에서 출력이 중단될 수 있습니다(파일 이름도 해당 문자 집합으로 인코딩됨). 거기에 다른 문자도 있습니다. 코딩에서.

³ 스크립트에서 shis가 로 설정되어 있고 또는로 시작하는 경우 환경으로 내보내지므로 해당 변수도 의도치 않게 몰래 들어올 수 있습니다.bashPOSIXLY_CORRECTyshsh-a-o allexport

답변3

나는 당신이 자신의 du를 작성해야한다고 생각합니다.

현재 두 개의 find와 하나의 du를 사용하여 계층 구조를 세 번 반복합니다.

Perl File::Find패키지부터 시작하는 것이 좋습니다.

또는 첫 번째 조회에서 다음과 같은 내용이 출력될 수 있습니다 -printf '%k %h\n'. 그런 다음 디렉터리별로 정렬하고 Perl 또는 awk(또는 심지어 bash)를 사용하여 디렉터리를 합산하고 "사람이" 읽을 수 있는 형식으로 변환한 다음 마지막으로 정렬 및 헤더를 작성할 수 있습니다.

어느 쪽이든 A) 디렉터리 트리를 한 번만 탐색하고 B) 가능한 적은 수의 프로세스를 만들어야 합니다.

편집: 구현 예

#!/bin/bash

find . -type f -iname '*.py' -printf '%k %h\n' | sort -k2 | (
    at=
    bt=
    output() {
        if [[ -n "$at" ]]
        then
            printf '%s\t%s\n' "$at" "$bt"
        fi
    }
    while read a b
    do
        if [[ "$b" != "$bt" ]]
        then
            output
            bt="$b"
            at=0
        fi
        at=$(( $at + $a ))
    done
    output
) | sort -hr | head -50 | numfmt -d'   ' --field=1 --from-unit=Ki --to=iec-i

참고: %k가 중요합니다. %s는 겉보기 크기를 보고하고 %k(및 du)는 디스크 크기를 보고합니다. 스파스 파일과 대용량 파일의 경우 다릅니다. (원한다면 du --apparent-size그렇게 하세요.)

참고: numfmt는 마지막에 배치되어야 한 번 실행됩니다. "%k"를 사용할 경우 시작단위를 지정해야 합니다.

참고: numfmt에 대한 -d 매개변수에는 단일 탭이 포함되어야 합니다. 여기에 입력할 수 없으며 numfmt에서는 이를 허용하지 않습니다 -d'\t'. 구분 기호가 탭이 아닌 경우 간격이 엉망이 됩니다. 그래서 본문에 echo 대신 printf를 사용했습니다. (또 다른 방법은 마지막에 echo와 sed를 사용하여 첫 번째 공백을 탭으로 변경하는 것입니다.

참고: 처음에 첫 번째 정렬을 놓쳤고 다시 테스트할 때 일부 디렉터리에 대한 중복 항목이 생겼습니다.

참고: numfmt는 최근 추가되었습니다.

답변4

이것은 훨씬 더 빠를 수 있지만 귀하의 방법과 정확히 동일하지 않습니까? 하위 디렉터리 파일은 두 번 계산되지 않습니다.

find . -type f -iname '*.py' -printf '%s %h\0' |
    awk 'BEGIN { RS="\0"; }; '\
'{ pos=index($0," "); size=substr($0,1,(pos-1)); dir=substr($0,pos+1); gsub("\n","\\n",dir); '\
'if(dir!=lastdir) { if(NR>1) { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; } '\
'sizesum=size; lastdir=dir; } '\
'else sizesum=sizesum+size; }; '\
'END { "numfmt --to=iec-i --suffix=B " sizesum " | tr -d \"\n\"" | getline fsize; print fsize " " lastdir; }'

3,2KiB ./dir1
1,1MiB ./dir2

더 빠른 것 외에도 개행 문자를 리터럴로 대체합니다 \n. 줄 바꿈이 포함된 디렉터리 이름이 필요한 경우 파이프 끝까지 이를 처리해야 하는데, 이는 코드에서 수행하지 않는 작업입니다.

관련 정보