Go红队开发—文件操作

目录

文件操作

由于在上一期编解码中对文件有操作需求,所以这一期补上文件操作功能.

依旧是直接放代码,加速学习进度,代码拿着就用即可,都是内置的功能函数。

PS:一些函数参数或者功能解释都放在代码注释里面了

创建目录

go 复制代码
// 创建目录

func createDir() {

    err := os.Mkdir("test_dir", os.ModePerm)

    if err != nil {

        fmt.Println("创建目录失败")

        return

    }

    fmt.Println("创建目录成功")

}

创建文件

go 复制代码
// 创建文件

func createFile(filename string) {

    newfile, err := os.Create(filename)

    defer newfile.Close()

    if err != nil {

        fmt.Println("创建文件失败:", err)

        return

    }
    fmt.Println("创建文件成功:", newfile.Name())

}

获取File信息

文件与目录都能判断

go 复制代码
func getFileInfo() {

    //path := "test_dir"

    path := "test.txt"

    fileInfo, err := os.Stat(path)

    if err != nil {

        fmt.Println(err)

        return

    }

    fmt.Println("文件名:", fileInfo.Name())

    fmt.Println("文件大小:", fileInfo.Size())

    fmt.Println("文件权限:", fileInfo.Mode())

    fmt.Println("最后一次修改时间:", fileInfo.ModTime())

    fmt.Println("是否为目录: ", fileInfo.IsDir())

    //fmt.Println("系统接口类型: %T", fileInfo.Sys())

    //fmt.Println("系统信息: %+v", fileInfo.Sys())

}

文件重命名

go 复制代码
// 文件重命名

func renameFile() {

    oldFileName := "text.txt"

    newFileName := "newtest.txt"

    err := os.Rename(oldFileName, newFileName)

    if err != nil {

        fmt.Println("重命名出错:", err)

        return

    }

}

删除文件

go 复制代码
// 删除文件

func removeFile(fileaName string) {

    err := os.Remove(fileaName)

    if err != nil {

        fmt.Println("删除失败:", err)

        return

    }

    fmt.Println(fileaName, "删除成功")

}

打开关闭文件

go 复制代码
// 打开关闭文件

// 介绍原始open打开文件与OpenFile可以提供更多的选项打开文件

func open_close_file() {

  

    fmt.Println("os.Open打开文件")

    file, err := os.Open("test.txt")

    defer file.Close() //延迟关闭文件,建议打开文件后都习惯性写一个

    if err != nil {

        fmt.Println("os.Open打开文件失败:", err)

        return

    }

    fmt.Println("os.Open文件打开成功")

    //---------------------------------------------

    fmt.Println("os.OpenFile打开文件")

    file, err = os.OpenFile("test.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("os.OpenFile打开文件失败:", err)

        return

    }

    fmt.Println("os.OpenFile打开文件成功")

}

判断文件是否存在

go 复制代码
// 判断文件是否存在

func isExistFile(fileName string) {

    _, err := os.Stat(fileName)

    if err != nil {

        //因为报出错误可能有很多种原因,想要知道是不是文件不存在导致的err还需要验证

        if os.IsNotExist(err) {

            fmt.Println("确认这个错误是文件不存在导致的:", err)

        }

  

    }

}

判断文件是否有读取权限

go 复制代码
// 判断文件是否有读写权限

func isPermission() {

  

    file, err := os.OpenFile("test.txt", os.O_RDONLY, 0666) //直接给O_WRONLY标志,可以判断是否有写权限

    defer file.Close()

    if err != nil {

        if os.IsPermission(err) {

            fmt.Println("没有读取权限:", err)

        }

    }

    fmt.Println("可读")

  

    file, err = os.OpenFile("test.txt", os.O_WRONLY, 0666) //直接给O_WRONLY标志,可以判断是否有写权限

    defer file.Close()

    if err != nil {

        if os.IsPermission(err) {

            fmt.Println("没有写权限", err)

        }

    }

    fmt.Println("可写")

  

}

复制文件

go 复制代码
// 文件复制

