Sscanf of multiple string fields in golang - go

I am trying to use sscanf to parse multiple string fields. Here is an example code snippet:
package main
import "fmt"
func main() {
var name, currency string
_, err := fmt.Sscanf("transaction benson: dollars", "transaction %s: %s", &name, &currency)
fmt.Println(err, name, currency)
}
The output is
input does not match format benson:
Program exited.

%s is greedy and gobbles up to the next space, which means it eats up the colon. After processing the %s, it then tries to scan in the colon, but wait, that’s already been consumed, and the next character is actually a space, not a colon! So it fails.
In C you’d get around this by using %[^:] rather than %s, but it appears Go doesn’t support this. You might need to find some way to parse your string without Sscanf.

Related

How to convert the string representation of a Terraform set of strings to a slice of strings

I've a terratest where I get an output from terraform like so s := "[a b]". The terraform output's value = toset([resource.name]), it's a set of strings.
Apparently fmt.Printf("%T", s) returns string. I need to iterate to perform further validation.
I tried the below approach but errors!
var v interface{}
if err := json.Unmarshal([]byte(s), &v); err != nil {
fmt.Println(err)
}
My current implementation to convert to a slice is:
s := "[a b]"
s1 := strings.Fields(strings.Trim(s, "[]"))
for _, v:= range s1 {
fmt.Println("v -> " + v)
}
Looking for suggestions to current approach or alternative ways to convert to arr/slice that I should be considering. Appreciate any inputs. Thanks.
Actually your current implementation seems just fine.
You can't use JSON unmarshaling because JSON strings must be enclosed in double quotes ".
Instead strings.Fields does just that, it splits a string on one or more characters that match unicode.IsSpace, which is \t, \n, \v. \f, \r and .
Moeover this works also if terraform sends an empty set as [], as stated in the documentation:
returning [...] an empty slice if s contains only white space.
...which includes the case of s being empty "" altogether.
In case you need additional control over this, you can use strings.FieldsFunc, which accepts a function of type func(rune) bool so you can determine yourself what constitutes a "space". But since your input string comes from terraform, I guess it's going to be well-behaved enough.
There may be third-party packages that already implement this functionality, but unless your program already imports them, I think the native solution based on the standard lib is always preferrable.
unicode.IsSpace actually includes also the higher runes 0x85 and 0xA0, in which case strings.Fields calls FieldsFunc(s, unicode.IsSpace)
package main
import (
"fmt"
"strings"
)
func main() {
src := "[a b]"
dst := strings.Split(src[1:len(src)-1], " ")
fmt.Println(dst)
}
https://play.golang.org/p/KVY4r_8RWv6

How to parse timestamp with underscores in Golang

I'm trying to parse access log timestamp like "2020/11/06_18:17:25_455" in Filebeat according to Golang spec.
Here is my test program to verify layout:
package main
import (
"fmt"
"log"
"time"
)
func main() {
eventDateLayout := "2006/01/02_15:04:05_000"
eventCheckDate, err := time.Parse(eventDateLayout, "2020/11/06_18:17:25_455")
if err != nil {
log.Fatal(err)
}
fmt.Println(eventCheckDate)
}
Result:
2009/11/10 23:00:00 parsing time "2020/11/06_18:17:25_455" as
"2006/01/02_15:04:05_000": cannot parse "455" as "_000"
As I understand underscore has a special meaning in Golang, but from documentation it's not clear how to escape it.
Any ideas, please?
It doesn't seem possible to use any escape characters for the time layout (e.g. "\\_" doesn't work), so one would have to do something different.
This issue describes the same problem, but it was solved in a very non-general way that doesn't seem to apply to your format.
So your best bet seems to be replacing _ with something else/stripping it from the string, then using a layout without it. To make sure that the millisecond part ist also parsed, it must be separated with a . instead of _, then it's recognized as part of the seconds (05) format.
eventDateLayout := "2006/01/02.15:04:05"
val := strings.Replace("2020/11/06_18:17:25_455", "_", ".", 2)
eventCheckDate, err := time.Parse(eventDateLayout, val)
if err != nil {
panic(err)
}
fmt.Println(eventCheckDate)
Playground link
From time.Format
A fractional second is represented by adding a period and zeros to the
end of the seconds section of layout string, as in "15:04:05.000" to
format a time stamp with millisecond precision.
You cannot specify millisecond precision with an underscore you need 05.000 instead:
// eventDateLayout := "2006/01/02_15:04:05_000" // invalid format
eventDateLayout := "2006/01/02_15:04:05.000"
eventCheckDate, err := time.Parse(eventDateLayout, "2020/11/06_18:17:25.455")
So basically use a simple translate function to convert the final _ to a . and use the above parser.
https://play.golang.org/p/POPgXC_qe81

