path.split strange behavior - go

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.

Related

Getting an error when trying to check path

I am trying to check windows dir in my golang app.
Here is my code
func createWalletDirectory(path string) (err error) {
_, err = os.Stat(path)
if os.IsNotExist(err) {
return err
}
path = filepath.FromSlash(path)
path = path + string(os.PathSeparator) + DirectoryName
err = os.Mkdir(path, 0666)
return
}
So on the first line of the function I am getting an error look like this
invalid character 'i' in string escape code
Example path : C:\Users
Note: The path I am getting from users via POST request
So I need to make a code which will check crossplatform paths.
How can I solve this error ?
You can use path package to work with the urls('path/filepath' for the file paths) which also contributes in platform independency. So you can do following to create the path
givenPath = filepath.Join(DirectoryName, path)
There is also another way of doing this
path := strings.Join([]string{DirectoryName, path}, string(os.PathSeparator))
In Go strings enclosed by double quotes, a backslash starts an escape code, e.g. \n or \u2318. To avoid this, you have two options:
use a double backslash (\\), e.g. "C:\\Users"
use backticks (`) instead of double quotes to define a "raw string", e.g. `C:\Users`
Further reading

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

How to find a package name from given call in runtime?

For logging purpose I want to write a function which will print a package name.
I can do it for a directory name:
// file is the full file name
// 4 - how many calls we want to go up in a stack trace.
_, file, line, ok := runtime.Caller(4)
... but can't find a way for package name (package name can be different from directory name).
I came across a similar problem - from a package path how do you get the package name. The best solution I found is to exec the "go list" command. Not ideal but I came up blank elsewhere.
In my case I also had a problem that sometimes the package is an empty directory. With no source files, "go list" throws an error, so I added a function to create a sensible package name from the path.
Here's the code:
func getPackageName(path string) string {
output, err := exec.Command("go", "list", "-f", "{{.Name}}", path).CombinedOutput()
if err != nil {
return guessPackageName(path)
}
return strings.TrimSpace(string(output))
}
func guessPackageName(path string) string {
preferred := path
if strings.HasSuffix(preferred, "/") {
// training slashes are usually tolerated, so we can get rid of one if it exists
preferred = preferred[:len(preferred)-1]
}
if strings.Contains(preferred, "/") {
// if the path contains a "/", use the last part
preferred = preferred[strings.LastIndex(preferred, "/")+1:]
}
if strings.Contains(preferred, "-") {
// the name usually follows a hyphen - e.g. github.com/foo/go-bar
// if the package name contains a "-", use the last part
preferred = preferred[strings.LastIndex(preferred, "-")+1:]
}
if strings.Contains(preferred, ".") {
// dot is commonly usually used as a version - e.g. github.com/foo/bar.v1
// if the package name contains a ".", use the first part
preferred = preferred[:strings.LastIndex(preferred, ".")]
}
return preferred
}

Golang Flag gets interpreted as first os.Args argument

I would like to run my program like this:
go run launch.go http://example.com --m=2 --strat=par
"http://example.com" gets interpreted as the first command line argument, which is ok, but the flags are not parsed after that and stay at the default value. If I put it like this:
go run launch.go --m=2 --strat=par http://example.com
then "--m=2" is interpreted as the first argument (which should be the URL).
I could also just remove the os.Args completely, but then I would have only optional flags and I want one (the URL) to be mandatory.
Here's my code:
package main
import (
"fmt"
"webcrawler/crawler"
"webcrawler/model"
"webcrawler/urlutils"
"os"
"flag"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Url must be provided as first argument")
}
strategy := flag.String("strat", "par", "par for parallel OR seq for sequential crawling strategy")
routineMultiplier := flag.Int("m", 1, "Goroutine multiplier. Default 1x logical CPUs. Only works in parallel strategy")
page := model.NewBasePage(os.Args[1])
urlutils.BASE_URL = os.Args[1]
flag.Parse()
pages := crawler.Crawl(&page, *strategy, *routineMultiplier)
fmt.Printf("Crawled: %d\n", len(pages))
}
I am pretty sure that this should be possible, but I can't figure out how.
EDIT:
Thanks justinas for the hint with the flag.Args(). I now adapted it like this and it works:
...
flag.Parse()
args := flag.Args()
if len(args) != 1 {
log.Fatal("Only one argument (URL) allowed.")
}
page := model.NewBasePage(args[0])
...
os.Args doesn't really know anything about the flag package and contains all command-line arguments. Try flag.Args() (after calling flag.Parse(), of course).
As a followup, to parse flags that follow a command like
runme init -m thisis
You can create your own flagset to skip the first value like
var myValue string
mySet := flag.NewFlagSet("",flag.ExitOnError)
mySet.StringVar(&myValue,"m","mmmmm","something")
mySet.Parse(os.Args[2:])
This tripped me up too, and since I call flag.String/flag.Int64/etc in a couple of places in my app, I didn't want to have to pass around a new flag.FlagSet all over the place.
// If a commandline app works like this: ./app subcommand -flag -flag2
// `flag.Parse` won't parse anything after `subcommand`.
// To still be able to use `flag.String/flag.Int64` etc without creating
// a new `flag.FlagSet`, we need this hack to find the first arg that has a dash
// so we know when to start parsing
firstArgWithDash := 1
for i := 1; i < len(os.Args); i++ {
firstArgWithDash = i
if len(os.Args[i]) > 0 && os.Args[i][0] == '-' {
break
}
}
flag.CommandLine.Parse(os.Args[firstArgWithDash:])
The reason I went with this is because flag.Parse just calls flag.CommandLine.Parse(os.Args[1:]) under the hood anyway.
You can check if the Arg starts with "--" or "-" and avoid using that Arg in a loop.
For example:
for _, file := range os.Args[1:] {
if strings.HasPrefix(file, "--") {
continue
}
//do stuff
}

Resources