func copyFile() {

  

    //打开一个你要复制的文件

    src_file, src_err := os.Open("test.txt")

    defer src_file.Close()

    if src_err != nil {

        fmt.Println("文件打开失败:", src_err)

    }

  

    //创建一个新文件用来复制内容到这里(其实可以用存在的文件继续打开也行,主要是用file句柄而已)

    des_file, des_err := os.Create("newtest.txt")

    defer des_file.Close()

    if des_err != nil {

        fmt.Println("文件创建失败", des_err)

    }

  

    //复制内容到新文件中

    bytesWritten, err := io.Copy(des_file, src_file) //bytesWritten是计算复制的长度

    if err != nil {

        fmt.Println("复制失败:", err)

    }

    fmt.Println("写入了:", bytesWritten)

  

    // 将文件内容flush到硬盘中,这操作对于我们现在来说是不必要的,

    // 因为在大多数情况下,程序正常退出时,数据会被操作系统自动刷新到磁盘。

    // 当我们在不断地写入内容的时候可能就需要不定期的进行flush

    err = des_file.Sync()

}

Read读取

go 复制代码
// ---------------------------------------------------

// 读取文件内容

// ---------------------------------------------------

// Read读取文件内容,返回读取的长度和内容,如果读取的长度小于切片的长度,说明文件读取完了

func readFile() {

    //打开文件

    file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("打开文件失败:", err)

        return

    }

    bytes_content := make([]byte, 10)           //Read就是传进内容到这个切片空间中

    contentlen, err := file.Read(bytes_content) //返回的是读取的长度

    if err != nil {

        fmt.Println("读取文件失败:", err)

        return

    }

    fmt.Printf("读取长度:%d\n", contentlen)

    fmt.Println("读取内容:", string(bytes_content))

}

ReadFull读取

go 复制代码
// ReadFull读取文件内容,返回读取的长度和内容,如果读取的长度小于切片的长度会返回错误

func readFullFile() {

    //打开文件

    file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("打开文件失败:", err)

        return

    }

  

    //ReadFull就是传进内容到这个bytes_content切片空间中,如果给的空间大于读取的内容就会报错,小于的的话给多少读多少

    bytes_content := make([]byte, 2)

    contentlen, err := io.ReadFull(file, bytes_content) //返回的是读取的长度

    if err != nil {

        fmt.Println("读取文件失败:", err)

        return

    }

    fmt.Printf("读取长度:%d\n", contentlen)

    fmt.Println("读取内容:", string(bytes_content))

}

ReadAtLeast读取

go 复制代码
// ReadAtLeast至少读取xx bytes文件内容

func readAtLeastFile() {

    //打开文件

    file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("打开文件失败:", err)

        return

    }

    bytes_content := make([]byte, 512) //Read就是传进内容到这个切片空间中

  

    // io.ReadAtLeast()在拿不到最小的字节内容的时候会返回错误,

    // 如果你不return回去,

    // 他会把已读的文件保留。你依旧能够打印读取到的内容

    //相比ReadFull,ReadAtLeast不会报错,

    // 只是说最小字节数是至少读取这么多,

    // 但是依旧会往后读取,报错会留下已读取的内容

    minBytes := 10

    contentlen, err := io.ReadAtLeast(file, bytes_content, minBytes) //返回的是读取的长度

    if err != nil {

        fmt.Println("读取文件失败:", err)

        //return   //取消return,依旧能打印读取到的内容

    }

    fmt.Printf("读取长度:%d\n", contentlen)

    fmt.Println("读取内容:", string(bytes_content))

}

ReadAll读取

go 复制代码
// ReadAll读取文件所有内容

func readAllFile() {

    //打开文件

    file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("打开文件失败:", err)

        return

    }

    // io.ReadAll()会读取所有的文件内容,返回的是读取的长度

    content, err := io.ReadAll(file) //返回的是读取的长度

    if err != nil {

        fmt.Println("读取文件失败:", err)

        return

    }

    fmt.Println("读取内容:", string(content))

}

bufio读取

以下是 bufio.Scanner 中几种常用分割符的具体说明和示例:


bufio.ScanWords

  • 分割符 : 空格( )或连续的空白字符(包括空格、制表符 \t、换行符 \n 等)。

  • 行为: 每次扫描一个单词(以空白字符分隔的字符串)。

  • 示例 :

    go 复制代码
    scanner.Split(bufio.ScanWords)

    输入 :

    "Hello   World\nGo Programming"
    

    输出 :

    "Hello"
    "World"
    "Go"
    "Programming"
    

