How to find number of flags coming from console in Golang - go

I'm passing argument from console. There are some flags too. Like:
go run test.go "-IP=10.10.10.10" "-db=kite" "-wv=45" "-cv=75" "A = value1" "B = value2" "C = 100" "D := ((A-B)/A)*C" "D ?"
Here, -IP, -db, -wv, -wc these four are flags and others are passing as normal argument as I know.
Number of flags can be variable.
How can I know how many flags are passed to my program. In this case 4 flags are passed.

If you use the standard flag package to parse command-line flags, you can call the NFlag function to get the number of flags:
package main
import "fmt"
import "flag"
func main() {
flag.Bool("a", true, "A value");
flag.Bool("b", true, "B value");
flag.Parse();
fmt.Println(flag.NFlag())
}
Test:
$ go run test.go
0
$ go run test.go -a
1
$ go run test.go -a -b
2

Related

"rune literal not terminated" error when attempting to run gofmt -r with exec.Command

In the following directory structure,
.
├── foo.go
├── go.mod
└── main.go
I have a foo.go with a simple type definition:
package main
type Foo struct {
Baz string
}
If I run gofmt -r from the command line to replace a variable name, it works:
> gofmt -r 'Foo -> Bar' foo.go
package main
type Bar struct {
Baz string
}
However, if I try to do this from main.go with the program
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
combinedOutput, err := exec.Command("gofmt", "-r", "'Foo -> Bar'", "foo.go").CombinedOutput()
if err != nil {
log.Fatalf("gofmt foo.go: %v. Combined output: %s", err, combinedOutput)
}
fmt.Println(string(combinedOutput))
}
I get an error:
> go run main.go
2023/01/14 23:42:07 gofmt foo.go: exit status 2. Combined output: parsing pattern 'Foo at 1:1: rune literal not terminated
exit status 1
Any idea what is causing this?
You don't need to quote arguments to exec.Command; quoting is a feature of your shell, and doesn't apply when you make system calls. It's also not necessary, because quoting is done to delineate arguments in the shell, but in exec.Command, the arguments are separated as arguments to the function call.
Concretely:
exec.Command("gofmt", "-r", "'Foo -> Bar'", "foo.go")
should be
exec.Command("gofmt", "-r", "Foo -> Bar", "foo.go")

How to provide the command line args first and then the flags in golang?

Golang's flag package reads the command line flags and args properly if the input provided is of the form : go run main.go -o filename.txt arg1 arg2
But if I try to provide the input like : go run main.go arg1 arg2 -o filename.txt, everything after main.go is read as arguments.
How to make this style work?
My program:
package main
import (
"flag"
"fmt"
)
func main() {
var output string
flag.StringVar(&output, "o", "", "Writes output to the file specified")
flag.Parse()
fmt.Println("Positional Args : ", flag.Args())
fmt.Println("Flag -o : ", output)
}
go run main.go -o filename.txt arg1 arg2
Output:
Positional Args : [arg1 arg2]
Flag -o : filename.txt
go run main.go arg1 arg2 -o filename.txt
Output:
Positional Args : [arg1 arg2 -o filename.txt]
Flag -o :
If you shimmy around with the contents of os.Args, it is possible to accept arg1 arg2 -o filename.txt
Go through the os.Args that is passed in from the command line in the for loop
If a - is seen then set a condition that indicates the first flag has been seen
If the condition is set then populate the "notargs" list. Otherwise, populate the "args" list
There is a bit of extra complication here as the args list that is used to set os.Args to the values that will do the flag processing must include the program name (the original os.Arg[0]) as the first value
This solution does not work with -o filename.txt arg1 arg2
package main
import (
"flag"
"fmt"
"os"
)
func main() {
var output string
var args[]string
var notargs[]string
var in_flags bool=false
for i:=0; i<len(os.Args); i++ {
if os.Args[i][0]=='-' {
in_flags=true
}
if i==0 || in_flags {
notargs=append(notargs,os.Args[i])
} else {
args=append(args,os.Args[i])
}
}
os.Args=notargs
flag.StringVar(&output, "o", "", "Writes output to the file specified")
flag.Parse()
fmt.Println("args ",args)
fmt.Println("Flag -o : ", output)
}

golang - read string argument include &

