文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Golang实践指南之获取目录文件列表

2023-01-07 15:00

关注

前言

获取目录下匹配某种规则的文件,返回文件列表,在开发中比较常用。本文实现此功能,并做了些扩展。

起因

笔者开发的内部工具,需要查找各式文件,比如:

由于设计原因,部分不同各类的文件会在同一目录出现,因此,要实现一个接口用以获取某个目录下符合条件的文件。

设计思路如下:

实现

为减少篇幅,仅摘录必要的源码。

根据匹配字符串(为描述方便,称之为patten)的位置,需实现如下接口:

另外实现os.FileInfo三个接口,达到降序排序目的。通过strings.Contains判断。

封装

代码如下:

// 按文件名排序,可扩展至文件时间
type byName []os.FileInfo

//func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } // 文件名升序,默认方式
func (f byName) Less(i, j int) bool { return f[i].Name() > f[j].Name() } // 文件名倒序
func (f byName) Len() int           { return len(f) }
func (f byName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }


// GetFileListBySuffix returns an ordered list of file paths.
// It recognize if given path is a file, and don't do recursive find.
func GetFileListBySuffix(dirPath, suffix string, needDir bool, isDescend bool, num int) ([]string, error) {
    if !IsExist(dirPath) {
        return nil, fmt.Errorf("given path does not exist: %s", dirPath)
    } else if IsFile(dirPath) {
        return []string{dirPath}, nil
    }

    // Given path is a directory.
    dir, err := os.Open(dirPath)
    if err != nil {
        return nil, err
    }

    fis, err := dir.Readdir(0)
    if err != nil {
        return nil, err
    }

    if isDescend {
        sort.Sort(byName(fis))
    }

    if num == 0 {
        num = len(fis)
    }
    files := make([]string, 0, num)
    for i := 0; i < num; i++ {
        fi := fis[i]
        if strings.HasSuffix(fi.Name(), suffix) {
            if needDir {
                files = append(files, filepath.Join(dirPath, fi.Name()))
            } else {
                files = append(files, fi.Name())
            }
        }
    }

    return files, nil
}

// as GetFileListBySuffix, but for Prefix
func GetFileListByPrefix(dirPath, suffix string, needDir bool, isDescend bool, num int) ([]string, error) {
    if !IsExist(dirPath) {
        return nil, fmt.Errorf("given path does not exist: %s", dirPath)
    } else if IsFile(dirPath) {
        return []string{dirPath}, nil
    }

    // Given path is a directory.
    dir, err := os.Open(dirPath)
    if err != nil {
        return nil, err
    }

    fis, err := dir.Readdir(0)
    if err != nil {
        return nil, err
    }

    if isDescend {
        sort.Sort(byName(fis))
    }

    if num == 0 {
        num = len(fis)
    }
    files := make([]string, 0, num)
    for i := 0; i < num; i++ {
        fi := fis[i]
        if strings.HasPrefix(fi.Name(), suffix) {
            if needDir {
                files = append(files, filepath.Join(dirPath, fi.Name()))
            } else {
                files = append(files, fi.Name())
            }
        }
    }

    return files, nil
}

// 根据关键字查找
func GetFileListByKey(dirPath, key string, needDir bool, isDescend bool, num int) ([]string, error) {
    if !IsExist(dirPath) {
        return nil, fmt.Errorf("given path does not exist: %s", dirPath)
    } else if IsFile(dirPath) {
        return []string{dirPath}, nil
    }

    // Given path is a directory.
    dir, err := os.Open(dirPath)
    if err != nil {
        return nil, err
    }

    fis, err := dir.Readdir(0)
    if err != nil {
        return nil, err
    }

    if isDescend {
        sort.Sort(byName(fis))
    }

    if num == 0 {
        num = len(fis)
    }
    files := make([]string, 0, num)
    for i := 0; i < num; i++ {
        fi := fis[i]
        if strings.Contains(fi.Name(), key) {
            if needDir {
                files = append(files, filepath.Join(dirPath, fi.Name()))
            } else {
                files = append(files, fi.Name())
            }
        }
    }

    return files, nil
}

测试及结果

测试用到的目录及文件如下:

