go 中获取环境变量和文件读取与写入

889 阅读1分钟

环境变量

go 中读取环境变量是通过 os.Getenv

db := os.Getenv("DB")
fmt.Println(db)

环境变量存储位置是看你的终端

如果你的终端是 zsh 那么你可以在 ~/.zshrc 中设置,如果是 bash 那么你可以在 ~/.bashrc 中设置

临时环境变量设置

$ export DB=postgres
$ go run .  # postgres

$ DB=mysql go run .  # mysql

批量读取环境变量

批量读取环境变量可以使用 "github.com/caarlos0/env"

通过定义一个结构体,然后通过 env.Parse 方法将环境变量解析到结构体中,代码如下:

type Config struct {
  DB        string `env:"DB"`
  LogLevel  string `env:"LogLevel"`
  RedisHost string `env:"RedisHost"`
  RedisPort string `env:"RedisPort"`
}

func main() {
  cfg := Config{}
  // 解析环境变量
  if err := env.Parse(&cfg); err != nil {
    fmt.Printf("%+v\n", err)
  }
  fmt.Printf("%v\n", cfg)
}

文件读取

os.Openos.OpenFile 用于打开文件,os.Open 只读方式打开文件,os.OpenFile 可以指定打开方式

读取文件通过 os.Open 方法,然后通过 file.Read 方法将文件内容读取到指定的缓冲区中

  • buf 是为读取文件的最大字节
  • file.Read 返回读取的字节数和错误信息
func main() {
  file, err := os.Open("example.csv")
  if err != nil {
    panic(err)
  }
  defer file.Close()

  buf := make([]byte, 100)
  n, err := file.Read(buf) // 将文件内容读取到 buf 中,buf 的长度是读取的最大长度,n 是读取的字节数
  if err != nil {
    panic(err)
  }
  fmt.Println(n)
  fmt.Println(string(buf[:n])) // 如果 buffer 太大,可能会有多余的字符,所以需要通过 n 来截取
}

这种方式读取文件是一次性读取文件的所有内容,如果文件过大,可能会导致内存溢出

如何解决这个问题呢?

循环读取文件

通过循环读取文件,每次读取文件的一部分,直到文件读取完毕,代码如下:

func main() {
	file, err := os.Open("example.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

  // buf 设置小一点,每次读取文件的一部分
	buf := make([]byte, 1024)
	for {
		n, err := file.Read(buf)
		if err != nil {
      // 如果读取到文件末尾,那么就退出循环
			if err == io.EOF {
				break
			}
			panic(err)
		}
		fmt.Println(string(buf[:n]))
		fmt.Println(n)
	}
}

这种方式有个问题:每次读取的字节数是固定的,可能会导致文件内容被截取

readAll

io.ReadAll 可以一次性读取文件的所有内容,代码如下:

func main() {
  file, err := os.Open("example.csv")
  if err != nil {
    panic(err)
  }
  defer file.Close()

  bytes, err := io.ReadAll(file)
  if err != nil {
    panic(err)
  }
  fmt.Println(string(bytes))
}

这种方式也有问题,如果文件过大,可能会导致内存溢出

按行读取文件

通过 bufio.NewReader 可以按行读取文件,代码如下:

func main() {
  file, err := os.Open("example.csv")
  if err != nil {
    panic(err)
  }
  defer file.Close()

  reader := bufio.NewReader(file)
  for {
    line, _, err := reader.ReadLine()
    if err != nil {
      if err == io.EOF {
        break
      }
      panic(err)
    }
    fmt.Println(string(line))
  }
}

这种方式可以按行读取文件,就不会出现内存溢出的问题

文件写入

os 文件操作:

  • os.O_TRUNC 清空文件内容
  • os.O_APPEND 在文件末尾追加内容
  • os.O_CREATE 创建文件
  • os.O_RDONLY 只读
  • os.O_WRONLY 只写
  • os.O_RDWR 读写
func main() {
  file, err := os.OpenFile("example.csv", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
  if err != nil {
    panic(err)
  }
  defer file.Close()

  for i := 0; i < 20; i++ {
    _, err := file.WriteString(fmt.Sprintf("%d\n", i))
    if err != nil {
      panic(err)
    }
  }
}

使用游标

写完文件后,如果想要读取文件,需要将游标移动到文件的开始位置

  • 因为写文件时,游标会移动到文件的末尾
func main() {
  file, err := os.OpenFile("example.csv", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
  if err != nil {
    panic(err)
  }
  defer file.Close()

  for i := 0; i < 20; i++ {
    _, err := file.WriteString(fmt.Sprintf("%d\n,", i))
    if err != nil {
      panic(err)
    }
  }

  // 移到开始的位置
  file.Seek(0, io.SeekStart)

  reader := bufio.NewReader(file)
  for {
    line, _, err := reader.ReadLine()
    if err != nil {
      if err == io.EOF {
        return
      }
      panic(err)
    }
    fmt.Println(string(line))
  }
}

递归读取文件夹

os.ReadDir 读出来的文件只是目录下的文件名,还需要手动拼接路径

在不同的操作系统中,路径的格式是不一样的,在 linux 中是以 / 拼接,而在 windows 中是以 \\ 拼接

filepath.Join 可以根据不同的操作系统,拼接符合操作系统的路径

func readDirRecursively(dir string) error {
  entries, err := os.ReadDir(dir)
  if err != nil {
    return err
  }

  for _, entry := range entries {
    // filepath.Join 用于拼接路径,会根据不同的操作系统,拼接符合操作系统的路径
    filePath := filepath.Join(dir, entry.Name())
    if entry.IsDir() {
      // 如果是文件夹,则递归读取
      if err := readDirRecursively(filePath); err != nil {
        return err
      }
      // 判断读取的文件是不是以 .go 结尾
    } else if filepath.Ext(filePath) == ".go" {
      fileBytes, err := os.ReadFile(filePath)
      if err != nil {
        return err
      }
      fmt.Println(string(fileBytes))
    }
  }
  return nil
}

ReadDir 可以传入数字,如果传入的数字小于 0,则读取全部文件,如果传入的数字大于 0,则读取指定数量的文件:

  • ReadDir(-1) 读取所有文件
  • ReadDir(1) 读取一个文件

递归读取文件的另一种方法是使用 filepath.WalkDir 方法,代码如下:

func walkDir(dir string) error {
	return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if !d.IsDir() && filepath.Ext(d.Name()) == ".go" {
			fileBytes, err := os.ReadFile(path)
			if err != nil {
				return err
			}
			fmt.Println(path, string(fileBytes))
		}
		return nil
	})
}

linux 文件权限

-rwxr-xr-x

rwx 三个一组,代表文件的权限

  • 第一组:文件所有者的权限
  • 第二组:文件所在组的权限
  • 第三组:其他用户的权限

修改权限

$ chmod a-x xxx # a 表示所有用户,- 表示去掉权限,这里的意思是去掉所有的执行权限
# 运行前
-rwxr-xr-x
# 运行后
-rw-r--r--
  • a 表示所有用户
  • - 表示去掉权限
  • + 表示加上权限

rwx 也可以用数字表示

  • r 表示 4
  • w 表示 2
  • x 表示 1

所以:

  • rwx 可以用 7 表示
  • r-x 可以用 5 表示
  • rw- 可以用 6 表示