bufio.ScanLines

  • 分割符 : 换行符(\n)。

  • 行为: 每次扫描一行(以换行符分隔的字符串)。

  • 示例 :

    go 复制代码
    scanner.Split(bufio.ScanLines)

    输入 :

    "Hello\nWorld\nGo Programming"
    

    输出 :

    "Hello"
    "World"
    "Go Programming"
    

bufio.ScanBytes

  • 分割符: 无(按字节分割)。

  • 行为: 每次扫描一个字节。

  • 示例 :

    go 复制代码
    scanner.Split(bufio.ScanBytes)

    输入 :

    "Hello"
    

    输出 :

    'H'
    'e'
    'l'
    'l'
    'o'
    

bufio.ScanRunes

  • 分割符: 无(按字符分割)。

  • 行为: 每次扫描一个字符(支持多字节字符,如中文)。

  • 示例 :

    go 复制代码
    scanner.Split(bufio.ScanRunes)

    输入 :

    "你好,世界!"
    

    输出 :

    '你'
    '好'
    ','
    '世'
    '界'
    '!'
    

总结

分割函数 分割符 行为 示例输入 示例输出
bufio.ScanWords 空格( )或连续空白字符 每次扫描一个单词 "Hello World\nGo" "Hello", "World", "Go"
bufio.ScanLines 换行符(\n 每次扫描一行 "Hello\nWorld\nGo" "Hello", "World", "Go"
bufio.ScanBytes 每次扫描一个字节 "Hello" 'H', 'e', 'l', 'l', 'o'
bufio.ScanRunes 每次扫描一个字符(支持多字节字符) "你好,世界!" '你', '好', ',', '世', '界', '!'

注意事项

  1. bufio.ScanWords:

    • 会忽略连续的空白字符(如多个空格或制表符)。
    • 适合处理以空格分隔的文本。
  2. bufio.ScanLines:

    • 会保留每行的内容(包括行尾的空白字符)。
    • 适合处理逐行文本。
  3. bufio.ScanBytes:

    • 按字节分割,适合处理二进制数据或需要逐字节分析的场景。
  4. bufio.ScanRunes:

    • 按字符分割,支持多字节字符(如中文、日文、韩文等)。
    • 适合处理 Unicode 文本。

示例代码:

go 复制代码
// bufio读取文件能够配合scanner一行一行读取,安全开发过程中可能用的最多,因为爆破的需求一般都是一行一行读取

func bufioReadFile() {

    //打开文件

    file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("打开文件失败:", err)

        return

    }

    //bufio.NewReader()返回一个io.Reader对象,可以用来读取文件内容

    reader := io.Reader(file)

    //bufio.NewScanner()返回一个io.Scanner对象,可以用来读取文件内容

    scanner := bufio.NewScanner(reader)

    //scanner.Split(bufio.ScanWords) //按照空格分割

    //scanner.Split(bufio.ScanLines) //按照换行符分割 默认不写就是按照换行符分割

    //scanner.Split(bufio.ScanBytes) //按照字节分割

    //scanner.Split(bufio.ScanRunes) //按照字符分割

    for scanner.Scan() {

        fmt.Println(scanner.Text())

    }

}

Write写入

go 复制代码
// ---------------------------------------------------

// 写入文件内容

// ---------------------------------------------------

func write() {

    file, err := os.OpenFile(

        "test.txt",

        os.O_CREATE|os.O_APPEND|os.O_RDWR|os.O_APPEND,

        0666,

    )

    defer file.Close()

    if err != nil {

        fmt.Println("文件打开失败:", err)

        return

    }

    bytesWrittenLen, err := file.Write([]byte("hello hack\n"))

    if err != nil {

        fmt.Println("内容写入失败:", err)

        return

    }

    fmt.Printf("写入成功:%d字节长度\n", bytesWrittenLen)

}

WriteFile快速写入

go 复制代码
// 快速写内容

// 用在需要快速简洁写入内容的时候

func ioutil_WriteFile() {

    err := ioutil.WriteFile("test.txt", []byte("hello hack2\n"), 0666)

    if err != nil {

        fmt.Println("内容写入失败:", err)

        return

    }

  

}

临时文件目录

go 复制代码
  

// 临时文件和目录,在开发工具的时候可能会用的上

