V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
0x11901
V2EX  ›  问与答

一个 bash 脚本性能优化的问题?

  •  
  •   0x11901 · 2019-07-23 15:03:16 +08:00 · 2658 次点击
    这是一个创建于 1982 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我想要的功能是寻找当前目录及子目录下所有与传入的分辨率相同的图片的地址。

    环境

    1. Windows
    2. Git Bash + MinGW

    思路

    1. 用 find 找到所有后缀名为 png 的文件的相对路径
    2. 把结果写到一个 temp 文件里
    3. 用 read 读取 temp 文件每一行,以一行一行处理相对路径
    4. 使用 file 获得对应路径的图片文件的信息
    5. 使用 awk 得到其中的分辨率字段
    6. 如果分辨率正确,打印相对路径

    问题

    然而,实际根本无法使用……虽然只有这么一点功能,但是架不住我要找的目录里图片多啊……差不多有 10g 了……第一版直接用不了,我调试了一下,find 还是可以接受的,但是一行一行 awk 太慢了,我尝试了多线程和回溯,勉强可以出结果了,但是我觉得不应该这么慢的,可能是我太菜了……毕竟我甚至不知道怎么把 find 的结果直接在内存中处理,甚至需要写到文件中的……

    代码

    #!/usr/bin/env bash
    
    set -euo pipefail
    
    if [[ $# != 2 ]]; then
        echo bad argument.
        exit 1
    fi
    
    TEMP_FILE=".todo.find"
    
    find . -name "*.png" >>${TEMP_FILE}
    
    while read line; do
        {
            resolution=$(file "${line}" | awk -F ',' '{print $2}')
            width=$(echo ${resolution} | awk '{print $1}')
    
            [[ ${width} != $1 ]] && continue
    
            height=$(echo ${resolution} | awk '{print $3}')
    
            if [[ ${height} == $2 ]]; then
                echo ${line}
            fi
        } &
    done <${TEMP_FILE}
    
    rm ${TEMP_FILE}
    
    

    需求

    总之希望有大佬指点一下怎么优化这个脚本!万分感谢!!!

    39 条回复    2019-07-24 16:45:33 +08:00
    dream10201
        1
    dream10201  
       2019-07-23 15:09:41 +08:00
    用其他语言写,然后找到就别存了,直接获取信息判断
    momocraft
        2
    momocraft  
       2019-07-23 15:14:51 +08:00
    10g 的图片文件一个个 file 本来就会慢

    写得这么复杂,性能也未必比 `find | xargs file | grep` 好很多
    0x11901
        3
    0x11901  
    OP
       2019-07-23 15:25:23 +08:00
    @dream10201 主要我也不怎么会 shell,用它是感觉这个小需求没必要用其他的语言增加复杂度……
    0x11901
        4
    0x11901  
    OP
       2019-07-23 15:25:58 +08:00
    @momocraft 主要还是太菜了,所以写成这样了……
    dream10201
        5
    dream10201  
       2019-07-23 15:28:27 +08:00
    1、递归查找图片
    2、获取文件信息判断分辨率
    两步解决,多线程一开,走位走位
    0x11901
        6
    0x11901  
    OP
       2019-07-23 15:36:42 +08:00
    @dream10201 其实我感觉可能是获取分辨率这里遇到瓶颈了,也许我能用 c 写一个小程序,输入地址打印分辨率……全部换语言太懒了不想改……
    thedrwu
        7
    thedrwu  
       2019-07-23 15:38:07 +08:00 via Android
    其实不是你的错。装个虚拟机,至少 10 倍速度
    dream10201
        8
    dream10201  
       2019-07-23 15:39:58 +08:00
    @0x11901 不不,find 查找所有文件(耗时),写到文件里(耗时),读文件(耗时),这几个才是关键,本来可以一步搞定,非要整几步
    momocraft
        9
    momocraft  
       2019-07-23 15:51:53 +08:00
    这个文件名列表多大
    耗时的真的是读写这个列表(而不是每个图像文件都开 2 个 file 3 个 awk 进程)吗?
    ant2017
        10
    ant2017  
       2019-07-23 15:55:54 +08:00 via Android
    find . -name "*.png" |xargs file>>${TEMP_FILE}
    awk -F "," -v height=$1 -v width=$2 '$1==height && $2==width' ${TEMP_FILE}
    0x11901
        11
    0x11901  
    OP
       2019-07-23 15:56:58 +08:00
    @dream10201 不是 find,我调了一下,find 很快的,读写也挺快的,就是 awk 太慢了。
    0x11901
        12
    0x11901  
    OP
       2019-07-23 15:57:28 +08:00
    @momocraft 就是 awk 那里太久了!
    0x11901
        13
    0x11901  
    OP
       2019-07-23 15:58:07 +08:00
    @ant2017 这是什么神奇操作,我试一下!
    0x11901
        14
    0x11901  
    OP
       2019-07-23 16:00:46 +08:00
    @dream10201 而且主要是我不知道怎么把这个结果搞到内存里,所以才用的 io
    ETiV
        15
    ETiV  
       2019-07-23 16:04:03 +08:00 via iPhone
    xargs 有个大写 P 参数,试试这个
    0x11901
        16
    0x11901  
    OP
       2019-07-23 16:04:11 +08:00
    @ant2017 老哥,不好使啊……
    ReVanTis
        17
    ReVanTis  
       2019-07-23 16:08:44 +08:00
    awk 换成别的工具会好点吗? cut 啥的。。。应该会比 awk 快点
    rrfeng
        18
    rrfeng  
       2019-07-23 16:14:26 +08:00   ❤️ 1
    简单优化一下:

    find | while read x; do file $x; done | awk .....

    总结:
    1. 文件缓存没必要,增加大量 IO
    2. 既然用了 awk,直接使用 awk 解决所有判断、输出的问题,不要用过多的指令(重复 fork 非常多)
    3. 还是要看下到底哪一步耗时才好优化。

    awk 启动慢,因为你启动了太多次,将 file 的结果合并到一起传给 awk 按行处理即可。
    0x11901
        19
    0x11901  
    OP
       2019-07-23 16:23:26 +08:00
    @rrfeng 感觉非常有道理……问题的关键是我 awk 是在阮一峰的入门教程那里速成的啊 😂
    maxbon
        20
    maxbon  
       2019-07-23 16:28:04 +08:00
    find 可以搭配 xargs 使用,find 的结果传过来就好了,然后可以用 & 伪多线程
    Rekkles
        21
    Rekkles  
       2019-07-23 16:36:04 +08:00
    IO 就慢死你了
    0x11901
        22
    0x11901  
    OP
       2019-07-23 17:01:08 +08:00
    @maxbon 加完班晚上回去改改试试!

    @Rekkles 然后貌似不是 IO 的问题
    maxbon
        23
    maxbon  
       2019-07-23 17:02:15 +08:00
    find . -name "*.png" | xargs file |awk -F, '{print $1,$2}'|awk "\$(NF-2)==$1 && \$NF==$2{print\$1}"
    参考一下,没测性能,应该比你的快很多吧,至少全在内存完成的
    maxbon
        24
    maxbon  
       2019-07-23 17:04:43 +08:00   ❤️ 1
    $(NF-2)==$1
    $NF==$2
    这里的$1 和$2 是传过来的目标分辨率参数
    maxbon
        25
    maxbon  
       2019-07-23 17:12:18 +08:00   ❤️ 1
    @0x11901 #22 刚跑了 10 万图片,要 10s,慢在 find
    xderam
        26
    xderam  
       2019-07-23 22:10:22 +08:00 via iPhone
    windows 为啥不用 ntfs 文件系统的特性?类似于 everything 那种搜索工具就是利用 ntfs 的文件名索引 搜索速度很快
    freelancher
        27
    freelancher  
       2019-07-23 23:33:10 +08:00 via iPhone   ❤️ 1
    我是运维。第一步 find 太吃性能了。改成 ls 加参数列出路径 grep png 出来。觉得有用点个赞。明天继续写。
    iwishing
        28
    iwishing  
       2019-07-23 23:49:35 +08:00
    弄个数据库,sqlite 就行,便利一遍所有文件,把信息写入数据库。类似 everything 或者 windows search 给文件做索引。
    当然如果你只跑一次就没必要了
    iwishing
        29
    iwishing  
       2019-07-23 23:50:23 +08:00
    btw,建议用 python -_-b
    0x11901
        30
    0x11901  
    OP
       2019-07-24 00:30:35 +08:00
    @maxbon 老哥,可用!就是感觉这一行代码已经超越了我驾驭的范围了,我还想加几个参数和指令增加点功能……就很尴尬了 😂
    0x11901
        31
    0x11901  
    OP
       2019-07-24 00:33:12 +08:00
    @xderam 太菜了不会用 Windows,而且就是这门工作需要用 Windows,平时我也不打算使用 Windows

    @freelancher 给你点赞了

    @iwishing 一个小需求而已啊,我觉得不用这么复杂吧……而且 bash 不比 python 做这种事好太多么……
    widewing
        32
    widewing  
       2019-07-24 00:53:23 +08:00 via Android
    时间主要费在 file awk 起进程关进程上吧 同建议 python,没事别在循环里做起进程这么费时的事
    ReVanTis
        33
    ReVanTis  
       2019-07-24 09:18:05 +08:00 via Android
    @xderam
    想用 ntfs 特性,find 可以换成 everything 的命令行版,es
    maxbon
        34
    maxbon  
       2019-07-24 09:38:36 +08:00
    @0x11901 #30 你要做成一个常用脚本的话,建议拆开写成 function
    Chowe
        35
    Chowe  
       2019-07-24 10:12:15 +08:00
    list=`find ./ -name "*.png" `
    OLDIFS="$IFS"
    IFS="
    "
    for i in $list
    do
    var=`file $i | grep "$1"` #where $1 like 1080x1920,
    if [ -n "$var" ]
    then
    echo $i
    fi
    done
    IFS="$OLDIFS"
    0x11901
        36
    0x11901  
    OP
       2019-07-24 11:29:06 +08:00
    @maxbon 比如说我想加一个-f 参数实现模糊查找,让分辨率的范围+-5,我该怎么拆开这行命令啊?
    xderam
        37
    xderam  
       2019-07-24 14:48:58 +08:00 via iPhone
    @0x11901 用对方法 效率会有一个量级的提升的
    maxbon
        38
    maxbon  
       2019-07-24 15:07:00 +08:00
    @0x11901 #36 你可以把这个判断做成范围的,但是会牺牲性能 $(NF-2)==$1 && $NF==$2
    goodryb
        39
    goodryb  
       2019-07-24 16:45:33 +08:00
    建议另外搞个脚本,读取每个照片的路径和分辨率,写入到数据库中,比如 sqlite 中,脚本可以每天定时跑一下,把当天新增的文件过滤出来就行。

    然后查询方面,你想怎么查就怎么查,SQL 随便写。不然你没查一次就要解析一遍文件,io 消耗太大了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3075 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 13:24 · PVG 21:24 · LAX 05:24 · JFK 08:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.