用Go语言重写Linux系统命令 -- ls

用Go语言重写Linux系统命令 – ls

1. 引言

1.1 为什么要用Go重写ls

如果你曾被ls命令的输出迷住过,或只是单纯想挑战下自己,那么用Go语言重写它无疑是一次有趣的尝试。这不仅能帮助你理解文件系统和系统调用,还能让你在Go语言的实践中如虎添翼。

1.2 实现目标

本文的目标是使用Go语言重写ls命令,并实现以下功能:

  • 列出目录中的文件和子目录。
  • 支持-a选项,显示隐藏文件。
  • 支持-l选项,长格式显示文件详细信息。
  • 支持-h选项,以人类可读的格式显示文件大小。

1.3 完整代码

package main/*
#include <grp.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
*/
import "C"
import ("fmt""os""os/user""path/filepath""syscall""github.com/spf13/pflag"
)func main() {showAll := pflag.BoolP("all", "a", false, "显示所有文件,包括隐藏文件")longFormat := pflag.BoolP("format", "l", false, "长列表格式显示详细信息")humanReadable := pflag.BoolP("human-readable", "h", false, "以更易读的方式显示文件大小")// 解析命令行参数pflag.Parse()// 如果没有参数,则默认为当前目录name := "."if pflag.NArg() > 0 {name = pflag.Arg(0)}// 如果是文件, 则打印详细信息if info, err := os.Stat(name); err == nil && info.Mode().IsRegular() {printFileInfo(info.Name(), "", *humanReadable)return}// 如果是目录, 则打印目录中的文件列表listFiles(name, *showAll, *longFormat, *humanReadable)
}// 列出目录中的文件
func listFiles(dir string, showAll, longFormat, humanReadable bool) {files, err := os.ReadDir(dir)if err != nil {fmt.Printf("Error reading directory: %v\n", err)return}for _, file := range files {if !showAll && file.Name()[0] == '.' {continue}if longFormat {printFileInfo(file.Name(), dir, humanReadable)} else {fmt.Println(file.Name())}}
}// 打印详细文件信息
func printFileInfo(fileName, dir string, humanReadable bool) {fileFullPath := filepath.Join(dir, fileName)info, err := os.Stat(fileFullPath)if err != nil {fmt.Printf("Error getting file info: %v\n", err)return}stat, ok := info.Sys().(*syscall.Stat_t)if !ok {fmt.Printf("Error asserting to syscall.Stat_t\n")return}size := info.Size()sizeStr := fmt.Sprintf("%d", size)if humanReadable {sizeStr = humanReadableSize(size)}fmt.Printf("%-10s %-1d %-1s %-1s %10s %s %s\n",info.Mode().String(),   // 文件权限stat.Nlink,             // 硬链接数getUserName(stat.Uid),  // 拥有者getGroupName(stat.Gid), // 组sizeStr,                // 文件大小info.ModTime().Format("2006-01-02 15:04:05"), // 修改时间filepath.Base((fileFullPath)),                // 文件名)
}// 获取组名
func getGroupName(gid uint32) string {grp := C.getgrgid(C.gid_t(gid))if grp == nil {return fmt.Sprint(gid)}return C.GoString(grp.gr_name)
}// 获取用户名
func getUserName(uid uint32) string {userObj, err := user.LookupId(fmt.Sprint(uid))if err != nil {return fmt.Sprint(uid)}return userObj.Username
}// 将字节转换为人类可读的格式
func humanReadableSize(size int64) string {const (KB = 1024MB = KB * 1024GB = MB * 1024)switch {case size >= GB:return fmt.Sprintf("%.2fG", float64(size)/GB)case size >= MB:return fmt.Sprintf("%.2fM", float64(size)/MB)case size >= KB:return fmt.Sprintf("%.2fK", float64(size)/KB)default:return fmt.Sprintf("%d", size)}
}

2. 准备工作

2.1 环境配置

在开始之前,请确保你的环境已配置好:

  1. 安装Go语言:从Go官网下载并安装。
  2. 安装pflag库:我们使用pflag库来解析命令行参数。执行以下命令安装它:
    go get github.com/spf13/pflag
    

2.2 必备知识

  • 文件系统基础:理解文件、目录、权限等概念。
  • Go语言基础:熟悉ossyscallpflag等包的使用。

3. 项目结构与基础实现

3.1 初始化项目

创建一个新目录,并初始化Go模块:

mkdir gols && cd gols
go mod init gols

3.2 基础功能代码解析

我们从最简单的功能开始:列出目录中的文件。

package mainimport ("fmt""os"
)func main() {dir := "."files, err := os.ReadDir(dir)if err != nil {fmt.Printf("Error reading directory: %v\n", err)return}for _, file := range files {fmt.Println(file.Name())}
}

4. 选项支持与功能增强

4.1 支持-a选项:显示隐藏文件

我们使用pflag来解析命令行参数。

import "github.com/spf13/pflag"func main() {showAll := pflag.BoolP("all", "a", false, "显示所有文件,包括隐藏文件")pflag.Parse()dir := "."files, _ := os.ReadDir(dir)for _, file := range files {if !*showAll && file.Name()[0] == '.' {continue}fmt.Println(file.Name())}
}

4.2 支持-l选项:长格式输出

为了显示详细信息,我们需要调用os.Stat获取文件的元信息。

func printFileInfo(fileName, dir string) {info, _ := os.Stat(filepath.Join(dir, fileName))fmt.Printf("%-10s %10d %s\n", info.Mode().String(), info.Size(), info.ModTime())
}

4.3 支持-h选项:人类可读的文件大小

func humanReadableSize(size int64) string {const (KB = 1024MB = KB * 1024GB = MB * 1024)switch {case size >= GB:return fmt.Sprintf("%.2fG", float64(size)/GB)case size >= MB:return fmt.Sprintf("%.2fM", float64(size)/MB)case size >= KB:return fmt.Sprintf("%.2fK", float64(size)/KB)default:return fmt.Sprintf("%dB", size)}
}

5. 代码深度解读

5.1 用户名与组名解析

在Unix系统中,每个文件都有一个所有者(用户)所属组。为了显示文件的所有者和组名,我们需要根据文件的UID和GID解析用户名和组名。以下是具体实现与代码解释。

用户名解析
我们使用Go标准库中的user包,通过用户ID(UID)查找用户名:

func getUserName(uid uint32) string {userObj, err := user.LookupId(fmt.Sprint(uid))if err != nil {return fmt.Sprint(uid)}return userObj.Username
}

代码解释:

  • user.LookupId(fmt.Sprint(uid))

    • 将传入的uid转换为字符串形式,因为LookupId函数接受字符串类型的用户ID。
    • 调用LookupId查询系统用户信息,返回一个*user.User对象。
  • 错误处理

    • 如果查询失败(例如找不到对应的用户),直接返回uid的字符串形式,确保程序不会因为错误中断。
  • 返回用户名

    • 如果查询成功,返回userObj.Username,即用户的登录名。

组名解析
组名解析通过CGO调用C标准库中的getgrgid函数完成。CGO允许Go代码与C语言代码交互,提供更底层的系统功能访问。

/*
#include <grp.h>
#include <sys/types.h>
#include <unistd.h>
*/
import "C"func getGroupName(gid uint32) string {grp := C.getgrgid(C.gid_t(gid))if grp == nil {return fmt.Sprint(gid)}return C.GoString(grp.gr_name)
}

代码解释:

  • C头文件包含

    • #include <grp.h>:提供getgrgid函数,用于获取组信息。
    • #include <sys/types.h>#include <unistd.h>:定义了系统调用所需的基本数据类型。
  • C.getgrgid(C.gid_t(gid))

    • 将Go中的gid转换为C语言中的gid_t类型。
    • 调用getgrgid函数,根据组ID(GID)获取struct group结构的指针。
    • 如果返回nil,说明没有找到对应的组,直接返回GID的字符串形式。
  • C.GoString(grp.gr_name)

    • 将C语言的字符串grp->gr_name转换为Go语言的string类型,并返回组名。

5.2 文件权限与元信息解析

我们从syscall.Stat_t中提取权限和硬链接数:

import "syscall"func printFileInfo(fileName, dir string, humanReadable bool) {info, _ := os.Stat(filepath.Join(dir, fileName))// 调用 Sys() 方法并进行类型断言stat, ok := info.Sys().(*syscall.Stat_t)fmt.Printf("%s %d %s %s\n", info.Mode().String(), stat.Nlink, getUserName(stat.Uid), getGroupName(stat.Gid))
}
  • os.Stat()会返回一个os.FileInfo接口, os.FileInfo 接口的 Sys() 方法返回一个底层数据源相关的任意类型。对于 Unix 系统(包括 Linux 和 macOS),这个方法通常返回一个指向 syscall.Stat_t 结构体的指针,该结构体包含了文件系统调用 stat 的原始结果。
  • syscall.Stat_tsyscall 包中的一个结构体,它直接映射到 C 语言中的struct stat结构,用于存储文件或文件系统的状态信息。当你需要访问比 os.FileInfo 提供的更详细的文件系统信息时,可以使用这种方式。
  • .(*syscall.Stat_t) 是一个类型断言,它将 info.Sys() 返回的接口值转换为 *syscall.Stat_t 类型。

5.3 格式化时间字符串

	fmt.Printf("%-10s %-1d %-1s %-1s %10s %s %s\n",info.Mode().String(),   // 文件权限stat.Nlink,             // 硬链接数getUserName(stat.Uid),  // 拥有者getGroupName(stat.Gid), // 组sizeStr,                // 文件大小info.ModTime().Format("2006-01-02 15:04:05"), // 修改时间filepath.Base((fileFullPath)),                // 文件名)
  • info.ModTime() 返回文件的最后修改时间,Format("2006-01-02 15:04:05") 将时间格式化为指定的字符串格式。

  • info.ModTime()

    • 这是 os.FileInfo 接口的方法,返回一个 time.Time 类型,表示文件的最后修改时间。
    • 示例:2024-12-01 14:23:45 +0800 CST
  • Format

    • Formattime.Time 类型的方法,用于将时间转换为指定格式的字符串。
  • Go 语言使用特定的时间模板来格式化时间,模板必须使用固定时间点**“2006-01-02 15:04:05”**。这个时间点对应如下含义:

    • 2006:年
    • 01:月(两位数)
    • 02:日(两位数)
    • 15:小时(24小时制)
    • 04:分钟
    • 05:秒
  • 这个固定时间点代表的格式是“yyyy-MM-dd HH:mm:ss”。

示例

假设文件的修改时间是 2024年12月01日 下午3:30:45

modTime := info.ModTime()
fmt.Println(modTime.Format("2006-01-02 15:04:05"))

输出:

2024-12-01 15:30:45

小技巧:常见时间格式化模板

格式字符串输出示例含义
2006-01-022024-12-01日期格式:年-月-日
15:04:0515:30:45时间格式:时:分:秒(24小时制)
2006-01-02 03:04:05 PM2024-12-01 03:30:45 PM日期和时间,12小时制
02 Jan 2006 15:0401 Dec 2024 15:30英文日期格式

6. 打包与发布

6.1 交叉编译与打包

编译成可执行文件并分发到Linux系统:

CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -o gols ./
  • 启用 CGO,支持调用 C 代码。
  • 交叉编译,目标平台为 Linux,架构为 AMD64。
  • 强制重新编译所有包,并生成一个 静态链接 的二进制文件。
  • 输出文件名为 gols,源码目录为当前目录。

7. 总结与扩展

7.1 实现回顾

在本项目中,我们重写了 Linux 系统的经典命令 ls,不仅实现了基本的文件列表功能,还支持了以下特性:

  • 显示所有文件(包括隐藏文件)。
  • 长格式显示,包含详细的文件信息,如权限、所有者、组、大小等。
  • 人类可读的文件大小,方便用户理解。
  • 用户名与组名解析,结合了 Go 和 C 的功能,体现了 CGO 在系统编程中的优势。

通过这个过程,我们不仅掌握了 ls 命令的核心逻辑,还深入了解了 Go 语言在系统编程中的强大之处,包括如何使用 syscall 获取底层信息、如何调用 C 语言库,以及如何进行静态链接,生成跨平台的二进制文件。


7.2 扩展功能建议

1. 添加更多选项
  • 排序功能:支持按名称、大小、修改时间等排序。
    • 示例:ls -t 按修改时间排序,ls -S 按文件大小排序。
  • 颜色输出:为不同类型的文件(目录、可执行文件、符号链接等)添加颜色。
    • 可借助 ANSI 转义序列,为终端输出设置不同的颜色。
    • 示例:目录显示为蓝色,可执行文件显示为绿色。
  • 递归列出文件:类似于 ls -R,可以递归显示子目录中的文件。
2. 支持多平台输出
  • 添加对 Windows 平台的支持,兼容不同平台的系统调用。
  • 可以借助 Go 的 build tags,根据平台条件编译特定代码。
3. 与系统调用的深度结合
  • 使用 Go 的 syscallgolang.org/x/sys 包来操作文件描述符、获取更底层的文件信息。
  • 探索如何用 Go 实现 inotify 等 Linux 特性,监控文件和目录的变化。
4. 输出格式扩展
  • JSON 输出:添加选项,将文件信息输出为 JSON 格式,方便与其他工具集成。
    • 示例:ls --json 输出类似 { "name": "file.txt", "size": 1024, "owner": "user" }
5. 性能优化
  • 使用 Goroutine 并行获取文件信息,提升在大目录下的执行效率。
  • 优化内存分配,避免不必要的临时变量,减少垃圾回收开销。

7.3 总结

通过这个项目,我们不仅掌握了 Go 语言的基本语法和系统编程技巧,还探索了如何用 Go 高效地操控文件系统。未来可以继续深入优化和扩展功能,将这个工具变得更加实用和强大。Go 语言的强大生态和简单优雅的语法,足以胜任各种复杂的系统工具开发任务!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/483132.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ONE·基础算法 || 动态规划(三)】

总言 主要内容&#xff1a;编程题举例&#xff0c;熟悉理解动态规划类题型&#xff08;回文串问题、两个数组的 dp问题&#xff09;。                文章目录 总言7、回文串问题7.1、 回文子串&#xff08;medium&#xff09;7.1.1、题解 7.2、 最长回文子串&#…

Python 3 教程第33篇(MySQL - mysql-connector 驱动)

Python MySQL - mysql-connector 驱动 MySQL 是最流行的关系型数据库管理系统&#xff0c;如果你不熟悉 MySQL&#xff0c;可以阅读我们的 MySQL 教程。 本章节我们为大家介绍使用 mysql-connector 来连接使用 MySQL&#xff0c; mysql-connector 是 MySQL 官方提供的驱动器。…

LLM*:路径规划的大型语言模型增强增量启发式搜索

路径规划是机器人技术和自主导航中的一个基本科学问题&#xff0c;需要从起点到目的地推导出有效的路线&#xff0c;同时避开障碍物。A* 及其变体等传统算法能够确保路径有效性&#xff0c;但随着状态空间的增长&#xff0c;计算和内存效率会严重降低。相反&#xff0c;大型语言…

【Db First】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

企业品牌曝光的新策略:短视频矩阵系统

企业品牌曝光的新策略&#xff1a;短视频矩阵系统 在当今数字化时代&#xff0c;短视频已经渗透到我们的日常生活之中&#xff0c;成为连接品牌与消费者的关键渠道。然而&#xff0c;随着平台于7月20日全面下线了短视频矩阵的官方接口&#xff0c;许多依赖于此接口的小公司和内…

006 MATLAB编程基础

01 M文件 MATLAB输入命令有两种方法&#xff1a; 一是在MATLAB主窗口逐行输入命令&#xff0c;每个命令之间用分号或逗号分隔&#xff0c;每行可包含多个命令。 二是将命令组织成一个命令语句文集&#xff0c;使用扩展名“.m”&#xff0c;称为M文件。它由一系列的命令和语句…

Java基于SpringBoot+Vue的IT技术交流和分享平台(附源码+lw+部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【计算机网络】实验3:集线器和交换器的区别及交换器的自学习算法

实验 3&#xff1a;集线器和交换器的区别及交换器的自学习算法 一、 实验目的 加深对集线器和交换器的区别的理解。 了解交换器的自学习算法。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、熟悉集线器和交换器的区别 (1) 第一步&#xff1a;构建网络…

UICollectionView在xcode16编译闪退问题

使用xcode15运行工程&#xff0c;控制台会出现如下提示&#xff1a; Expected dequeued view to be returned to the collection view in preparation for display. When the collection views data source is asked to provide a view for a given index path, ensure that a …

Proteus8.17下载安装教程

Proteus是一款嵌入式系统仿真开发软件&#xff0c;实现了从原理图设计、单片机编程、系统仿真到PCB设计&#xff0c;真正实现了从概念到产品的完整设计&#xff0c;其处理器模型支持8051、HC11、PIC10/12/16/18/24/30/DsPIC33、AVR、ARM、8086和MSP430等&#xff0c;能够帮助用…

Vue教程|搭建vue项目|Vue-CLI2.x 模板脚手架

一、项目构建环境准备 在构建Vue项目之前&#xff0c;需要搭建Node环境以及Vue-CLI脚手架&#xff0c;由于本篇文章为上一篇文章的补充&#xff0c;也是为了给大家分享更为完整的搭建vue项目方式&#xff0c;所以环境准备部分采用Vue教程&#xff5c;搭建vue项目&#xff5c;V…

一款支持80+语言,包括:拉丁文、中文、阿拉伯文、梵文等开源OCR库

大家好&#xff0c;今天给大家分享一个基于PyTorch的OCR库EasyOCR&#xff0c;它允许开发者通过简单的API调用来读取图片中的文本&#xff0c;无需复杂的模型训练过程。 项目介绍 EasyOCR 是一个基于Python的开源项目&#xff0c;它提供了一个简单易用的光学字符识别&#xff…

cocotb pytest

打印python中的print &#xff0c; 应该使用 pytest -s

【C++】STL——map和set

目录 1、序列式容器和关联式容器前 2、set 2.1 set类的介绍 2.2 set的构造和迭代器 2.3 set的增删查 set 的插入 set的查找 set的删除 2.4 multiset和set的差异 3、map 3 .1 pair类型 3.2 map的构造 3.3 map的增删查 map的构造遍历 map的插入 map的删除 map的查…

java基础概念46-数据结构1

一、引入 List集合的三种实现类使用了不同的数据结构&#xff01; 二、数据结构的定义 三、常见的数据结构 3-1、栈 特点&#xff1a;先进后出&#xff0c;后进先出。 java内存容器&#xff1a; 3-2、队列 特点&#xff1a;先进先出、后进后出。 栈VS队列-小结 3-3、数组 3-…

Docker:在 ubuntu 系统上生成和加载 Docker 镜像

本文将介绍在 ubuntu系统上进行 Docker 镜像的生成和加载方法和代码。 文章目录 一、下载和安装 docker二、加载 docker 文件三、保存你的镜像四、将镜像上传到云端并通过连接下载和加载 Docker 镜像五、Docker 容器和本地的文件交互5.1 从容器复制文件到本地宿主机5.1.1 单个文…

《数据挖掘:概念、模型、方法与算法(第三版)》

嘿&#xff0c;数据挖掘的小伙伴们&#xff01;今天我要给你们介绍一本超级实用的书——《数据挖掘&#xff1a;概念、模型、方法与算法》第三版。这本书是数据挖掘领域的经典之作&#xff0c;由该领域的知名专家编写&#xff0c;系统性地介绍了在高维数据空间中分析和提取大量…

做异端中的异端 -- Emacs裸奔之路4: 你不需要IDE

确切地说&#xff0c;你不需要在IDE里面编写或者阅读代码。 IDE用于Render资源文件比较合适&#xff0c;但处理文本&#xff0c;并不划算。 这的文本文件&#xff0c;包括源代码&#xff0c;配置文件&#xff0c;文档等非二进制文件。 先说说IDE带的便利: 函数或者变量的自动…

【C++】编程题目分析与实现回顾:从浮点数运算到整型转换的全面解读

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目一&#xff1a;计算成绩问题分析与优化实现优化后的实现优势 &#x1f4af;题目二&#xff1a;浮点数向零舍入不同实现方式的比较1. 使用强制类型转换 (int)2. 使用标准…

时间表格Java

输入&#xff1a;XXX XXX 小时 分钟 输出&#xff1a; XXX&#xff1a;XXX ~ XXX: XXX XXX&#xff1a;XXX ~ XXX: XXX XXX&#xff1a;XXX ~ XXX: XXX 处理&#xff1a;间隔五分钟、区间45分钟 14:15 ~ 15:0 15:5 ~ 15:50 15:55 ~ 16:40 16:45 ~ 17:30 17:35 ~ 18:20…