func tmpDir_File() {

    // 第一个参数是空字符串表示TempDir 会使用系统的默认临时目录。

    // 在 Linux/macOS 上,通常是 /tmp。

    // 在 Windows 上,通常是 C:\Users\<User>\AppData\Local\Temp。

    tempDir, err := ioutil.TempDir("", "tmpDirName")

    if err != nil {

        fmt.Println("创建失败:", err)

        return

    }

    fmt.Println("目录创建成功:", tempDir)

  

    tempFile, err := ioutil.TempFile(tempDir, "temp.txt") //创建文件要给参数:tempDir目录和temp文件名字

    if err != nil {

        fmt.Println("文件创建失败:", err)

        return

    }

    fmt.Println("文件创建成功:", tempFile.Name())

  

    //虽然说是临时文件也耀手动关闭文件

    defer tempFile.Close()

  

    err = os.Remove(tempFile.Name())

    if err != nil {

        fmt.Println("文件删除失败")

        return

    }

  

    err = os.Remove(tempDir)

    if err != nil {

        fmt.Println("目录删除失败")

        return

    }

  

}

下载文件

go 复制代码
// http get downLoad文件到本地

func downLoadFile() {

    httpFile, err := os.OpenFile(

        "baidu.html",

        os.O_CREATE|os.O_RDWR,

        0666,

    )

    if err != nil {

        fmt.Println("文件打开失败")

    }

    defer httpFile.Close()

    url := "http://www.baidu.com"

    rep, err := http.Get(url)

    defer rep.Body.Close()

  

    written, err := io.Copy(httpFile, rep.Body)

    if err != nil {

        fmt.Println("文件复制失败:", err)

    }

    fmt.Println("下载成功,文件大小(byte):\n", written)

}

文件指针操作

go 复制代码
func offsetFile() {

    //打开文件

    file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println("打开文件失败:", err)

        return

    }

    //获取文件的偏移量

    var (

        offset int64 = 1

        whence int   = 1

  

        //whence用来计算offset的位置

        // 0 = 文件开始位置

        // 1 = 当前位置

        // 2 = 文件结尾处

    )

    newP, err := file.Seek(offset, whence)

    if err != nil {

        fmt.Println("获取文件偏移量失败:", err)

        return

    }

    fmt.Println("文件偏移量:", newP)

    //直接当前位置读取文件内容

    bytes_content := make([]byte, 10)           //Read就是传进内容到这个切片空间中

    contentlen, err := file.Read(bytes_content) //返回的是读取的长度

    if err != nil {

        fmt.Println("读取文件失败:", err)

        return

    }

    fmt.Printf("读取长度:%d\n", contentlen)

    fmt.Println("读取内容:", string(bytes_content))

  

}

修改文件权限/拥有者/时间戳

参考文章:https://colobu.com/2016/10/12/go-file-operations/

这篇文章作者写的这真的很好,全是干货。

go 复制代码
package main

import (

    "log"

    "os"

    "time"

)

func main() {

    // 使用Linux风格改变文件权限

    err := os.Chmod("test.txt", 0777)

    if err != nil {

        log.Println(err)

    }

    // 改变文件所有者

    err = os.Chown("test.txt", os.Getuid(), os.Getgid())

    if err != nil {

        log.Println(err)

    }

    // 改变时间戳

    twoDaysFromNow := time.Now().Add(48 * time.Hour)

    lastAccessTime := twoDaysFromNow

    lastModifyTime := twoDaysFromNow

    err = os.Chtimes("test.txt", lastAccessTime, lastModifyTime)

    if err != nil {

        log.Println(err)

    }

}

知识细节补充

OS.OpenFile

先介绍一下这个openfile功能,之前我是使用os.open来打开文件, 但是只能以只读的方式打开文件,而openfile更通用的文件打开函数,可以指定打开模式(只读、只写、读写等)和文件权限。接下来我都会使用openfile来操作文件,更精细化控制文件。

⽂件打开模式标志,在使用openfile的时候需要以一个怎样的模式打开文件

