이것은 내 작업 코드이지만 최적화되지 않았다고 생각합니다. 이보다 더 빨리 작업을 완료할 수 있는 방법이 있어야 합니다.
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=C
GNU는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 문자 집합을 사용하는 로케일에서 출력이 중단될 수 있습니다(파일 이름도 해당 문자 집합으로 인코딩됨). 거기에 다른 문자도 있습니다. 코딩에서.
³ 스크립트에서 sh
is가 로 설정되어 있고 또는로 시작하는 경우 환경으로 내보내지므로 해당 변수도 의도치 않게 몰래 들어올 수 있습니다.bash
POSIXLY_CORRECT
y
sh
sh
-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
. 줄 바꿈이 포함된 디렉터리 이름이 필요한 경우 파이프 끝까지 이를 처리해야 하는데, 이는 코드에서 수행하지 않는 작업입니다.