$ tree foo/
foo/
|-- bar_1.json
|-- bar_10.json
|-- bar_2.json
|-- bar_20.json
|-- bar_3.json
|-- log.1
|-- log.10
|-- log.2
|-- log.20
`-- log.3

0 directories, 10 files

前缀匹配调用:

list, err1 := GetFileListByPrefix("./foo", "log", true, false, 0)
fmt.Println(list, err1)

结果:

[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>

后缀匹配调用:

list, err1 = GetFileListBySuffix("./foo", "json", true, false, 0)
fmt.Println(list, err1)

结果:

[foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>

一般匹配调用:

list, err1 = GetFileListByKey("./foo", "1", true, false, 0)
fmt.Println(list, err1)

结果:

[foo\bar_1.json foo\bar_10.json foo\log.1 foo\log.10] <nil>

从结果分析,符合预期。

扩展

优化接口

从前面封装的接口代码分析,绝大部分代码是相同的,仅仅是读取后对文件名称的判断不同而已。因此,可以将它们合成一个接口。设计如下:

代码如下:

func GetFileListByKey_new(dirPath, patten string, needDir bool, isDescend bool, num int) ([]string, error) {
    if !IsExist(dirPath) {
        return nil, fmt.Errorf("given path does not exist: %s", dirPath)
    } else if IsFile(dirPath) { // 如果是文件,返回该文件
        return []string{dirPath}, nil
    }

    if len(patten) < 2 {
        return nil, fmt.Errorf("given patten len < 2")
    }

    // 默认为中间
    pos := 3
    rpatten := patten

    if patten[0] == '*' && patten[len(patten)-1] == '*' { // 一般
        rpatten = patten[1 : len(patten)-1]
        pos = 3
    } else if patten[0] == '*' { // 后缀
        pos = 1
        rpatten = patten[1:]
    } else if patten[len(patten)-1] == '*' { // 前缀
        rpatten = patten[:len(patten)-1]
        pos = 2
    }

    // Given path is a directory.
    dir, err := os.Open(dirPath)
    if err != nil {
        return nil, err
    }

    fis, err := dir.Readdir(0)
    if err != nil {
        return nil, err
    }

    fnum := len(fis)
    if fnum == 0 {
        return []string{}, nil
    }

    if isDescend {
        sort.Sort(byName(fis))
    }

    if num == 0 {
        num = fnum
    } else {
        if num > fnum {  // 如指定数量多于实际的,则使用实际
            num = fnum
        }
    }
    files := make([]string, 0, num)

    checkFile := func(filename string) bool {
        if pos == 1 {
            return strings.HasSuffix(filename, rpatten)
        } else if pos == 2 {
            return strings.HasPrefix(filename, rpatten)
        } else if pos == 3 {
            return strings.Contains(filename, rpatten)
        }
        return true
    }
    for i := 0; i < num; i++ {
        fi := fis[i]
        if checkFile(fi.Name()) {
            if needDir {
                files = append(files, filepath.Join(dirPath, fi.Name()))
            } else {
                files = append(files, fi.Name())
            }
        }
    }

    return files, nil

调用如下:

list, err1 = GetFileListByKey_new("./foo", "log*", true, false, 0)
fmt.Println(list, err1)
list, err1 = GetFileListByKey_new("./foo", "*json", true, false, 0)
fmt.Println(list, err1)
list, err1 = GetFileListByKey_new("./foo/", "*1*", true, false, 0)
fmt.Println(list, err1)

结果如下:

[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>

[foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>

[foo\bar_1.json foo\bar_10.json foo\log.1 foo\log.10] <nil>

正则表达式

虽然目前未使用到,但还是实现了按正则表达式来匹配文件名的接口,实现如下:

// 根据正规正则表达式查找
func GetFileListByPatten(dirPath, patten string, needDir bool, isDescend bool, num int) ([]string, error) {
    if !IsExist(dirPath) {
        return nil, fmt.Errorf("given path does not exist: %s", dirPath)
    } else if IsFile(dirPath) {
        return []string{dirPath}, nil
    }

    // Given path is a directory.
    dir, err := os.Open(dirPath)
    if err != nil {
        return nil, err
    }

    fis, err := dir.Readdir(0)
    if err != nil {
        return nil, err
    }

    fnum := len(fis)
    if fnum == 0 {
        return []string{}, nil
    }

    if isDescend {
        sort.Sort(byName(fis))
    }

    if num == 0 {
        num = fnum
    } else {
        if num > fnum { // 如指定数量多于实际的,则使用实际
            num = fnum
        }
    }

    match := func(str string, patten string) bool {
        rep := regexp.MustCompile(patten)
        ret := rep.MatchString(str)
        return ret
    }

    files := make([]string, 0, num)
    for i := 0; i < num; i++ {
        fi := fis[i]
        if match(fi.Name(), patten) {
            if needDir {
                files = append(files, filepath.Join(dirPath, fi.Name()))
            } else {
                files = append(files, fi.Name())
            }
        }
    }

    return files, nil
}

调用如下:

list, err1 = GetFileListByPatten("./foo", `log.+`, true, false, 0)
fmt.Println(`log.+ : `, list, err1)
list, err1 = GetFileListByPatten("./foo/", `[a-z0-9_]*.json`, true, false, 0)
fmt.Println(`[a-z0-9_]*.json : `, list, err1)

结果如下:

log.+ :  [foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>
[a-z0-9_]*.json :  [foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>

说明:正则表达式一定要符合要求,否则不识别。

带数字的文件名称排序

前面的测试结果:

[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3]

可以看到,文件的排序不是我们想象中那样的顺序,实际上,从字符串比较(因为代码就是使用这个方式)的角度看,上述排序是正常的。如要达到想象的顺序,则要修改比较方式,重新实现os.FileInfo中的Less接口。

因为带数字的文件名称有各式各样,此处假定按文件的“后缀名”数字由大到小排序。实际上,这是真实的的场合:某工程程序产生的日志,按超过指定大小会自动创建新的文件,后缀带数字即表达有多少个日志文件。

代码如下:

// 按带数字的文件名排序
type byNumericalFilename []os.FileInfo

func (f byNumericalFilename) Len() int      { return len(f) }
func (f byNumericalFilename) Swap(i, j int) { f[i], f[j] = f[j], f[i] }

func (f byNumericalFilename) Less(i, j int) bool {
    pathA := f[i].Name()
    pathB := f[j].Name()

    // !! 根据需求,文件最后是数字,按其值降序排序,示例:ddd.log.x.1 ddd.log.x.2
    // 如有其它者,也可以修改
    a, err1 := strconv.Atoi(pathA[strings.LastIndex(pathA, ".")+1:])
    b, err2 := strconv.Atoi(pathB[strings.LastIndex(pathB, ".")+1:])

    // 整体文件(不含后缀名)名称排序,名称是数字,如1.txt 2.txt 10.txt
    // a, err1 := strconv.Atoi(pathA[0:strings.LastIndex(pathA, ".")])
    // b, err2 := strconv.Atoi(pathB[0:strings.LastIndex(pathB, ".")])

    // 有错误,默认降序
    if err1 != nil || err2 != nil {
        return pathA > pathB
    }

    // 按数字降序
    return a > b
}


func getFileListByPrefix(dirPath, suffix string, needDir bool, num int) ([]string, error) {
    if !isExist(dirPath) {
        return nil, fmt.Errorf("given path does not exist: %s", dirPath)
    } else if isFile(dirPath) {
        return []string{dirPath}, nil
    }

    // Given path is a directory.
    dir, err := os.Open(dirPath)
    if err != nil {
        return nil, err
    }

    fis, err := dir.Readdir(0)
    if err != nil {
        return nil, err
    }

    fnum := len(fis)
    if fnum == 0 {
        return []string{}, nil
    }

    sort.Sort(byNumericalFilename(fis))

    if num == 0 {
        num = fnum
    } else {
        if num > fnum {
            num = fnum
        }
    }
    files := make([]string, 0, num)
    for i := 0; i < num; i++ {
        fi := fis[i]
        if strings.HasPrefix(fi.Name(), suffix) {
            if needDir {
                files = append(files, filepath.Join(dirPath, fi.Name()))
            } else {
                files = append(files, fi.Name())
            }
        }
    }

    return files, nil
}

调用如下:

list, err1 = getFileListByPrefix("./foo/", `log`, true, 0)
fmt.Println(`sort number : `, list, err1)

结果如下:

sort number :  [foo\log.20 foo\log.10 foo\log.3 foo\log.2 foo\log.1] <nil>

从结果看,已实现根据数字从大到小排序。

总结

文中所实现的接口,已应用到笔者实际的工程。目前未发现有什么问题。

隔了一天的PS:

由于传递参数可以指定文件数量,当指定的数量多了实际数量时,会报错,需要加上判断。在“优化接口”及后面小节的代码中已修正,前面由于实际中不使用,故保留。

有时话真的不是说得太满。记之并以此为鉴。

到此这篇关于Golang实践指南之获取目录文件列表的文章就介绍到这了,更多相关Golang获取目录文件列表内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