How do I read string argument include & in Go for example this link
$ ./main
https://www.youtube.com/watch?v=G3PvTWRIhZA&list=PLQVvvaa0QuDeF3hP0wQoSxpkqgRcgxMqX
without use double quotation (")
$ ./main
"https://www.youtube.com/watch?v=G3PvTWRIhZA&list=PLQVvvaa0QuDeF3hP0wQoSxpkqgRcgxMqX"
main.go
package main
import (
"os"
"fmt"
)
func main() {
link := os.Args[1]
fmt.Println(link)
}
$ go build main.go
$ ./main
https://www.youtube.com/watch?v=G3PvTWRIhZA&list=PLQVvvaa0QuDeF3hP0wQoSxpkqgRcgxMqX
output will be
https://www.youtube.com/watch?v=G3PvTWRIhZA
Both #JimB and #Adrian are correct, the & needs to be escaped.
If you absolutely must find a workaround, you could opt to not use a command-line argument and rather read input instead to bypass need for escaping.
Example:
package main
import (
"fmt"
)
func main() {
var input string
fmt.Scan(&input)
fmt.Println(input)
}
input:
$ ./main
https://www.youtube.com/watch?v=G3PvTWRIhZA&list=PLQVvvaa0QuDeF3hP0wQoSxpkqgRcgxMqX
output:
https://www.youtube.com/watch?v=G3PvTWRIhZA&list=PLQVvvaa0QuDeF3hP0wQoSxpkqgRcgxMqX

Why `build` with ldflag `-X` works only when the filename is specified?

I have the file playground.go:
package main
import (
"fmt"
)
var GitCommit string
func main() {
fmt.Printf("Hello world, version: %s\n", GitCommit)
}
why the behavior of the build ldflag -X depends on specifying the source filename?:
# CASE 1
$ go build -ldflags "-X main.GitCommit=abc" && ./go_playground
Hello world, version:
$ go tool nm go_playground
52a900 D main.GitCommit
# CASE 2
$ go build -ldflags "-X main.GitCommit=abc" playground.go && ./playground
Hello world, version: abc
$ go tool nm playground
5242f0 D main.GitCommit
4c5678 R main.GitCommit.str
EDIT: it seems that the problem happens when case 1 is executed from a symlinked directory; when case 1 is executed from the original directory, the variable is passed. I'm not sure if this is expected, or it's a bug.

Passing go code directly into go run without a file

Is it possible to pass a string of go code into go run instead of go run /some/path/script.go? I tried:
echo "some awesome go code here" | go run
But does not work. Thanks.
I don't think that there is such an option. At least not with the standard *g compilers or
go run.
You can try using gccgo as GCC supports reading from stdin.
Since I thought that this would be a useful thing to have, I wrote a relatively small Python script that achieves what I think you want. I called it go-script, and here are some usage examples:
# Assuming that test.go is a valid go file including package and imports
$ go-script --no-package < test.go
# Runs code from stdin, importing 'fmt' and wrapping it in a func main(){}
$ echo 'fmt.Println("test")' | go-script --import fmt --main
$ echo 'fmt.Println("test")' | go-script -ifmt -m
Help:
Usage: go-script [options]
Options:
-h, --help show this help message and exit
-i PACKAGE, --import=PACKAGE
Import package of given name
-p, --no-package Don't specify 'package main' (enabled by default)
-m, --main Wrap input in a func main() {} block
-d, --debug Print the generated Go code instead of running it.
The source (also available as a gist):
#!/usr/bin/env python
from __future__ import print_function
from optparse import OptionParser
import os
import sys
parser = OptionParser()
parser.add_option("-i", "--import", dest="imports", action="append", default=[],
help="Import package of given name", metavar="PACKAGE")
parser.add_option("-p", "--no-package", dest="package", action="store_false", default=True,
help="Don't specify 'package main' (enabled by default)")
parser.add_option("-m", "--main", dest="main", action="store_true", default=False,
help="Wrap input in a func main() {} block")
parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False,
help="Print the generated Go code instead of running it.")
(options, args) = parser.parse_args()
stdin = ""
for line in sys.stdin.readlines():
stdin += "%s\n" % line
out = ""
if options.package:
out += "package main\n\n"
for package in options.imports:
out += "import \"%s\"\n" % package
out += "\n"
if options.main:
out += "func main() {\n%s\n}\n" % stdin
else:
out += stdin
if options.debug:
print(out)
else:
tmpfile = "%s%s" % (os.environ["TMPDIR"], "script.go")
f = open(tmpfile, 'w')
print(out, file=f)
f.close()
os.execlp("go", "", "run", tmpfile)
This works
cat <<EOF | tee /tmp/blah.go | go run /tmp/blah.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
EOF
If you want to not have to open a file and edit it first. Although I wouldn't find this super practical for every day use.

Resources