Golang中如何使用相对路径嵌入父目录
Golang中如何使用相对路径嵌入父目录 我的问题是:是否可以为 go:embed 启用相对路径?
示例:我想将我的 README.md 文件包含到项目中,但我将所有嵌入变量都放在了 assets 包中。
代码:
//go:embed ../README.md
var README []byte
问题:
assets/assets.go:7:12: pattern ../README.md: invalid pattern syntax
“除了根目录为 . 的特殊情况。”
我理解为 ./../README.md 可能有效。
不过我刚检查了一下,似乎确实因为某种原因不被接受……像这样的路径在Windows、Linux以及可能Mac上都是被接受的。
你是否尝试过使用仅包含两个点的有效路径?
虽然我从未使用过 go:embed,但我能找到的所有相关示例确实使用了相对路径(尽管没有示例向上访问父目录,都是向下访问子文件夹)。
你试过只用两个点的有效路径吗?
是的,这里只是打错了。
另外,到子文件夹的路径工作正常。问题出在指向父目录的路径上。
我在Go仓库中提交了一个问题。我认为,我们应该在这里讨论一下这个想法。例如,如果..位于模块区域内,是否应该允许go:embed?大家对此有什么看法?
我读到这,似乎
./../README.md应该能工作。
它之所以不能工作,是因为 io/fs.ValidPath 方法,该方法在 Go 源代码中被使用。
路径名称不得包含“.”或“..”或空字符串元素,除非根目录被命名为“.”这一特殊情况。路径不得以斜杠开头或结尾:“/x”和“x/”是无效的。
引用 io/fs 的源代码:
这里检查了当 name 为 "." 的特殊情况:
if name == "." {
// special case
return true
}
否则,如果不检查这个条件:
// Iterate over elements in name, checking each.
for {
i := 0
for i < len(name) && name[i] != '/' {
i++
}
elem := name[:i]
if elem == "" || elem == "." || elem == ".." {
return false
}
if i == len(name) {
return true // reached clean ending
}
name = name[i+1:]
}
因此,我认为这个错误是由这个 io/fs.ValidPath 调用引起的:
// Check pattern is valid for //go:embed.
if _, err := path.Match(pattern, ""); err != nil || !validEmbedPattern(pattern) {
return nil, nil, fmt.Errorf("invalid pattern syntax")
}
func validEmbedPattern(pattern string) bool {
return pattern != "." && fs.ValidPath(pattern)
}
现在,我明白了,我的错误源于在Go源代码 go/src/cmd/go/internal/load/pkg.go(第1971行)中使用了 io/fs.ValidPath:
func ValidPath 1.16
func ValidPath(name string) bool
ValidPath reports whether the given path name is valid for use in a call to Open.
Path names passed to open are UTF-8-encoded, unrooted, slash-separated sequences of path elements, like “x/y/z”. Path names must not contain an element that is “.” or “..” or the empty string, except for the special case that the root directory is named “.”. Paths must not start or end with a slash: “/x” and “x/” are invalid.
Note that paths are slash-separated on all systems, even Windows. Paths containing other characters such as backslash and colon are accepted as valid, but those characters must never be interpreted by an FS implementation as path element separators.
元素,例如“x/y/z”。路径名不得包含“.”、“…”或空字符串的元素。
然而,我不理解为什么不允许这样做,因为 os.Open 允许包含“.”和“…”的字符串。
在Go语言中,go:embed指令不支持使用相对路径(如../)来引用父目录。这是Go 1.16引入embed功能时的设计限制,主要是出于安全性和可移植性的考虑。
不过,你可以通过以下几种方式解决这个问题:
方案1:将文件移动到当前包目录或子目录
最简单的方法是将README.md文件移动到assets目录中,或者创建一个子目录:
// 将README.md移动到assets目录下
//go:embed README.md
var README []byte
或者:
// 在assets目录下创建docs子目录
//go:embed docs/README.md
var README []byte
方案2:使用符号链接(Symbolic Link)
在支持符号链接的系统上,可以创建一个指向父目录的符号链接:
# 在assets目录中创建符号链接
ln -s ../README.md assets/README.md
然后嵌入:
//go:embed README.md
var README []byte
方案3:在项目根目录创建embed包
创建一个专门用于嵌入的包,放在项目根目录:
// embed/embed.go
package embedfiles
//go:embed README.md
var README []byte
然后在assets包中使用:
package assets
import "yourproject/embedfiles"
func GetReadme() []byte {
return embedfiles.README
}
方案4:使用构建标签和条件编译
如果文件位置因环境而异,可以使用构建标签:
//go:build !windows
//go:embed ../README.md
var README []byte
//go:build windows
//go:embed README.md
var README []byte
方案5:运行时读取文件
如果必须使用相对路径,可以在运行时读取文件:
package assets
import (
"io/ioutil"
"path/filepath"
"runtime"
)
func ReadReadme() ([]byte, error) {
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Dir(filename)
readmePath := filepath.Join(dir, "..", "README.md")
return ioutil.ReadFile(readmePath)
}
实际示例
假设你的项目结构如下:
project/
├── README.md
└── assets/
└── assets.go
在assets.go中,你可以这样处理:
package assets
import (
"embed"
"path/filepath"
"runtime"
)
// 嵌入assets目录内的文件
//go:embed *.txt
var localFiles embed.FS
// 运行时读取父目录文件
func GetParentFile(filename string) ([]byte, error) {
_, currentFile, _, _ := runtime.Caller(0)
assetsDir := filepath.Dir(currentFile)
parentDir := filepath.Dir(assetsDir)
targetPath := filepath.Join(parentDir, filename)
return ioutil.ReadFile(targetPath)
}
// 使用示例
func Example() {
// 嵌入assets目录内的文件
data, _ := localFiles.ReadFile("example.txt")
// 运行时读取README.md
readme, _ := GetParentFile("README.md")
}
最推荐的方法是方案1或方案3,因为它们最符合Go的设计哲学,且能保证代码的可移植性。如果必须访问父目录文件,方案5(运行时读取)是最可靠的跨平台解决方案。

