resolve symlinks in Go
Asked Answered
R

3

24

How can I resolve symlinks in Go?

Currently I call readlink -f but I want something more idiomatic.

package main

import (
    "os/exec"
    "fmt"
)

func resolve(p string) string {
    cmd := exec.Command("readlink", "-f", p)
    out, _ := cmd.Output()

    return (string(out))
}

func main() {
    fmt.Printf(resolve("/initrd.img"))
}
Redeploy answered 5/8, 2013 at 15:33 Comment(0)
M
26

Use os.Lstat:

func Lstat(name string) (fi FileInfo, err error)

Lstat returns a FileInfo describing the named file. If the file is a symbolic link, the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link. If there is an error, it will be of type *PathError.

EDIT:

Then returned os.FileInfo will only allow you to check if 'name' is a link or not (fi.Mode() & os.ModeSymlink != 0). If it is, then use os.Readlink to get the pointee:

func Readlink(name string) (string, error)

Readlink returns the destination of the named symbolic link. If there is an error, it will be of type *PathError.

Mernamero answered 5/8, 2013 at 15:35 Comment(3)
where does the FileInfo struct provide the target of the link?Redeploy
s, _ := os.Readlink(p); return s it is.Redeploy
this doesn't seem to work for recursive links unless used in a loop. Is there a simpler way to get folder and filename of the final target of the symlink?Premeditation
C
40

See filepath.EvalSymlinks().

EvalSymlinks returns the path name after the evaluation of any symbolic links. If path is relative the result will be relative to the current directory, unless one of the components is an absolute symbolic link.

Examples

Tree:

/bin/sh -> bash
/usr/lib/libresolv.so -> ../../lib/libresolv.so.2

os.Readlink()

os.Readlink("/bin/sh") // => bash
os.Readlink("/usr/lib/libresolv.so") //=> ../../lib/libresolv.so.2

filepath.EvalSymlinks()

filepath.EvalSymlinks("/bin/sh") // => /bin/bash
filepath.EvalSymlinks("/usr/lib/libresolv.so") //=> /lib/libresolv-2.20.so

Note: not absolute path (cd /bin)

 filepath.EvalSymlinks("sh") // => bash
 filepath.EvalSymlinks("/bin/sh") // => /bin/bash

And somthing more

 filepath.EvalSymlinks("/bin/bash") // => /bin/bash
 // but
 os.Readlink("/bin/bash") // => error: readlink /bin/bash: invalid argument

Example application not for playground

Crockett answered 31/1, 2015 at 6:47 Comment(0)
M
26

Use os.Lstat:

func Lstat(name string) (fi FileInfo, err error)

Lstat returns a FileInfo describing the named file. If the file is a symbolic link, the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link. If there is an error, it will be of type *PathError.

EDIT:

Then returned os.FileInfo will only allow you to check if 'name' is a link or not (fi.Mode() & os.ModeSymlink != 0). If it is, then use os.Readlink to get the pointee:

func Readlink(name string) (string, error)

Readlink returns the destination of the named symbolic link. If there is an error, it will be of type *PathError.

Mernamero answered 5/8, 2013 at 15:35 Comment(3)
where does the FileInfo struct provide the target of the link?Redeploy
s, _ := os.Readlink(p); return s it is.Redeploy
this doesn't seem to work for recursive links unless used in a loop. Is there a simpler way to get folder and filename of the final target of the symlink?Premeditation
A
0

I had the problem that filepath.EvalSymlinks() prepended /private/ to resolved links that were in /tmp/ on Mac (maybe symlinks are not supposed to be pointing there).

os.Readlink() on the other hand, returned an absolute path on Mac, but a relative path on Ubuntu.

Therefore, to be sure to retrieve an absolute path, the following solution worked for me:

resolvedLink, _ := os.Readlink(someSymlink) // TODO: Handle error here
if !filepath.IsAbs(resolvedLink) { // Output of os.Readlink is OS-dependent...
    resolvedLink = path.Join(path.Dir(someSymlink), resolvedLink)
}
Asir answered 16/2, 2022 at 12:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.