I have a string "12:34" that is "MM:SS" format and I want to convert it to a time.Duration. Wasted too much time on this already. What am I doing wrong in this code:
package main
import (
"fmt"
"strings"
"time"
)
func parseDuration(input string) (time.Duration, error) {
var layout string
if strings.Count(input, ":") == 1 {
layout = "04:05"
} else {
layout = "15:04:05"
}
t, err := time.Parse(layout, input)
if err != nil {
return 0, err
}
return t.Sub(time.Time{}), nil
}
func main() {
input := "00:04"
duration, err := parseDuration(input)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(int(duration.Seconds())) // I should get 4 but I get -31622396
}
https://go.dev/play/p/A-eHc-EPTrd
The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
func parseDuration(input string) (time.Duration, error) {
var layout string
if strings.Count(input, ":") == 1 {
layout = "04:05"
} else {
layout = "15:04:05"
}
t, err := time.Parse(layout, input)
if err != nil {
return 0, err
}
return t
}
fmt.Println(time.Time{})
// this prints 0001-01-01 00:00:00 +0000 UTC
fmt.Println(parseDuration("00:04"))
// this prints 0000-01-01 00:00:04 +0000 UTC
In your case, you should define a start object instead of using time.Time{} directly. For example,
package main
import (
"fmt"
"strings"
"time"
)
var origin = time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)
func parseDuration(input string) (time.Duration, error) {
var layout string
if strings.Count(input, ":") == 1 {
layout = "04:05"
} else {
layout = "15:04:05"
}
t, err := time.Parse(layout, input)
if err != nil {
return 0, err
}
return t.Sub(origin), nil
}
func main() {
input := "00:04"
duration, err := parseDuration(input)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(duration.String()) // this prints 4s
}
https://go.dev/play/p/maGeyA0KWd3
The issue is with the line return t.Sub(time.Time{}), nil. You're subtracting the zero value of time.Time from t, which is equivalent to subtracting the Unix epoch time from t.
To get the duration from the string, you should use time.ParseDuration() instead:
duration, err := time.ParseDuration(input)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(int(duration.Seconds()))
This will correctly parse the duration in the string, and you'll get the expected output of 4.
Related
I'm having a csv file, and want to read:
Header names
Fields types
So, I wrote the below:
package main
import (
"encoding/csv"
"fmt"
"os"
"log"
"reflect"
"strconv"
)
func main() {
filePath := "./file.csv"
headerNames := make(map[int]string)
headerTypes := make(map[int]string)
// Load a csv file.
f, _ := os.Open(filePath)
// Create a new reader.
r := csv.NewReader(f)
// Read first row only
header, err := r.Read()
checkError("Some other error occurred", err)
// Add mapping: Column/property name --> record index
for i, v := range header {
headerNames[i] = v
}
// Read second row
record, err := r.Read()
checkError("Some other error occurred", err)
// Check record fields types
for i, v := range record {
var value interface{}
if value, err = strconv.Atoi(v); err != nil {
if value, err = strconv.ParseFloat(v, 64); err != nil {
if value, err = strconv.ParseBool(v); err != nil {
if value, err = strconv.ParseBool(v); err != nil { // <== How to do this with unknown layout
// Value is a string
headerTypes[i] = "string"
value = v
fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
} else {
// Value is a timestamp
headerTypes[i] = "time"
fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
}
} else {
// Value is a bool
headerTypes[i] = "bool"
fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
}
} else {
// Value is a float
headerTypes[i] = "float"
fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
}
} else {
// Value is an int
headerTypes[i] = "int"
fmt.Println(reflect.TypeOf(value), reflect.ValueOf(value))
}
}
for i, _ := range header {
fmt.Printf("Header: %v \tis\t %v\n", headerNames[i], headerTypes[i])
}
}
func checkError(message string, err error) {
// Error Logging
if err != nil {
log.Fatal(message, err)
}
}
And with csv file as:
name,age,developer
"Hasan","46.4","true"
I got an output as:
Header: name is string
Header: age is float
Header: developer is bool
The output is correct.
The thing that I could not do is the one is checking if the field is string as I do not know what layout the field could be.
I aware I can pasre string to time as per the format stated at https://go.dev/src/time/format.go, and can build a custom parser, something like:
test, err := fmtdate.Parse("MM/DD/YYYY", "10/15/1983")
if err != nil {
panic(err)
}
But this will work only (as per my knowledge) if I know the layout?
So, again my question is, how can I parse time, or what shall I do to be able to parse it, if I do not know the layout?
Thanks to the comment by Burak, I found the solution by using this package: github.com/araddon/dateparse
// Normal parse. Equivalent Timezone rules as time.Parse()
t, err := dateparse.ParseAny("3/1/2014")
// Parse Strict, error on ambigous mm/dd vs dd/mm dates
t, err := dateparse.ParseStrict("3/1/2014")
> returns error
// Return a string that represents the layout to parse the given date-time.
layout, err := dateparse.ParseFormat("May 8, 2009 5:57:51 PM")
> "Jan 2, 2006 3:04:05 PM"
I have spent some time reading the code and docs of go-yaml, but I have not found any way to do this, except forking the project..
I want to extend the YAML unmarshaller so that it can accept a custom YAML tag (!include <file> in this case), which in turn would allow me to add support for including files. This is easily implemented with other YAML libraries, like in this answer.
Is there any way to accomplish this, using the public interface of the library (or another yaml library)?
Yes, this is possible (since v3). You can load the whole YAML file into a yaml.Node and then walk over the structure. The trick is that yaml.Node is an intermediate representation which you can only access if you define an unmarshaler.
For example:
package main
import (
"errors"
"fmt"
"io/ioutil"
"gopkg.in/yaml.v3"
)
// used for loading included files
type Fragment struct {
content *yaml.Node
}
func (f *Fragment) UnmarshalYAML(value *yaml.Node) error {
var err error
// process includes in fragments
f.content, err = resolveIncludes(value)
return err
}
type IncludeProcessor struct {
target interface{}
}
func (i *IncludeProcessor) UnmarshalYAML(value *yaml.Node) error {
resolved, err := resolveIncludes(value)
if err != nil {
return err
}
return resolved.Decode(i.target)
}
func resolveIncludes(node *yaml.Node) (*yaml.Node, error) {
if node.Tag == "!include" {
if node.Kind != yaml.ScalarNode {
return nil, errors.New("!include on a non-scalar node")
}
file, err := ioutil.ReadFile(node.Value)
if err != nil {
return nil, err
}
var f Fragment
err = yaml.Unmarshal(file, &f)
return f.content, err
}
if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
var err error
for i := range node.Content {
node.Content[i], err = resolveIncludes(node.Content[i])
if err != nil {
return nil, err
}
}
}
return node, nil
}
type MyStructure struct {
// this structure holds the values you want to load after processing
// includes, e.g.
Num int
}
func main() {
var s MyStructure
yaml.Unmarshal([]byte("!include foo.yaml"), &IncludeProcessor{&s})
fmt.Printf("Num: %v", s.Num)
}
Code prints Num: 42 when a file foo.yaml exists with the content num: 42.
Modified #flyx's original code a little to make it modular for adding custom resolvers.
package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v3"
)
var tagResolvers = make(map[string]func(*yaml.Node) (*yaml.Node, error))
type Fragment struct {
content *yaml.Node
}
func (f *Fragment) UnmarshalYAML(value *yaml.Node) error {
var err error
// process includes in fragments
f.content, err = resolveTags(value)
return err
}
type CustomTagProcessor struct {
target interface{}
}
func (i *CustomTagProcessor) UnmarshalYAML(value *yaml.Node) error {
resolved, err := resolveTags(value)
if err != nil {
return err
}
return resolved.Decode(i.target)
}
func resolveTags(node *yaml.Node) (*yaml.Node, error) {
for tag, fn := range tagResolvers {
if node.Tag == tag {
return fn(node)
}
}
if node.Kind == yaml.SequenceNode || node.Kind == yaml.MappingNode {
var err error
for i := range node.Content {
node.Content[i], err = resolveTags(node.Content[i])
if err != nil {
return nil, err
}
}
}
return node, nil
}
func resolveIncludes(node *yaml.Node) (*yaml.Node, error) {
if node.Kind != yaml.ScalarNode {
return nil, errors.New("!include on a non-scalar node")
}
file, err := ioutil.ReadFile(node.Value)
if err != nil {
return nil, err
}
var f Fragment
err = yaml.Unmarshal(file, &f)
return f.content, err
}
func resolveGetValueFromEnv(node *yaml.Node) (*yaml.Node, error) {
if node.Kind != yaml.ScalarNode {
return nil, errors.New("!getValueFromEnv on a non-scalar node")
}
value := os.Getenv(node.Value)
if value == "" {
return nil, fmt.Errorf("environment variable %v not set", node.Value)
}
var f Fragment
err := yaml.Unmarshal([]byte(value), &f)
return f.content, err
}
func AddResolvers(tag string, fn func(*yaml.Node) (*yaml.Node, error)) {
tagResolvers[tag] = fn
}
func main() {
// Register custom tag resolvers
AddResolvers("!include", resolveIncludes)
AddResolvers("!getValueFromEnv", resolveGetValueFromEnv)
type MyStructure struct {
// this structure holds the values you want to load after processing
// includes, e.g.
Num int
}
var s MyStructure
os.Setenv("FOO", `{"num": 42}`)
err := yaml.Unmarshal([]byte("!getValueFromEnv FOO"), &CustomTagProcessor{&s})
if err != nil {
panic("Error encountered during unmarshalling")
}
fmt.Printf("\nNum: %v", s.Num)
err = yaml.Unmarshal([]byte("!include foo.yaml"), &CustomTagProcessor{&s})
if err != nil {
panic("Error encountered during unmarshalling")
}
fmt.Printf("\nNum: %v", s.Num)
}
I have the following code which I can't run on play because I use the gin framework and filewalk.
package main
import (
"fmt"
"os"
"path/filepath"
"time"
"regexp"
"github.com/gin-gonic/gin"
"sort"
"strings"
"github.com/davidscholberg/go-durationfmt"
)
var filext = regexp.MustCompile(`\.[mM][pP]4|\.[mM]4[vV]|\.jpg|\.[hH]264|\.go`)
var m map[int]string
var keys []int
func main() {
if gin.IsDebugging() {
fmt.Print("This progamm shows only the path and the age of a file in 'STARTDIR'\n")
fmt.Print("only the following files will be handled '.[mM][pP]4|.[mM]4[vV$|.[hH]264|.go'\n")
fmt.Print("The git repository: https://gitlab.com/aleks001/show-files-age \n")
}
if len(os.Getenv("LISTEN_IP_PORT")) == 0 {
fmt.Print("I need a ip and port on which I should listen.\n")
os.Exit(1)
}
router := gin.Default()
gin.DisableConsoleColor()
router.GET("/videoinfo",getInfo)
router.Run(os.Getenv("LISTEN_IP_PORT"))
}
func getInfo(c *gin.Context) {
loc, _ := time.LoadLocation("Europe/Vienna")
var startdir = ""
if os.Getenv("STARTDIR") != "" {
startdir = os.Getenv("STARTDIR")
} else if c.GetHeader("STARTDIR") != "" {
startdir = c.GetHeader("STARTDIR")
} else {
c.String(404,"Startdir not found <br>\n")
return
}
m = make(map[int]string)
keys = nil
filepath.Walk(startdir,walkpath)
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
t := time.Date(time.Now().Year(),time.Now().Month(),time.Now().Day(),time.Now().Hour(),k,time.Now().Second(),time.Now().Nanosecond(),loc)
durStr, err := durationfmt.Format(time.Since(t), "%h:%m")
if err != nil {
fmt.Println(err)
} else {
//fmt.Println(durStr)
fmt.Printf("Key: %s Value: %s\n", durStr , m[k])
c.String(200,"Minutes: %s File: %s\n", durStr, m[k])
}
}
}
func walkpath(path string, f os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
} else {
if filext.MatchString(path) {
age := time.Now().Sub(f.ModTime())
path_new := strings.Replace(path,"/videos/","",1)
// path_new := strings.Replace(path,"..\\","",1)
/*
fmt.Printf("Path: %s, ModTime: %s, Age: %s <br>\n", walker.Path(), walker.Stat().ModTime(), age)
c.String(200,"Path: %s, ModTime: %s, Age: %s <br>\n", walker.Path(), walker.Stat().ModTime(), age)
*/
fmt.Printf("Path: %s, Age: %d age minutes %0.2f <br>\n", path_new, age, age.Minutes())
m[int(age.Minutes())]=path_new
//c.String(200,"Path: %s, Age: %0.2f <br>\n", path, age.Minutes())
}
//fmt.Printf("%s with %d bytes at motime %s\n", path,f.Size(), f.ModTime())
}
return nil
}
What I want to do is a sorted output of files based on filext als filter and the modtime as sort criteria.
I was able to fulfil the most part of the request but the output looks ugly as you can see below.
I have used https://github.com/davidscholberg/go-durationfmt to format the duration but the output looks ugly or I missus the library.
Minutes: 0:6 File: upload/dir003/file1.m4v
Minutes: 0:5 File: transfer/dir5/file2.jpg
Minutes: -5:-48 File: transfer/dir001/file.mp4
Minutes: -6:-21 File: transfer/03.jpg
Minutes: -6:-22 File: transfer/02.mp4
FYI, if you just want to quickly display a duration, the built-in formatting works well:
fmt.Sprintf("duration: %s", d)
will display something like this:
duration: 7h3m45s
For example, to provide a custom format for a duration,
package main
import (
"fmt"
"time"
)
func fmtDuration(d time.Duration) string {
d = d.Round(time.Minute)
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
return fmt.Sprintf("%02d:%02d", h, m)
}
func main() {
modTime := time.Now().Round(0).Add(-(3600 + 60 + 45) * time.Second)
since := time.Since(modTime)
fmt.Println(since)
durStr := fmtDuration(since)
fmt.Println(durStr)
}
Playground: https://play.golang.org/p/HT4bFfoA5r
Output:
1h1m45s
01:02
If you want to sort on a duration then use the Go sort package. I would sort on ModTime to defer the calculation of the duration, Since(ModTime), to be accurate at the time it is printed. For example,
package main
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
func isVideo(path string) bool {
videos := []string{".mp4", ".m4v", ".h264"}
ext := strings.ToLower(filepath.Ext(path))
for _, video := range videos {
if ext == video {
return true
}
}
return false
}
type modTimeInfo struct {
path string
modTime time.Time
}
func walkModTime(root string) ([]modTimeInfo, error) {
var infos []modTimeInfo
err := filepath.Walk(
root,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode().IsRegular() {
path = filepath.Clean(path)
if !isVideo(path) {
return nil
}
sep := string(filepath.Separator)
dir := sep + `Videos` + sep
path = strings.Replace(path, dir, sep, 1)
infos = append(infos, modTimeInfo{
path: path,
modTime: info.ModTime()},
)
}
return nil
},
)
if err != nil {
return nil, err
}
return infos, nil
}
func sortModTime(infos []modTimeInfo) {
sort.SliceStable(
infos,
func(i, j int) bool {
return infos[i].modTime.Before(infos[j].modTime)
},
)
}
func fmtAge(d time.Duration) string {
d = d.Round(time.Minute)
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
return fmt.Sprintf("%02d:%02d", h, m)
}
func main() {
root := `/home/peter/Videos` // Testing ...
infos, err := walkModTime(root)
if err != nil {
fmt.Println(err)
return
}
sortModTime(infos)
now := time.Now()
for _, info := range infos {
age := fmtAge(now.Sub(info.modTime))
fmt.Println("Age (H:M):", age, "File:", info.path)
}
}
Playground: https://play.golang.org/p/j2TUmJdAi4
Another way to format the duration if you don't care about the day, month or year
package main
import (
"fmt"
"time"
)
type Timespan time.Duration
func (t Timespan) Format(format string) string {
z := time.Unix(0, 0).UTC()
return z.Add(time.Duration(t)).Format(format)
}
func main() {
dur := 7777 * time.Second
fmt.Println(Timespan(dur).Format("15:04:05")) // 02:09:37
}
https://play.golang.org/p/XM-884oYMvE
Another easy way is to use the built in time.Duration String function.
duration.String()
Output: 28m26.550805438s
Or to round the seconds first
duration.Round(time.Second).String()
Output: 28m27s
Format yourself.
package main
import (
"fmt"
"time"
)
func main() {
d := 7777 * time.Second
hour := int(d.Seconds() / 3600)
minute := int(d.Seconds()/60) % 60
second := int(d.Seconds()) % 60
fmt.Printf("%d:%02d:%02d\n", hour, minute, second) // 02:09:37
}
https://go.dev/play/p/AFay62Qg2GB
Hi I've generated Md5 and uuid in golang but now I want generate it for multiple files using command line arguments, so what exactly I've to do. This is how I've generated my md5 and uuid:
package main
import (
"crypto/rand"
"crypto/md5"
"fmt"
"io"
"os"
"log"
"text/template"
)
type Data struct {
Uuid string
Md5 string
}
func main() {
uuid, err := newUUID()
if err != nil {
fmt.Printf("error: %v\n", err)
}
fmt.Printf("UUID: %s\n", uuid)
md5 := Getmd5(uuid)
fmt.Printf("Checksum: %s\n",md5)
fillData := Data{uuid, md5}
file, err := os.Create("text.txt")
if err != nil {
return
}
defer file.Close()
templ, err := template.ParseFiles("template.html")
if err !=nil{
log.Fatalln(err)
}
err = templ.Execute(file,fillData)
if err != nil{
log.Fatalln(err)
}
}
// newUUID generates a random UUID according to RFC 4122
func newUUID() (string, error) {
uuid := make([]byte, 16)
n, err := io.ReadFull(rand.Reader, uuid)
if n != len(uuid) || err != nil {
return "", err
}
// variant bits
uuid[8] = uuid[8]&^0xc0 | 0x80
// version 4 (pseudo-random)
uuid[6] = uuid[6]&^0xf0 | 0x40
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}
func Getmd5(uuid string) (string) {
data := []byte(uuid)
//md5_buffer := fmt.Sprintf("%x", md5.Sum(data))
md5_buffer := md5.Sum(data)
return fmt.Sprintf("{0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x};\n",md5_buffer[0:1],
md5_buffer[1:2],md5_buffer[2:3],md5_buffer[3:4],md5_buffer[4:5],md5_buffer[5:6],md5_buffer[6:7],md5_buffer[7:8],
md5_buffer[8:9],md5_buffer[9:10],md5_buffer[10:11],md5_buffer[11:12],md5_buffer[12:13],md5_buffer[13:14],md5_buffer[14:15],
md5_buffer[15:16])
}
Can anyone help me out?
You can use os.Args to accept command line arguements
os.Args provides access to raw command-line arguments. Note that the first value in this slice is the path to the program, and os.Args[1:] holds the arguments to the program.
Your program will look like this, have a look at createFile and getNumberOfFiles functions and the main
package main
import (
"crypto/md5"
"crypto/rand"
"errors"
"fmt"
"io"
"log"
"os"
"strconv"
"text/template"
)
type Data struct {
Uuid string
Md5 string
}
func createFile(uuid string) {
md5 := Getmd5(uuid)
fmt.Printf("Checksum: %s\n", md5)
fillData := Data{uuid, md5}
file, err := os.Create(uuid + ".txt")
if err != nil {
return
}
defer file.Close()
templ, err := template.ParseFiles("template.html")
if err != nil {
log.Fatalln(err)
}
err = templ.Execute(file, fillData)
if err != nil {
log.Fatalln(err)
}
}
func getNumberOfFiles() (num int, err error) {
if len(os.Args) == 1 {
return 0, errors.New("Not enough arguements")
}
if num, err = strconv.Atoi(os.Args[1]); err != nil {
return
}
return num, nil
}
func main() {
numberOfFiles, err := getNumberOfFiles()
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("Creating %d files", numberOfFiles)
for i := 0; i < numberOfFiles; i++ {
uuid, err := newUUID()
if err != nil {
fmt.Printf("error: %v\n", err)
}
createFile(uuid)
}
}
// newUUID generates a random UUID according to RFC 4122
func newUUID() (string, error) {
uuid := make([]byte, 16)
n, err := io.ReadFull(rand.Reader, uuid)
if n != len(uuid) || err != nil {
return "", err
}
// variant bits
uuid[8] = uuid[8]&^0xc0 | 0x80
// version 4 (pseudo-random)
uuid[6] = uuid[6]&^0xf0 | 0x40
return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}
func Getmd5(uuid string) string {
data := []byte(uuid)
//md5_buffer := fmt.Sprintf("%x", md5.Sum(data))
md5_buffer := md5.Sum(data)
return fmt.Sprintf("{0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x};\n", md5_buffer[0:1],
md5_buffer[1:2], md5_buffer[2:3], md5_buffer[3:4], md5_buffer[4:5], md5_buffer[5:6], md5_buffer[6:7], md5_buffer[7:8],
md5_buffer[8:9], md5_buffer[9:10], md5_buffer[10:11], md5_buffer[11:12], md5_buffer[12:13], md5_buffer[13:14], md5_buffer[14:15],
md5_buffer[15:16])
}
I want to get the process id by the process name in windows environment?
I find golang only has the api os.FindProcess(id),but no by name.
I had to struggle with this too, and found the way to the solution not very straightforward, becauseā¦ WinApi :)
In the end you have to create a snapshot of the current windows process list using CreateToolhelp32Snapshot. Then you get the first process in the snapshot with Process32First. After that keep iterating over the list with Process32Next, until you get the ERROR_NO_MORE_FILES error. Only then you have the whole process list.
See how2readwindowsprocesses for a working example.
Here is the gist:
const TH32CS_SNAPPROCESS = 0x00000002
type WindowsProcess struct {
ProcessID int
ParentProcessID int
Exe string
}
func processes() ([]WindowsProcess, error) {
handle, err := windows.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer windows.CloseHandle(handle)
var entry windows.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
// get the first process
err = windows.Process32First(handle, &entry)
if err != nil {
return nil, err
}
results := make([]WindowsProcess, 0, 50)
for {
results = append(results, newWindowsProcess(&entry))
err = windows.Process32Next(handle, &entry)
if err != nil {
// windows sends ERROR_NO_MORE_FILES on last process
if err == syscall.ERROR_NO_MORE_FILES {
return results, nil
}
return nil, err
}
}
}
func findProcessByName(processes []WindowsProcess, name string) *WindowsProcess {
for _, p := range processes {
if strings.ToLower(p.Exe) == strings.ToLower(name) {
return &p
}
}
return nil
}
func newWindowsProcess(e *windows.ProcessEntry32) WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return WindowsProcess{
ProcessID: int(e.ProcessID),
ParentProcessID: int(e.ParentProcessID),
Exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}
You can list all the processes and match them with the name you want to find, by using the updated sys call package, https://godoc.org/golang.org/x/sys,
it has most of the windows api.
func Process32First(snapshot Handle, procEntry *ProcessEntry32) (err error)
func Process32Next(snapshot Handle, procEntry *ProcessEntry32) (err error)
also see the msdn docs:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms684834(v=vs.85).aspx
const TH32CS_SNAPPROCESS = 0x00000002
type WindowsProcess struct {
ProcessID int
ParentProcessID int
Exe string
}
func newWindowsProcess(e *syscall.ProcessEntry32) WindowsProcess {
// Find when the string ends for decoding
end := 0
for {
if e.ExeFile[end] == 0 {
break
}
end++
}
return WindowsProcess{
ProcessID: int(e.ProcessID),
ParentProcessID: int(e.ParentProcessID),
Exe: syscall.UTF16ToString(e.ExeFile[:end]),
}
}
func processes() ([]WindowsProcess, error) {
handle, err := syscall.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer syscall.CloseHandle(handle)
var entry syscall.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
// get the first process
err = syscall.Process32First(handle, &entry)
if err != nil {
return nil, err
}
results := make([]WindowsProcess, 0, 50)
for {
results = append(results, newWindowsProcess(&entry))
err = syscall.Process32Next(handle, &entry)
if err != nil {
// windows sends ERROR_NO_MORE_FILES on last process
if err == syscall.ERROR_NO_MORE_FILES {
return results, nil
}
return nil, err
}
}
}
func findProcessByName(processes []WindowsProcess, name string) *WindowsProcess {
for _, p := range processes {
if bytes.Contains([]byte(strings.ToUpper(p.Exe)), []byte(strings.ToUpper(name))) {
return &p
}
}
return nil
}
This seems to do it:
package main
import (
"fmt"
"golang.org/x/sys/windows"
)
// unsafe.Sizeof(windows.ProcessEntry32{})
const processEntrySize = 568
func processID(name string) (uint32, error) {
h, e := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
if e != nil { return 0, e }
p := windows.ProcessEntry32{Size: processEntrySize}
for {
e := windows.Process32Next(h, &p)
if e != nil { return 0, e }
if windows.UTF16ToString(p.ExeFile[:]) == name {
return p.ProcessID, nil
}
}
return 0, fmt.Errorf("%q not found", name)
}
func main() {
n, e := processID("WindowsTerminal.exe")
if e != nil {
panic(e)
}
println(n)
}
https://pkg.go.dev/golang.org/x/sys/windows#CreateToolhelp32Snapshot