package xexec import ( "io/fs" "os" "path/filepath" "runtime" "strings" ) // findExecutable is from package os/exec func findExecutable(file string) error { d, err := os.Stat(file) if err != nil { return err } m := d.Mode() if !m.IsDir() && m&0111 != 0 { return nil } return fs.ErrPermission } // SearchPath searches for all executables that have prefix in their names in // the directories named by the PATH environment variable. func SearchPath(prefix string) ([]string, error) { var matches []string envPath := os.Getenv("PATH") dirSet := make(map[string]struct{}) for _, dir := range filepath.SplitList(envPath) { if dir == "" { // From exec package: // Unix shell semantics: path element "" means "." dir = "." } if _, ok := dirSet[dir]; ok { continue } dirSet[dir] = struct{}{} files, err := os.ReadDir(dir) if err != nil { continue } for _, f := range files { if strings.HasPrefix(f.Name(), prefix) { match := filepath.Join(dir, f.Name()) // Unideal but I don't want to maintain two separate implementations of this // function like os/exec. if runtime.GOOS == "windows" { matches = append(matches, match) continue } err = findExecutable(match) if err == nil { matches = append(matches, match) } } } } return matches, nil }