【问题标题】:Very slow script很慢的脚本
【发布时间】:2013-06-23 20:33:56
【问题描述】:

我有问题。我需要编写一个 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 秒,可以找到日期创建更多一年的文件

【问题讨论】:

  • 如果目录名或文件名有空格或其他有趣的符号,这从一开始就被破坏了。
  • 我已经通过在所有带路径的变量之前和之后添加“”解决了这个问题。还是我错过了什么?
  • 你错过了一件重要的事情:你正在解析find的输出!
  • find 输出有什么问题?
  • 不,不要假设,这是糟糕的做法!我绝不会让你那样做!

标签: linux bash shell unix scripting


【解决方案1】:

通常情况下,在 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)
    }'

【讨论】:

    【解决方案2】:

    关键是要避免启动过多的实用程序。您似乎每个文件调用了两个或三个,这会很慢。

    此外,cmets 表明,处理文件名通常很复杂,尤其是在文件名中可能包含空格和/或换行符的情况下。但是,如果我正确理解您的问题,您实际上并不需要文件名,因为您只是使用它们来收集信息。

    如果你使用的是 gnu find,你可以直接从 find 中提取统计信息,这样效率会高很多,因为 find 需要在每个文件上做一个 stat()。这是一个示例,为简单起见,它从 find 管道到 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}'
    }
    

    在我的机器上,它在十分之一秒的时间内汇总了 4817 个目录中的 28718 个文件。 YMMV。

    【讨论】:

    • +1。请注意,%A 如果用于文件访问时间。 OPs 问题中的 Graphics files 表明他最终可能需要文件名。
    • @StephaneChazelas:哎呀。改为 C。
    【解决方案3】:

    您肯定希望避免像以前那样解析 find 的输出(请参阅我的评论):只要文件名中有空格,它就会中断。

    您肯定希望避免分叉到外部进程,例如您的 $(stat ...)$(date ...) 语句:每个分叉的成本都很高!

    事实证明,find 本身可以做很多事情。例如,如果我们要计算文件、目录和链接的数量。

    我们都知道 中的幼稚方式(几乎是你所做的):

    #!/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
    

    我希望这会给你一些想法!祝你好运!

    【讨论】:

      猜你喜欢
      • 2013-07-12
      • 2013-10-19
      • 1970-01-01
      • 2015-11-18
      • 2016-12-02
      • 2018-01-22
      • 1970-01-01
      • 2017-12-05
      • 2021-12-29
      相关资源
      最近更新 更多