How do I get the root directory of a relative path - go

I need to extract the name first of the first directory in a relative path.
I know I can go about:
relPath := "a/b/c/file.so"
splitPath := strings.Split(relPath, string(os.PathSeparator))
rootDirName := splitPath[0]
Is there a better way?

If you're asking whether there is way to do it with 1 standard Go function: not that I know of.
An alternative way would be:
relPath := "a/b/c/file.so"
i := strings.Index(relPath, string(os.PathSeparator))
rootDirName := relPath[:i]
Or if it is possible that the path contains no / at all:
relPath := "a/b/c/file.so"
i := strings.Index(relPath, string(os.PathSeparator))
rootDirName := ""
if i != -1 {
rootDirName = relPath[:i]
}
This has the benefit of not having to split the whole string and might, therefore, be a little faster on long paths.

Related

path.split strange behavior

it seems that there is an issue with the split method in path/filepath
I have looked at the debugger and it seems like it does not split the path into
sections
go version:1.19
debugger output:https://imgur.com/a/VqgQznh
case fsnotify.Rename:
dir, filename := path.Split(event.Name)
fileIndex := indexOfFile(filename, s.LoggedFiles[dir])
if fileIndex == -1 {
errChannel <- errors.New("file path does not exist in map")
break
}
s.LoggedFiles[dir] = append(s.LoggedFiles[dir], s.LoggedFiles[dir][fileIndex])
fmt.Println(s.LoggedFiles[dir])
}
The path.Split() uses the / separator.
To split OS specific directories you should use the path/filepath.Split() that uses the os.PathSeparator.

How to create an empty file in golang at a specified path. Lets say at $HOME/newFile.txt

Trying to create a new file at a specified path. I tried using filepath.abs() but it doesn't give the absolute path.
For example:
filePath, _ := filepath.Abs("$HOME/internship.txt")
f, err := os.Create(filePath)
this code doesnt give me the absolute path of $HOME/internship.txt; instead it gives me the path of the current directory plus $HOME/internship.txt
You should use "os" library(the one you already imported). Example:
filePath, _ := filepath.Abs(os.Getenv("HOME") + "/internship.txt")
or (As Peter said)
home, _ := os.UserHomeDir()
filePath, _ := filepath.Abs(home + "/internship.txt")
Go is not a Unix shell.

Get the parent path

I am creating Go command-line app and I need to generate some stuff in the current directory (the directory which the user execute the commands from)
to get the pwd I need to use
os.Getwd()
but this give me path like
/Users/s05333/go/src/appcmd
and I need path like this
/Users/s05333/go/src/
which option I've in this case?
Omit the last string after the / or there is better way in Go?
Take a look at the filepath package, particularly filepath.Dir:
wd,err := os.Getwd()
if err != nil {
panic(err)
}
parent := filepath.Dir(wd)
Per the docs:
Dir returns all but the last element of path, typically the path's directory.
Another option is the path package:
package main
import "path"
func main() {
s := "/Users/s05333/go/src/appcmd"
t := path.Dir(s)
println(t == "/Users/s05333/go/src")
}
https://golang.org/pkg/path#Dir

Trim multiple characters to right of final slash including slash

Golang doesn't have the strrchr function that php does. If I want to remove /path (including the final slash) from this string, how does one do it in golang?
mystr := "/this/is/my/path"
Desired output
"/this/is/my"
I can get the index of the final slash like this
lastSlash := strings.LastIndex(mystr, "/")
but I'm not sure how to create a new string with /path removed. How to do that?
Try output := mystr[:strings.LastIndex(mystr, "/")]
mystr := "/this/is/my/path"
idx := strings.LastIndex(mystr, "/")
if idx != -1{
mystr = mystr[:idx]
}
fmt.Println(mystr)
playground link
captncraig's answer works for any type of separator char, but assuming you are running on a POSIX-style machine ("/" is the path separator) and what you are manipulating are indeed paths:
http://play.golang.org/p/oQbXTEhH30
package main
import (
"fmt"
"path/filepath"
)
func main() {
s := "/this/is/my/path"
fmt.Println(filepath.Dir(s))
// Output: /this/is/my
}
From the godoc (https://golang.org/pkg/path/filepath/#Dir):
Dir returns all but the last element of path, typically the path's directory. After dropping the final element, the path is Cleaned and trailing slashes are removed.
Though if you run it with /path, it will return /, which may or may not be what you want.
One corner case not covered by the previous (quite satisfactory) solutions is that of a trailing /. Ie - if you wanted /foo/bar/quux/ trimmed to /foo/bar rather than /foo/bar/quux. That can be accomplished with the regexp library:
mystr := "/this/is/my/path/"
trimpattern := regexp.MustCompile("^(.*?)/[^/]*/?$")
newstr := trimpattern.ReplaceAllString(mystr, "$1")
fmt.Println(newstr)
There's a bit fuller example here: http://play.golang.org/p/ii-svpbaHt

check if given path is a subdirectory of another in golang

Say we have two paths:
c:\foo\bar\baz and c:\foo\bar
Is there any package/method that will help me determine if one is a subdirectory of another? I am looking at a cross-platform option.
You could try and use path.filepath.Rel():
func Rel(basepath, targpath string) (string, error)
Rel returns a relative path that is lexically equivalent to targpath when joined to basepath with an intervening separator.
That is, Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself
That means Rel("c:\foo\bar", "c:\foo\bar\baz") should be baz, meaning a subpath completely included in c:\foo\bar\baz, and without any '../'.
The same would apply for unix paths.
That would make c:\foo\bar\baz a subdirectory of c:\foo\bar.
I haven't found a reliable solution for all types of paths, but the best you can get is by using filepath.Rel as VonC suggested.
It works if both filepaths are either absolute or relative (mixing is not allowed) and works on both Windows and Linux:
func SubElem(parent, sub string) (bool, error) {
up := ".." + string(os.PathSeparator)
// path-comparisons using filepath.Abs don't work reliably according to docs (no unique representation).
rel, err := filepath.Rel(parent, sub)
if err != nil {
return false, err
}
if !strings.HasPrefix(rel, up) && rel != ".." {
return true, nil
}
return false, nil
}
Absolute windows paths that start with a drive letter will require an additional check though.
You can use the function path.filepath.Match()
Match reports whether name matches the shell file name pattern.
For example:
pattern := "C:\foo\bar" + string(filepath.Separator) + "*"
matched, err := filepath.Match(pattern, "C:\foo\bar\baz")
Where matched should be true.
If you first canonicalize both paths by calling filepath.EvalSymlinks() and filepath.Abs() on them, you can simply append a '/' to each one, since the UNIX kernel itself forbids a '/' within a path component. At this point you can simply use strings.HasPrefix() on the two paths, in either order.
Try this code. This checks if either is a sub-directory of the other. Try changing values of both base and path and the results should be valid.
package main
import (
"fmt"
"path/filepath"
"strings"
)
func main() {
base := "/b/c/"
path := "/a/b/c/d"
if len(base) > len(path) {
base, path = path, base
}
rel, err := filepath.Rel(base, path)
fmt.Printf("Base %q: Path %q: Rel %q Err %v\n", base, path, rel, err)
if err != nil {
fmt.Println("PROCEED")
return
}
if strings.Contains(rel, "..") {
fmt.Println("PROCEED")
return
}
fmt.Println("DENY")
}

Resources