Как получить путь к директории текущего исполняемого файла в Go?
Правильное получение директории текущего исполняемого файла
Вы на верном пути, используя os.Executable(), но есть важные нюансы, особенно при работе с символическими ссылками и разными средами запуска. Рассмотрим надёжное решение.
Основной подход через os.Executable()
Функция os.Executable() возвращает абсолютный путь к текущему исполняемому файлу. Это самый надёжный способ, но его нужно правильно обрабатывать:
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
// Получаем путь к исполняемому файлу
exePath, err := os.Executable()
if err != nil {
panic(err)
}
fmt.Println("Executable path:", exePath)
// Разрешаем символические ссылки, если они есть
realPath, err := filepath.EvalSymlinks(exePath)
if err != nil {
panic(err)
}
fmt.Println("Real path:", realPath)
// Получаем директорию
dir := filepath.Dir(realPath)
fmt.Println("Directory:", dir)
// Формируем путь к конфигу
configPath := filepath.Join(dir, "configs", "config.yaml")
fmt.Println("Config path:", configPath)
}
Почему это важно?
- Символические ссылки: Если ваш бинарник запущен через симлинк,
os.Executable()вернёт путь к ссылке, а не к реальному файлу.filepath.EvalSymlinksрешает эту проблему. - go run vs скомпилированный бинарник: При
go runGo создаёт временный бинарник во временной папке.os.Executable()вернёт путь к этому временному файлу, что может быть неожиданно. Для разработки лучше использовать рабочий каталог или отдельный тестовый режим. - Кроссплатформенность: Решение работает на Windows, Linux и macOS.
Альтернатива: filepath.Abs + os.Args[0]
Иногда можно использовать os.Args[0] в комбинации с filepath.Abs:
func getExecDir() (string, error) {
// Получаем имя программы из аргументов
prog := os.Args[0]
// Преобразуем в абсолютный путь
absPath, err := filepath.Abs(prog)
if err != nil {
return "", err
}
// Разрешаем симлинки
realPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
return "", err
}
return filepath.Dir(realPath), nil
}
Рекомендации для продакшена
Для production-приложений рекомендую:
- Использовать
os.Executable()сfilepath.EvalSymlinks - Добавить флаг командной строки для переопределения пути (например,
--config-dir) - Рассмотреть использование переменных окружения для гибкости
Ваш пример с конфигом будет работать стабильно, если добавить обработку ошибок и разрешение симлинков. Помните, что при go run вы получите путь к временной папке — это нормальное поведение для разработки.
Использование runtime.Caller для получения пути к исходному файлу
Если вам нужен не путь к бинарнику, а путь к исходному .go файлу (например, для логирования или тестов), используйте runtime.Caller:
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func main() {
_, filename, _, ok := runtime.Caller(0)
if !ok {
panic("No caller information")
}
// Получаем директорию текущего исходного файла
dir := filepath.Dir(filename)
fmt.Println("Source directory:", dir)
// Путь к конфигу относительно исходника
configPath := filepath.Join(dir, "..", "configs", "config.yaml")
fmt.Println("Config path:", configPath)
}
Важное отличие: Этот метод возвращает путь к файлу main.go во время компиляции, а не к скомпилированному бинарнику. Он полезен для:
- Тестов, где нужно найти тестовые данные рядом с тестовым файлом
- Инструментов разработки, которые работают с исходным кодом
- Приложений, которые всегда запускаются из одной и той же директории проекта
Однако для production-приложений этот метод не подходит, так как после компиляции и перемещения бинарника пути к исходникам теряют смысл. Для вашей задачи с конфигами лучше использовать os.Executable() из первого ответа.