比如:os.OpenFile("example.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)

多个形式可以用|管道符分开,表示可读可写,同时CREATE表示当文件不存在的时候创建,同时APPEND表示追加模式。

更多模式可以看下面介绍

shell 复制代码
os.O_RDONLY:只读模式打开⽂件。 
os.O_WRONLY:只写模式打开⽂件。 
os.O_RDWR:读写模式打开⽂件。 
os.O_APPEND:追加模式。如果⽂件已存在,数据将写⼊⽂件末尾。 
os.O_CREATE:如果⽂件不存在,则创建⽂件。 
os.O_EXCL:与 os.O_CREATE ⼀起使⽤,⽂件必须不存在。 
os.O_SYNC:同步 I/O,保证写⼊数据和元数据同步到存储设备。
os.O_TRUNC:如果⽂件已存在,打开时将其⻓度截断为零。

细节写在前头:我们读取完或者写入完后,文件指针是不在开头的,如果你读取完就直接写入就是在你读取到的当前位置写入,或者你写入完成后继续读取就是在当前位置读取,可能读取不到你想要的内容,所以file.Seek就很有必要了,或者你重新打开该文件也可以自动将指针指向开头。


演示代码:

打开一个文件写入hello hack,然后读取这个文件内容

  • O_RDWR:可读可写
  • O_CREATE:文件不存在就创建
  • O_APPEND:追加模式
  • 最后的0666是文件操作权限,这里就涉及到linux的文件权限问题了(忽略,后面讲)
go 复制代码
func write_readTest() {

    file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)

    defer file.Close()

    if err != nil {

        fmt.Println(err)

        return

    }

    file.WriteString("hello hack") // 写入文件

    // 读取文件

    scanner := bufio.NewScanner(file)

  

    //读取完文件后,文件指针已经到了文件末尾,需要重新定位到文件开头

    // 将文件指针重新定位到文件开头

    file.Seek(0, 0)

    for scanner.Scan() {

        fmt.Println(scanner.Text()) //一行一行读取

    }

    if err := scanner.Err(); err != nil {

        fmt.Println("读取文件失败:", err)

    }

  

}

bufio

我们在编解码的时候就用了这个bufio,我们可以new一个scanner来一行一行读取文件内容,如果你需要更细粒度的控制(如按字节读取),可以使用 bufio.Reader

go 复制代码
//常用方法
file, err := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	// 读取文件
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text()) //一行一行读取

    }
    if err := scanner.Err(); err != nil {
        fmt.Println("读取文件失败:", err)
    }

os.Stat

os.Stat 是 Go 语言中用于获取文件或目录信息的函数。它的作用是返回一个描述文件或目录状态的对象(os.FileInfo

  • 参数 name:

    • 接受一个字符串参数,表示文件或目录的路径。

    • 可以是相对路径(如 "../example.txt 或者目录 ../dirname")或绝对路径(如 "/home/user/example.txt")。

    • 也可以是文件名(如果文件在当前工作目录下)。

  • 返回值:

    • FileInfo: 一个接口类型,包含文件或目录的详细信息。

    • error: 如果操作失败(如文件不存在),返回错误信息。

os.fileInfo

os.FileInfo 是一个接口,包含以下方法:

方法名 返回值类型 说明
Name() string string 返回文件或目录的名称(不包括路径)。
Size() int64 int64 返回文件的字节大小(目录通常返回 0)。
Mode() FileMode FileMode 返回文件的权限和模式。
ModTime() time.Time time.Time 返回文件的最后修改时间。
IsDir() bool bool 判断是否是目录。
Sys() interface{} interface{} 返回底层数据源(通常为 nil)。

文件权限

使用⼋进制数表示⽂件的权限,这里是linux文件权限管理的知识,不懂自查。

os.ModePerm:⽂件的默认权限(0777)

go 复制代码
os.ModeDir:表示⼀个⽬录。 
os.ModeAppend:表示⽂件只能追加写⼊。 
os.ModeExclusive:表示⽂件是独占的。 
os.ModeTemporary:表示⽂件是临时⽂件。 
os.ModeSymlink:表示⽂件是⼀个符号链接。 
os.ModeDevice:表示⽂件是⼀个设备⽂件。 
os.ModeNamedPipe:表示⽂件是⼀个命名管道。 
os.ModeSocket:表示⽂件是⼀个 Unix 域套接字。 
os.ModeSetuid:表示⽂件具有 setuid 位。 
os.ModeSetgid:表示⽂件具有 setgid 位。 
os.ModeCharDevice:表示⽂件是⼀个字符设备。 
os.ModeSticky:表示⽂件具有粘滞位。 
os.ModeIrregular:表示⽂件是⼀个不规则⽂件。

参考文章:https://colobu.com/2016/10/12/go-file-operations