Unmarshal returns blank object due to encoding

I'm attempting to unmarshal a raw json string. There seems to be an error with encoding but I can't quite figure it out.
package main
import (
"encoding/json"
"fmt"
"log"
)
type Foo struct {
Transmission string `json:"transmission"`
Trim string `json:"trim"`
Uuid string `json:"uuid"`
Vin string `json:"vin"`
}
func main() {
var foo Foo
sample := `{
"transmission": "continuously\x20variable\x20automatic",
"trim": "SL",
"uuid" : "6993e4090a0e0ae80c59a76326e360a1",
"vin": "5N1AZ2MH6JN192059"
}`
err := json.Unmarshal([]byte(sample), &foo)
if err != nil {
log.Fatal(err)
}
fmt.Println(foo)
}
2009/11/10 23:00:00 invalid character 'x' in string escape code
It works if transmission entry is removed.
Here is a working playground.
Your input is not valid JSON. The JSON spec states that
All code points may be placed within the quotation marks except for the code points that must be escaped: quotation mark (U+0022), reverse solidus (U+005C), and the control characters U+0000 to U+001F.
Additionally, although there are two-character escape sequences, \x is not one of them, and thus it is being correctly interpreted as an invalid escape sequence by the Go parser. If you want to have a backslash literal in your JSON, it needs to be represented by \\ in the JSON input itself. See a modified version of your example: https://play.golang.org/p/JZdPJGpPR5q
(note that this is not an issue with your Go string literal since you're already using a raw (``) string literal—the JSON itself needs to have two backslashes.)
You can replace \x with \\x using string.Replace() function. Then, Unmarshal the replaced string. Here is a working example.

Why does fmt.Println in Go print the verb %s literal instead of the value?

Consider,
package main
import "fmt"
func main() {
name := "johnny"
fmt.Println("Hello world %s\n", name)
}
prints out,
Hello world %s
johnny
Why do I get the %s instead of this,
package main
import "fmt"
func main() {
name := "johnny"
fmt.Printf("Hello world %s\n", name)
}
which prints Hello world johnny?
I have tried to figure out the answer from the documentation,
If the format (which is implicitly %v for Println etc.) is valid for a
string (%s %q %v %x %X), the following two rules apply:
If an operand implements the error interface, the Error method will be invoked to convert the object to a string, which will then be
formatted as required by the verb (if any).
If an operand implements method String() string, that method will be invoked to convert the object to a string, which will then be
formatted as required by the verb (if any).
But I'm having trouble understanding if this is affecting my program.
the f in Printf is for "Formatting." That's why the %? verbs do anything at all, because the function is built to parse for them. Println does no such formatting.
Formatting isn't a property of strings like in some languages (maybe you, like myself, came from Python?)
Println just prints the string and appends a newline to it. Printf is short for 'print format' and is based off the C library which is where the conventions for format specifiers ect come from.
Simple answer is it's as designed. If you want to use format specifiers you gotta call the format method.

Read multi word string from console

I realized that the following only reads a single word string -
fmt.Scan(&sentence)
How do I read multi word string - as in, the string sentence should store a string that contains multiple words.
One can use the InputReader also to scan and print multiple words from the console.
The solution code is as follows:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
inputReader := bufio.NewReader(os.Stdin)
input, _ := inputReader.ReadString('\n')
fmt.Println(input)
}
Console Input:
Let's Go!!!
Console Output:
Let's Go!!!
Note:
To run a GOLANG program, open the command prompt or powershell, navigate to the directory where the program file is present and type in the following command:
go run file_name.go
Your question refers to scanning space separated input. The definition of fmt.Scan https://golang.org/pkg/fmt/#Scan states:
Scan scans text read from standard input, storing successive space-
separated values into successive arguments. Newlines count as space.
It returns the number of items successfully scanned. If that is less
than the number of arguments, err will report why.
So, by definition, input is scanned up until the first space is found. To scan, let's say until you hit a \n on the command line you can use the code from the comment scanning spaces from stdin in Go:
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
return scanner.Text()
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
Also this thread might be useful: https://groups.google.com/forum/#!topic/golang-nuts/r6Jl4D9Juw0

Resources