golang - ls

Golang实现ls命令基本功能

v1 同步版本

package main

import (
"fmt"
"io/ioutil"
"log"
"math"
"os"
"strings"
"unicode/utf8"

"github.com/fatih/color"
"golang.org/x/crypto/ssh/terminal"
)

func main() {
// 读取当前目录所有文件/文件夹
files, err := ioutil.ReadDir(".")
if err != nil {
log.Fatal(err)
}
// 读取终端的宽度,后续许计算什么时候折行
width, _, err := terminal.GetSize(int(os.Stdin.Fd()))
if err != nil {
log.Fatal(err)
}
// 保持每一项最小间距一个空格
max := getMaxLen(files) + 1
// 每一行最多放多少个输出项
countsEachRow := math.Floor(float64(width) / max)
// 输出显示
printText(unHiddenFiles(files), int(max), int(countsEachRow))
}

// 获取所有文件总文件名字最长的长度,为了能够输出时对其
func getMaxLen(files []os.FileInfo) (length float64) {
length = 0
for _, file := range files {
// 计算的时候排除掉隐藏文件
if !strings.HasPrefix(file.Name(), ".") {
length = math.Max(float64(utf8.RuneCountInString(file.Name())), length)
}
}
return
}
func printText(files []os.FileInfo, max int, num int) {
// 对文件夹和可执行命令输出不同颜色
FgCyan := color.New(color.FgCyan).PrintfFunc()
red := color.New(color.FgRed).PrintfFunc()
for index, file := range files {
// 获取文件本身的长度
length := utf8.RuneCountInString(file.Name())
// 不足长度的用空格补齐
fileName := file.Name() + strings.Repeat(" ", max-length)
// 对文件夹处理
if file.IsDir() {
FgCyan(fileName)
// 每行的最后一项需要主动换行
if index%num == 0 {
fmt.Println()
}
continue
}
// 对可执行文件处理
if strings.Count(file.Mode().String(), "x") == 3 {
red(fileName)
if index%num == 0 {
fmt.Println()
}
continue
}
// 对普通文件处理
fmt.Printf(fileName)
if index%num == 0 {
fmt.Println()
}
}
}

// 过滤掉隐藏文件
func unHiddenFiles(files []os.FileInfo) (arr []os.FileInfo) {
arr = make([]os.FileInfo, 0)
for _, file := range files {
if !strings.HasPrefix(file.Name(), ".") {
arr = append(arr, file)
}
}
return
}

v2 并发执行

package main

import (
"fmt"
"io/ioutil"
"log"
"math"
"os"
"strings"
"sync"
"unicode/utf8"

"github.com/fatih/color"
"golang.org/x/crypto/ssh/terminal"
)

// ch中的文件结构
type fileModel struct {
name string
category int
}

func main() {
// 运送需要输出的内容的ch
names := make(chan fileModel)
files, err := ioutil.ReadDir(".")
if err != nil {
log.Fatal(err)
}
width, _, err := terminal.GetSize(int(os.Stdin.Fd()))
if err != nil {
log.Fatal(err)
}
max := getMaxLen(files) + 1
countsEachRow := math.Floor(float64(width) / max)
printText(files, int(max), names)
FgCyan := color.New(color.FgCyan).PrintfFunc()
red := color.New(color.FgRed).PrintfFunc()

// 因为是已知的长度,可以根据是否是每行最后一项和类型做不同处理
for index := 0; index < len(files); index++ {
name := <-names
switch category := name.category; category {
case 0:
// 文件夹处理
FgCyan(name.name)
case 1:
// 可执行文件处理
red(name.name)
default:
// 普通文件处理
fmt.Print(name.name)
}
// 是否主动换行判断
if index%int(countsEachRow) == 0 {
fmt.Println()
}
}
}

func getMaxLen(files []os.FileInfo) (length float64) {
length = 0
for _, file := range files {
if !strings.HasPrefix(file.Name(), ".") {
length = math.Max(float64(utf8.RuneCountInString(file.Name())), length)
}
}
return
}
func printText(files []os.FileInfo, max int, names chan<- fileModel) {
// 用wg让所有goroutines结束后再让main goroutine结束
var wg sync.WaitGroup
for _, file := range files {
wg.Add(1)
go func(file os.FileInfo) {
defer wg.Done()
if !strings.HasPrefix(file.Name(), ".") {
length := utf8.RuneCountInString(file.Name())
fileName := file.Name() + strings.Repeat(" ", max-length)
if file.IsDir() {
// 传入到ch
names <- fileModel{fileName, 0}
return
}

if strings.Count(file.Mode().String(), "x") == 3 {
// 传入到ch
names <- fileModel{fileName, 1}
return
}
// 传入到ch
names <- fileModel{fileName, 2}
}
}(file)
}

go func() {
wg.Wait()
// 所有goroutines结束后结束ch
close(names)
}()
}