[英]How to troubleshoot very slow perl script that runs faster on one server but slow on another
[英]Very slow script
我有個問題。 我需要編寫一個bash腳本,它將在給定路徑中查找所有文件和目錄,並顯示一些有關結果的信息。 允許的時間:30秒。
#!/bin/bash
DIRS=0
FILES=0
OLD_FILES=0
LARGE_FILES=0
TMP_FILES=0
EXE_FILES=0
IMG_FILES=0
SYM_LINKS=0
TOTAL_BYTES=0
#YEAR_AGO=$(date -d "now - 1 year" +%s)
#SECONDS_IN_YEAR=31536000
function check_dir {
for entry in "$1"/*
do
if [ -d "$entry" ]; then
((DIRS+=1))
check_dir "$entry"
else if [ -f "$entry" ]; then
((FILES+=1))
#SIZE=$(stat -c%s "$entry")
#((TOTAL_BYTES+=SIZE))
#CREATE_DATE=$(date -r "$entry" +%s)
#CREATE_DATE=$(stat -c%W "$entry")
#DIFF=$((CREATE_DATE-YEAR_AGO))
#if [ $DIFF -ge $SECONDS_IN_YEAR ]; then
# ((OLD_FILES+=1))
#fi
fi
fi
done
}
if [ $# -ne 2 ]; then
echo "Usage: ./srpt path emailaddress"
exit 1
fi
if [ ! -d $1 ]; then
echo "Provided path is invalid"
exit 1
fi
check_dir $1
echo "Execution time $SECONDS"
echo "Dicrecoties $DIRS"
echo "Files $FILES"
echo "Sym links $SYM_LINKS"
echo "Old files $OLD_FILES"
echo "Large files $LARGE_FILES"
echo "Graphics files $IMG_FILES"
echo "Temporary files $TMP_FILES"
echo "Executable files $EXE_FILES"
echo "Total file size $TOTAL_BYTES"
這是執行上面帶有注釋行的結果:
Execution time 1
Dicrecoties 931
Files 14515
Sym links 0
Old files 0
Large files 0
Graphics files 0
Temporary files 0
Executable files 0
Total file size 0
如果我要刪除評論
SIZE=$(stat -c%s "$entry")
((TOTAL_BYTES+=SIZE))
我有:
Execution time 31
Dicrecoties 931
Files 14515
Sym links 0
Old files 0
Large files 0
Graphics files 0
Temporary files 0
Executable files 0
Total file size 447297022
31秒。 如何加快腳本速度? 再加上30秒鍾,可以查找日期創建一年以上的文件
通常,在Shell中使用循環表明您選擇了錯誤的方法。
外殼首先是運行其他工具的工具。
盡管它可以進行計數,但是awk
是一個更好的工具。
盡管它可以列出和查找文件,但find
更好。
最好的Shell腳本是那些設法使一些工具有助於完成任務的腳本,而不是那些順序啟動數百萬個工具並且所有工作都由Shell完成的腳本。
在這里,通常是一個更好的辦法是有find
找到文件,並收集所有你需要的數據,有awk
咀嚼它,並返回統計信息。 這里使用GNU find
和GNU awk
(對於RS='\\0'
)和GNU date
(對於-d
):
find . -printf '%y.%s.%Ts%p\0' |
awk -v RS='\0' -F'[.]' -v yearago="$(date -d '1 year ago' +%s)" '
{
type[$1]++;
if ($1 == "f") {
total_size+=$2
if ($3 < yearago) old++
if (!index($NF, "/")) ext[tolower($NF)]++
}
}
END {
printf("%20s: %d\n", "Directories", type["d"])
printf("%20s: %d\n", "Total size", total_size)
printf("%20s: %d\n", "old", old)
printf("%20s: %d\n", "jpeg", ext["jpg"]+ext["jpeg"])
printf("%20s: %d\n", "and so on...", 0)
}'
關鍵是要避免啟動過多的實用程序。 您似乎每個文件調用兩個或三個,這將非常慢。
此外,注釋還顯示,處理文件名通常很復雜,尤其是在文件名中可能包含空格和/或換行符的情況下。 但是,如果我正確地理解了您的問題,那么實際上並不需要文件名,因為您僅使用它們來收集信息。
如果您使用的是gnu find
,則可以直接從find
提取統計信息,這樣效率會高得多,因為find
仍然需要對每個文件執行stat()
。 這是一個示例,為簡單起見,將其從find
awk
到awk
:
summary() {
find "$@" '(' -type f -o -type d ')' -printf '%y %s %C@\n' |
awk '$1=="d"{DIR+=1;next}
$1!="f"{next}
{REG+=1;SIZE+=$2}
$3<'$(date +%s -d"last year")'{OLD+=1}
END{printf "Directories: %d\nFiles: %d\nOld files: %d\nTotal Size: %d\n",
DIR, REG, OLD, SIZE}'
}
在我的機器上,這在十分之一秒的時間內就將4718目錄中的28718個文件匯總了。 YMMV。
您肯定希望避免像以前那樣解析find
的輸出(請參閱我的評論):只要文件名中有空格,它就會中斷。
您肯定要避免派生到$(stat ...)
或$(date ...)
語句之類的外部過程:每個fork都花很多錢!
事實證明, find
可以做很多事情。 例如,如果我們要計算文件,目錄和鏈接的數量。
我們都知道bash的幼稚方式(幾乎完成了):
#!/bin/bash
shopt -s globstar
shopt -s nullglob
shopt -s dotglob
nbfiles=0
nbdirs=0
for f in ./**; do
[[ -f $f ]] && ((++nbfiles))
[[ -d $f ]] && ((++nbdirs))
done
echo "There are $nbdirs directories and $nbfiles files, and we're very happy."
警告 。 此方法根據鏈接的鏈接數進行計數:指向文件的鏈接將被計為文件。
find
方式如何? 計算文件,目錄和(符號)鏈接的數量:
#!/bin/bash
nbfiles=0
nbdirs=0
nblinks=0
while read t n; do
case $t in
dirs) ((nbdirs+=n+1)) ;;
files) ((nbfiles+=n+1)) ;;
links) ((nblinks+=n+1)) ;;
esac
done < <(
find . -type d -exec bash -c 'echo "dirs $#"' {} + \
-or -type f -exec bash -c 'echo "files $#"' {} + \
-or -type l -exec bash -c 'echo "links $#"' {} + 2> /dev/null
)
echo "There are $nbfiles files, $nbdirs dirs and $nblinks links. You're happy to know aren't you?"
使用關聯數組,更多字段和更多涉及的相同原理find
邏輯:
#!/bin/bash
declare -A fields
while read f n; do
((fields[$f]+=n))
done < <(
find . -type d -exec bash -c 'echo "dirs $(($#+1))"' {} + \
-or -type f -exec bash -c 'echo "files $(($#+1))"' {} + -printf 'size %s\n' \
\( \
\( -iname '*.jpg' -printf 'jpg 1\n' -printf 'jpg_size %s\n' \) \
-or -size +100M -printf 'large 1\n' \
\) \
-or -type l -exec bash -c 'echo "links $(($#+1))"' {} + 2> /dev/null
)
for f in "${!fields[@]}"; do
printf "%s: %s\n" "$f" "${fields[$f]}"
done
我希望這會給您一些想法! 祝好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.