I'm trying to figure out how to (or if it's possible to) combine multiple assignment and ranges in Golang
ex pseudo code of what I'd like to do
files := [2]*os.File{}
for i, _, fileName := 0, range os.Args[1:3] {
files[i], _ = os.Open(fileName)
}
The idea being I want to have both an iteration counter (i) and the filenames (fileName). I know this can be achieved by using the key from range and some math (key -1), thats not the point of the example.
Edit:
Upon debugging the above example, I learned that i will range 0-1 in that example; Because os.Args[1:2] is a slice and that slice has indexing 0-1 . Therefore I dont need "some math" to properly index the keys.
** EDIT 2: **
This post is also a must read as to why the above [2]*os.File{} is not idiomatic go, instead it should not have a size specified (files := []*os.File{}) so that files is of type slice of *os.File
There are a lot of different issues here. First, range already does what you want. There's no need for even math.
for i, fileName := range os.Args[1:] {
i will range from 0 to 1 here, just like you want. Ranging over a slice always starts at index 0 (it's relative to the start of the slice). (http://play.golang.org/p/qlVM6Y7yPD)
Note that os.Args[1:2] is just one element. You probably meant it to be two.
In any case, this is likely what you really meant:
http://play.golang.org/p/G4yfkKrEe7
files := make([]*os.File, 0)
for _, fileName := range os.Args[1:] {
f, err := os.Open(fileName)
if err != nil {
log.Fatalf("Could not open file: %v", err)
}
files = append(files, f)
}
fmt.Printf("%v\n", files)
Fixed-length arrays are very uncommon in Go. Generally you want a slice, created with make.
For example,
so.go:
package main
import (
"fmt"
"os"
)
func main() {
files := [2]*os.File{}
for i, fileName := range os.Args[1:] {
if i >= len(files) {
break
}
var err error
files[i], err = os.Open(fileName)
if err != nil {
// handle error
}
}
fmt.Println(files)
}
Output:
$ go build so.go && ./so no.go so.go to.go
[<nil> 0xc820030020]
$
Related
I would like to parse a package and output all of the strings in the code. The specific use case is to collect sql strings and run them through a sql parser, but that's a separate issue.
Is the best way to do this to just parse this line by line? Or is it possible to regex this or something? I imagine that some cases might be nontrivial, such as multiline strings:
str := "This is
the full
string"
// want > This is the full string
Use the go/scanner package to scan for strings in Go source code:
src, err := os.ReadFile(fname)
if err != nil {
/// handle error
}
// Create *token.File to scan.
fset := token.NewFileSet()
file := fset.AddFile(fname, fset.Base(), len(src))
var s scanner.Scanner
s.Init(file, src, nil, 0)
for {
pos, tok, lit := s.Scan()
if tok == token.EOF {
break
}
if tok == token.STRING {
s, _ := strconv.Unquote(lit)
fmt.Printf("%s: %s\n", fset.Position(pos), s)
}
}
https://go.dev/play/p/849QsbqVhho
I'm trying to use gomock to mock an interface that takes a decimal. The usecase:
v, err := decimal.NewFromString(order.Value)
if err != nil {
return err
}
if err := p.CDI.CreateBuyEvent(ctx, v); err != nil {
return err
}
And in the tests:
value := decimal.NewFromFloat64(1000)
cfg.cdi.EXPECT().CreateBuyEvent(ctx, value).Return(nil)
Running this, I get:
expected call doesn't match the argument at index 1.
Got: 1000 (entities.Order)
Want: is equal to 1000 (entities.Order)
However, if I instead instantiate the decimal using NewFromString("1000") in the tests, it passes. My question is: why is the underlying value different for NewFromString and NewFromFloat?
Because the two values happen to have different memory representations while logically containing the same values.
Use the Equal method to compare shopspring.Decimal values.
Demonstration:
package main
import (
"fmt"
"github.com/shopspring/decimal"
)
func main() {
fs, err := decimal.NewFromString("1000")
if err != nil {
panic(err)
}
ff := decimal.NewFromFloat(1000)
if ff != fs {
fmt.Printf("%#v != %#v\n", fs, ff)
}
fmt.Println(ff.Equal(fs))
}
Produces:
decimal.Decimal{value:(*big.Int)(0xc0001103a0), exp:0} != decimal.Decimal{value:(*big.Int)(0xc0001103c0), exp:3}
true
Playground.
I would add that there's no need to rush for SO to ask a question like this: you should have first performed at least minimal debugging.
Really, if you have two values which must be the same but they do not compare equal using the == operator which is dumb — it compares struct vaues field-wise, — just see what the values actually contain.
I try to do something very simple in Go but I do not manage to find any resources.
I receive an hexadump and I want to write it to a file but the content of both files (src and dst) do not match at all. Currently the only way I have find it's to manually add \x every 2 characters.
I tried to loop over my string and add \x the string looks identical but output is very different.
This code manually works:
binary.Write(f, binary.LittleEndian, []byte("\x00\x00\x00\x04\x0A\xFA\x64\xA7\x00\x03\x31\x30"))
But I did not manage to make it from string "000000040afa64a700033130"...
What i currently do (this is what I do in python3):
text := "000000040afa64a700033130"
j := 0
f, _ := os.OpenFile("gotest", os.O_WRONLY|os.O_CREATE, 0600)
for i := 0; i < len(text); i += 2 {
if (i + 2) <= len(text) {
j = i + 2
}
value, _ := strconv.ParseInt(hex, 16, 8)
binary.Write(f, binary.LittleEndian,value)
s = append(s, value)
}
If your hex data is in the from of a string and you want to write the raw bytes you'll have to convert it first, the easier way would be to use hex.Decode.
import (
"encoding/hex"
"io/ioutil"
)
func foo() {
stringData := []byte("48656c6c6f20476f7068657221")
hexData := make([]byte, hex.DecodedLen(len(stringData)))
_, err := hex.Decode(stringData, hexData)
// handle err
err := ioutil.WriteFile("filename", hexData, 0644)
// handle err
}
Based on your use you could swap over to using ioutil.WriteFile. It writes the given byte slice to a file, creating the file if it doesn't exist or truncating it in the case it already exists.
Since yesterday I was trying to write a script/program that would change filenames. Alongside learning golang I also started to make animations and for some reason Illustrator names every .png file I make like this - "name_working space 1 copy.png" and "* copy (number).png". I was tired of renaming those files manually and today I found this code and modified 1 line of it to get rid of "_working space 1 copy" but only from those files that have numbers. That leaves me with 2 files that are first frames of my animations and those are - "name_working space 1.png"; "name_working space 1 copy.png". I could totaly live with it by leaving those 2 blank but since I try to learn golang I wanted to ask if I can improve it to replace every single filename. I started learning monday this week.
import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
)
func currentDir() {
dir := "D:\\GoLang\\src\\gocourse\\renamefile\\rename"
file, err := os.Open(dir)
if err != nil {
log.Fatalf("failed using this directory %s", err)
}
defer file.Close()
list, err := file.Readdirnames(0)
if err != nil {
log.Fatalf("failed reading directory: %s", err)
}
re := regexp.MustCompile("_working space 1 copy ")
for _, name := range list {
oldName := name
fmt.Println("Old name - ", oldName)
newName := re.ReplaceAllString(oldName, "$1")
fmt.Println("New Name - ", newName)
err := os.Rename(filepath.Join(dir, oldName), filepath.Join(dir, newName))
if err != nil {
log.Printf("error renaming file: %s", err)
continue
}
fmt.Println("File names have been changed")
}
}
func main() {
currentDir()
}
ball_working space 1.png --> ball-1.png;
ball_working space 1 copy.png --> ball-2.png;
ball_working space 1 copy 1.png --> ball-3.png;
ball_working space 1 copy 2.png --> ball-4.png etc.
Here is a reimplementation of your code. However it is not complete, as you need
to better clarify what the names look like before, and what they should look like
after.
package main
import (
"os"
"path/filepath"
"strings"
)
func main() {
dir := `D:\GoLang\src\gocourse\renamefile\rename`
list, err := os.ReadDir(dir)
if err != nil {
panic(err)
}
for _, each := range list {
name := each.Name()
newName := strings.ReplaceAll(name, "_working space 1 copy", "")
os.Rename(filepath.Join(dir, name), filepath.Join(dir, newName))
}
}
I guess you could just parse the first part of each file name until it encounters an underscore. A function to illustrate if you are sure that the file you want to extract always ends with the same symbol '_'.
fileNames := []string{"ball_working space 1.png", "ball_working space 1 copy.png",
"ball_working space 1 copy 1.png", "ball_working space 1 copy 2.png"}
func parseName(names []string) {
res := []string{""}
for ind, val := range names {
data := val[:strings.IndexByte(val, '_')] // reads until the hardcoded symbol
data += "-" + strconv.Itoa(ind)
res = append(res, data)
return res
}
Then you will have a slice with all the "correct" file names and you can unpack it in your way of preference.
I am trying to make a program for checking file duplicates based on md5 checksum.
Not really sure whether I am missing something or not, but this function reading the XCode installer app (it has like 8GB) uses 16GB of Ram
func search() {
unique := make(map[string]string)
files, err := ioutil.ReadDir(".")
if err != nil {
log.Println(err)
}
for _, file := range files {
fileName := file.Name()
fmt.Println("CHECKING:", fileName)
fi, err := os.Stat(fileName)
if err != nil {
fmt.Println(err)
continue
}
if fi.Mode().IsRegular() {
data, err := ioutil.ReadFile(fileName)
if err != nil {
fmt.Println(err)
continue
}
sum := md5.Sum(data)
hexDigest := hex.EncodeToString(sum[:])
if _, ok := unique[hexDigest]; ok == false {
unique[hexDigest] = fileName
} else {
fmt.Println("DUPLICATE:", fileName)
}
}
}
}
As per my debugging the issue is with the file reading
Is there a better approach to do that?
thanks
There is an example in the Golang documentation, which covers your case.
package main
import (
"crypto/md5"
"fmt"
"io"
"log"
"os"
)
func main() {
f, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
log.Fatal(err)
}
fmt.Printf("%x", h.Sum(nil))
}
For your case, just make sure to close the files in the loop and not defer them. Or put the logic into a function.
Sounds like the 16GB RAM is your problem, not speed per se.
Don't read the entire file into a variable with ReadFile; io.Copy from the Reader that Open gives you to the Writer that hash/md5 provides (md5.New returns a hash.Hash, which embeds an io.Writer). That only copies a little bit at a time instead of pulling all of the file into RAM.
This is a trick useful in a lot of places in Go; packages like text/template, compress/gzip, net/http, etc. work in terms of Readers and Writers. With them, you don't usually need to create huge []bytes or strings; you can hook I/O interfaces up to each other and let them pass around pieces of content for you. In a garbage collected language, saving memory tends to save you CPU work as well.