Let see how to embed files in Go, symlinks, reading multiple files and wildcards.
By: Leo Gutiérrez
Tags: Linux, Filesystem, Go, Embedded Systems, Golang

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

Hello

World!

Can I use a := declaration with go:embed?

I will change the code from:

var helloContent string

to:

helloContent := ""

example:

package main import (

_ "embed"

"fmt"

) func main() {

//go:embed "hello.txt"

helloContent := ""

fmt.Println(helloContent)

}

This is the error I get (misplaced go:embed directive)

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

# command-line-arguments

./main.go:9:4: misplaced go:embed directive

2 🐧 leo@lein ~/go-embed $

It seems that it is NOT possible. You can only use var declarations.

What about variables already initialized? Let’s try it.

package main import (

_ "embed"

"fmt"

) func main() {

//go:embed "hello.txt"

var helloContent string = ""

fmt.Println(helloContent)

}

Trying to build:

2 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

# command-line-arguments

./main.go:9:4: go:embed cannot apply to var with initializer

2 🐧 leo@lein ~/go-embed $

It looks like you cannot attempt to “override its content”.

Reading the content in []byte

package main import (

_ "embed"

"fmt"

) func main() {

//go:embed "hello.txt"

var helloContent []byte

fmt.Println(string(helloContent))

}

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

Hello

World!

No surprises.

The embed.FS file system

To read an embedded tree of files and directories, you can use the embed.FS file system, this is a read-only (safe to use it from multiple goroutines). Let’s see how to use it and some subties…

To read a single file:

package main import (

"embed"

_ "embed"

"fmt"

"log"

) func main() {

//go:embed "hello.txt"

var f embed.FS

data, err := f.ReadFile("hello.txt")

if err != nil {

log.Fatal(err)

} fmt.Println(string(data))

}

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

Hello

World!

Reading multiple files

I will create some “dummy” files first in the current directory:

0 🐧 leo@lein ~/go-embed $ mkdir templates

0 🐧 leo@lein ~/go-embed $ echo '<html>...</html>' > templates/t1.html

0 🐧 leo@lein ~/go-embed $ echo '<html>...</html>' > templates/t2.html

0 🐧 leo@lein ~/go-embed $ echo '<html>...</html>' > templates/t3.html

0 🐧 leo@lein ~/go-embed $ echo 'hello' > templates/text.txt

0 🐧 leo@lein ~/go-embed $ ls templates/

t1.html t2.html t3.html text.txt

0 🐧 leo@lein ~/go-embed $

Now let’s read the “templates” directory reading from the embed.FS:

package main import (

"embed"

"fmt"

"io/fs"

) func main() {

//go:embed templates/*

var files embed.FS

templates, _ := fs.ReadDir(files, "templates")

for _, template := range templates {

fmt.Printf("%q

", template.Name())

}

}

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

"t1.html"

"t2.html"

"t3.html"

"text.txt"

No surprises, again, but you can also read multiple directories. Let’s create another directory with some other files and try to read them too with the same directive.

0 🐧 leo@lein ~/go-embed $ mkdir mydir

0 🐧 leo@lein ~/go-embed $ echo 'hello' > mydir/hello.cpp

0 🐧 leo@lein ~/go-embed $ ls mydir/

hello.cpp

The Golang code has changed to:

package main import (

"embed"

"fmt"

"io/fs"

) func main() {

//go:embed templates/*

//go:embed mydir/*

var files embed.FS

templates, _ := fs.ReadDir(files, "templates")

for _, template := range templates {

fmt.Printf("%q

", template.Name())

} myDirFiles, _ := fs.ReadDir(files, "mydir")

for _, cppFile := range myDirFiles {

fmt.Printf("%q

", cppFile.Name())

} }

As you can see, I am reading both templates/ and mydir/ directories, that is great! We are also able to read subdirectories, I will create a directory inside the “mydir” directory and read it.

0 🐧 leo@lein ~/go-embed $ mkdir mydir/subdir

0 🐧 leo@lein ~/go-embed $ echo 'world' > mydir/subdir/wold.hpp

0 🐧 leo@lein ~/go-embed $ tree mydir/

mydir/

├── hello.cpp

└── subdir

└── wold.hpp 1 directory, 2 files

The Golang code has changed to:

func main() {

//go:embed templates/*

//go:embed mydir/*

var files embed.FS

templates, _ := fs.ReadDir(files, "templates")

for _, template := range templates {

fmt.Printf("%q

", template.Name())

} myDirFiles, _ := fs.ReadDir(files, "mydir/subdir")

for _, cppFile := range myDirFiles {

fmt.Printf("%q

", cppFile.Name())

} }

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

"t1.html"

"t2.html"

"t3.html"

"text.txt"

"wold.hpp"

Wildcards * … *.EXT

It seems that we can always use wildcards to select/filter specific files. My “templates” directory contains .html files and a single .txt file:

0 🐧 leo@lein ~/go-embed $ ls templates/

t1.html t2.html t3.html text.txt

I should be able to “select” the “txt” file only using a wildcard in the directive.

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

"text.txt"

It works!

What about a wildcard range [A-B]?

I have the following html files in my templates directory:

0 🐧 leo@lein ~/go-embed $ ls templates/

t1.html t2.html t3.html text.txt

0 🐧 leo@lein ~/go-embed $

I want to select t2.html and t3.html only. For that I should be able to use the t[2–3]*.html wildcard:

package main import (

"embed"

"fmt"

"io/fs"

) func main() {

//go:embed templates/t[2-3]*.html

var files embed.FS

templates, _ := fs.ReadDir(files, "templates")

for _, template := range templates {

fmt.Printf("%q

", template.Name())

}

}

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

"t2.html"

"t3.html"

the t[23]*.html wildcard should work too:

package main import (

"embed"

"fmt"

"io/fs"

) func main() {

//go:embed templates/t[23]*.html

var files embed.FS

templates, _ := fs.ReadDir(files, "templates")

for _, template := range templates {

fmt.Printf("%q

", template.Name())

}

}

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

"t2.html"

"t3.html"

?[23]*.html too:

package main import (

"embed"

"fmt"

"io/fs"

) func main() {

//go:embed templates/?[23]*.html

var files embed.FS

templates, _ := fs.ReadDir(files, "templates")

for _, template := range templates {

fmt.Printf("%q

", template.Name())

}

}

Output:

0 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

"t2.html"

"t3.html"

Anyway, I wanted to show that wildcards in directives work perfectly well.

Running a linter

Why would this be interesting? Some linters will tell you that you have to put a space between // and the comment text, example:

package main import "fmt" func main() {

//the following line prints to stdout "hello world"

fmt.Println("hello world")

}

If I run “golangci-lint” against it I will get this:

🐧 leo@lein ~/go-embed $ golangci-lint run --enable-all

main.go:6:2: commentFormatting: put a space between `//` and comment text (gocritic)

//the following line prints to stdout "hello world"

^

I am curious on why this decision was made, I am sure they have good reasons. What happens if I add a space after the // in a go:embed directive? Let’s try it, having this code:

package main import (

"embed"

"fmt"

"io/fs"

) func main() {

// go:embed templates/?[23]*.html

var files embed.FS

templates, err := fs.ReadDir(files, "templates")

fmt.Println(err)

for _, template := range templates {

fmt.Printf("%q

", template.Name())

}

}

Let’s run it:

2 🐧 leo@lein ~/go-embed $ go build -o hello-embed main.go

0 🐧 leo@lein ~/go-embed $ ./hello-embed

panic: runtime error: invalid memory address or nil pointer dereference

[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47d5ad] goroutine 1 [running]:

embed.FS.lookup(0x0, 0x4d1ff3, 0x9, 0x4bc0e0)

/home/leo/sw/go/src/embed/embed.go:251 +0xad

embed.FS.Open(0x0, 0x4d1ff3, 0x9, 0x7fbf6fbec008, 0xc000078e20, 0x40a78a, 0x4bc080)

/home/leo/sw/go/src/embed/embed.go:280 +0x45

embed.FS.ReadDir(0x0, 0x4d1ff3, 0x9, 0x7fbf6fbec008, 0x0, 0x1, 0x0, 0x4125fb)

/home/leo/sw/go/src/embed/embed.go:292 +0x45

io/fs.ReadDir(0x4f22d8, 0x0, 0x4d1ff3, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0)

/home/leo/sw/go/src/io/fs/readdir.go:30 +0x3ef

main.main()

/home/leo/go-embed/main.go:12 +0x5d

There is an error “invalid memory address or nil” error, it is required.

I am not a Golang expert, I come from a Java background, for readability purposes (let’s imagine that you are trying to read quickly the source code file…), in my opinion, it would be better to have a special symbol to identify these directives, perhaps a “@”? Something like this:

// @go:embed templates/?[23]*.html

My Java brain would identify this as “metainformation” and know that I have to pay attention, that this is a different kind of comment.

Best Practices

I think it is too early to have some best practices for the go:embed usage, I think we will eventually see a section in the “Effective Go” documentation. I am really curious on what will be the suggestions.

I hope you find this information useful, please let me know what are your thoughts about this great feature and share this article.

Thank you!