In this article, we will walk through some examples of getting the directory of the currently running program. We compile a Go program for various platforms and run it by calling a relative path or just by its name, how every sometimes we need to know where is the executable file. With the help of some built-in packages, we can easily get the source file location or the executable file.
Method-1: The os package and Executable() function
Package os provides a platform-independent interface to operating system functionality. The design is Unix-like, although the error handling is Go-like; failing calls return values of type error rather than error numbers. Often, more information is available within the error.
func Executable() (string, error)
: Executable returns the path name for the executable that started the current process. There is no guarantee that the path is still pointing to the correct executable.
If a symlink was used to start the process, depending on the operating system, the result might be the symlink or the path it pointed to. If a stable result is needed, path/filepath.EvalSymlinks might help. Executable returns an absolute path unless an error occurred. The main use case is finding resources located relative to an executable.
To get the directory of the executable you can use path/filepath.Dir
.
Here is an example of printing out the location of the running file with the Executable()
function:
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
ex, err := os.Executable()
if err != nil {
panic(err)
}
// file executable
fmt.Println(ex)
// the executable directory
exPath := filepath.Dir(ex)
fmt.Println(exPath)
}
Output:
C:\Users\nguye\AppData\Local\Temp\go-build3009376824\b001\exe\ospath.exe
C:\Users\nguye\AppData\Local\Temp\go-build3009376824\b001\exe
Method-2: file/filepath and the Abs() function
Package filepath implements utility routines for manipulating filename paths in a way compatible with the target operating system-defined file paths.
func Abs(path string) (string, error)
: Abs returns an absolute representation of a path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path. The absolute path name for a given file is not guaranteed to be unique. Abs calls Clean on the result.
Here is an example of using the Abs()
function to get the current Golang executable file:
package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal(err)
}
fmt.Println(dir)
}
Output:
C:\Users\nguye\AppData\Local\Temp\go-build2221200443\b001\exe
Explanation: the Args hold the command-line arguments, starting with the program name. A problem with this solution: this uses os.Args
which is creating a dependency over os.Args
, restricts it from getting the path of files in other modules in the application. The os.Args[0] value are arbitrary and cannot be relied on; os.Args[0] can be "faked".
Method-3: Using the osext package
osext
is the extension to the "os" package. You can get this package by running the command below:
go get -u github.com/kardianos/osext
The code below shows an easy way to get the executable file's location using the osext
package:
package main
import (
"fmt"
"log"
"github.com/kardianos/osext"
)
func main() {
folderPath, err := osext.ExecutableFolder()
if err != nil {
log.Fatal(err)
}
fmt.Println(folderPath)
}
Output:
C:\Users\nguye\AppData\Local\Temp\go-build287942699\b001\exe
Method-4: Get the source file location
Sometimes, you do not want to find the location of the executable file, you want to know where the source file is located. To do that, we can use the Abs() function from the path package introduced above:
package main
import (
"fmt"
"log"
"path/filepath"
)
func main() {
dir, err := filepath.Abs("./")
if err != nil {
log.Fatal(err)
}
fmt.Println(dir)
}
Another way to get the current source file location is to use runtime.Caller()
function:
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
: Caller reports file and line number information about function invocations on the calling goroutine's stack. The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.)
The return values report the program counter, file name, and line number within the file of the corresponding call. The boolean ok is false if it was not possible to recover the information.
You can use the code below to get the current source file location with the runtime.Caller
function:
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func main() {
_, filename, _, ok := runtime.Caller(0)
if !ok {
fmt.Println("Unable to get the current filename")
}
dirname := filepath.Dir(filename)
fmt.Println(dirname)
}
Output:
Summary
The Golang programming language makes it easy to build simple, reliable, and efficient software. You can get the current directory of the source code you are running or the executable file. With the os
and file/filepath
built-in package, we can simply get this information. We can also use a third-party library (osext)
for getting the executable path.
References
https://github.com/kardianos/osext
https://pkg.go.dev/os@master#Executable
https://pkg.go.dev/path/filepath#Abs
https://pkg.go.dev/runtime#Callers