Golang實(shí)踐指南之獲取目錄文件列表
前言
獲取目錄下匹配某種規(guī)則的文件,返回文件列表,在開發(fā)中比較常用。本文實(shí)現(xiàn)此功能,并做了些擴(kuò)展。
起因
筆者開發(fā)的內(nèi)部工具,需要查找各式文件,比如:
- 數(shù)據(jù)文件,以csv結(jié)尾;
- 信息文件,以md結(jié)尾(因?yàn)橐延泄ぞ呱刹秩镜胔tml發(fā)布到網(wǎng)頁上,直接拿來使用);
- 壓縮包,以zip結(jié)尾。
由于設(shè)計(jì)原因,部分不同各類的文件會(huì)在同一目錄出現(xiàn),因此,要實(shí)現(xiàn)一個(gè)接口用以獲取某個(gè)目錄下符合條件的文件。
設(shè)計(jì)思路如下:
- 為簡單化,不遞歸查找子目錄,當(dāng)傳入的非目錄或不存在時(shí),直接返回即可。
- 設(shè)計(jì)是否帶目錄標(biāo)志,有時(shí)僅需文件名稱,而不需要其目錄。
- 設(shè)計(jì)返回文件列表數(shù)量,0表示所有文件,因此有時(shí)僅需要得到符合要求的第一個(gè)文件。
- 設(shè)計(jì)是否排序標(biāo)志,因?yàn)槟J(rèn)是升序,所以該標(biāo)志實(shí)際是降序標(biāo)志。
實(shí)現(xiàn)
為減少篇幅,僅摘錄必要的源碼。
根據(jù)匹配字符串(為描述方便,稱之為patten)的位置,需實(shí)現(xiàn)如下接口:
- GetFileListBySuffix:patten 作為后綴,可按后綴名匹配。通過strings.HasSuffix判斷。
- GetFileListByPrefix:同前,但是是前綴。通過strings.HasSuffix判斷。
- GetFileListByKey:同上,在任意位置出現(xiàn)均可。通過strings.HasPrefix判斷。
另外實(shí)現(xiàn)os.FileInfo三個(gè)接口,達(dá)到降序排序目的。通過strings.Contains判斷。
封裝
代碼如下:
// 按文件名排序,可擴(kuò)展至文件時(shí)間
type byName []os.FileInfo
//func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } // 文件名升序,默認(rèn)方式
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
}
// 根據(jù)關(guān)鍵字查找
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
}
測試及結(jié)果
測試用到的目錄及文件如下:
$ 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.30 directories, 10 files
前綴匹配調(diào)用:
list, err1 := GetFileListByPrefix("./foo", "log", true, false, 0)
fmt.Println(list, err1)
結(jié)果:
[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3] <nil>
后綴匹配調(diào)用:
list, err1 = GetFileListBySuffix("./foo", "json", true, false, 0)
fmt.Println(list, err1)
結(jié)果:
[foo\bar_1.json foo\bar_10.json foo\bar_2.json foo\bar_20.json foo\bar_3.json] <nil>
一般匹配調(diào)用:
list, err1 = GetFileListByKey("./foo", "1", true, false, 0)
fmt.Println(list, err1)
結(jié)果:
[foo\bar_1.json foo\bar_10.json foo\log.1 foo\log.10] <nil>
從結(jié)果分析,符合預(yù)期。
擴(kuò)展
優(yōu)化接口
從前面封裝的接口代碼分析,絕大部分代碼是相同的,僅僅是讀取后對文件名稱的判斷不同而已。因此,可以將它們合成一個(gè)接口。設(shè)計(jì)如下:
patten額外添加*,通過*位置匹配前綴(foo*)、后綴(*foo)、中間(*foo*,即包含該關(guān)鍵字)。- 在內(nèi)部判斷并去掉
*。其余機(jī)制同前。
代碼如下:
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")
}
// 默認(rèn)為中間
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 { // 如指定數(shù)量多于實(shí)際的,則使用實(shí)際
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
調(diào)用如下:
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)
結(jié)果如下:
[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>
正則表達(dá)式
雖然目前未使用到,但還是實(shí)現(xiàn)了按正則表達(dá)式來匹配文件名的接口,實(shí)現(xiàn)如下:
// 根據(jù)正規(guī)正則表達(dá)式查找
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 { // 如指定數(shù)量多于實(shí)際的,則使用實(shí)際
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
}
調(diào)用如下:
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)
結(jié)果如下:
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>
說明:正則表達(dá)式一定要符合要求,否則不識別。
帶數(shù)字的文件名稱排序
前面的測試結(jié)果:
[foo\log.1 foo\log.10 foo\log.2 foo\log.20 foo\log.3]
可以看到,文件的排序不是我們想象中那樣的順序,實(shí)際上,從字符串比較(因?yàn)榇a就是使用這個(gè)方式)的角度看,上述排序是正常的。如要達(dá)到想象的順序,則要修改比較方式,重新實(shí)現(xiàn)os.FileInfo中的Less接口。
因?yàn)閹?shù)字的文件名稱有各式各樣,此處假定按文件的“后綴名”數(shù)字由大到小排序。實(shí)際上,這是真實(shí)的的場合:某工程程序產(chǎn)生的日志,按超過指定大小會(huì)自動(dòng)創(chuàng)建新的文件,后綴帶數(shù)字即表達(dá)有多少個(gè)日志文件。
代碼如下:
// 按帶數(shù)字的文件名排序
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()
// !! 根據(jù)需求,文件最后是數(shù)字,按其值降序排序,示例: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:])
// 整體文件(不含后綴名)名稱排序,名稱是數(shù)字,如1.txt 2.txt 10.txt
// a, err1 := strconv.Atoi(pathA[0:strings.LastIndex(pathA, ".")])
// b, err2 := strconv.Atoi(pathB[0:strings.LastIndex(pathB, ".")])
// 有錯(cuò)誤,默認(rèn)降序
if err1 != nil || err2 != nil {
return pathA > pathB
}
// 按數(shù)字降序
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
}調(diào)用如下:
list, err1 = getFileListByPrefix("./foo/", `log`, true, 0)
fmt.Println(`sort number : `, list, err1)
結(jié)果如下:
sort number : [foo\log.20 foo\log.10 foo\log.3 foo\log.2 foo\log.1] <nil>
從結(jié)果看,已實(shí)現(xiàn)根據(jù)數(shù)字從大到小排序。
總結(jié)
文中所實(shí)現(xiàn)的接口,已應(yīng)用到筆者實(shí)際的工程。目前未發(fā)現(xiàn)有什么問題。
隔了一天的PS:
由于傳遞參數(shù)可以指定文件數(shù)量,當(dāng)指定的數(shù)量多了實(shí)際數(shù)量時(shí),會(huì)報(bào)錯(cuò),需要加上判斷。在“優(yōu)化接口”及后面小節(jié)的代碼中已修正,前面由于實(shí)際中不使用,故保留。
有時(shí)話真的不是說得太滿。記之并以此為鑒。
到此這篇關(guān)于Golang實(shí)踐指南之獲取目錄文件列表的文章就介紹到這了,更多相關(guān)Golang獲取目錄文件列表內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言中的自定義函數(shù)類型的實(shí)現(xiàn)
在Go語言中,函數(shù)類型是一種將函數(shù)作為值的數(shù)據(jù)類型,本文主要介紹了Go語言中的自定義函數(shù)類型,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
5個(gè)可以在Golang中優(yōu)化代碼以提高性能的技巧分享
作為一名軟件工程師,確保你的代碼高效且性能良好是非常重要的。本文主要和大家分享5個(gè)可以在Golang中優(yōu)化代碼以提高性能的技巧,希望對大家有所幫助2023-03-03
Go語言for range(按照鍵值循環(huán))遍歷操作
這篇文章主要介紹了Go語言for range(按照鍵值循環(huán))遍歷操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Golang連接PostgreSQL基本操作的實(shí)現(xiàn)
PostgreSQL是常見的免費(fèi)的大型關(guān)系型數(shù)據(jù)庫,本文主要介紹了Golang連接PostgreSQL基本操作的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02

