Is it possible to prevent flag.Parse() from "swallowing" (removing) the -- from the flag.Args()?
Example
package main
import (
"flag"
"fmt"
)
func main() {
flag.Parse()
fmt.Println(flag.Args())
}
I can't differentiate these 2 invocations:
$ go run . hello
[hello]
$ go run . -- hello
[hello]
Why would I like to differentiate these 2 invocations?
I'm writing a Go program that wraps another subprogram.
My program has some optional positional args:
myprog [options] [ARG1 ARG2 ...] [-- SUBARG1 SUBARG2...]
Invocation examples:
$ myprog -flag1 val1
$ myprog -flag1 val1 foo
# foo is for myprog
$ myprog -flag1 val1 foo -- bar
# foo is for myprog
# bar is for the subprogram
$ myprog -flag1 val1 -- bar
# bar is normally for the subprogram, BUT flag.Args() = ["bar"] so I have no way to know that it was after "--"
I understand that I can use --- as separator or any other combination, but I was just curious to know for the -- argument.
Edit after accepted answer
Source:
package main
import (
"flag"
"fmt"
"os"
)
func main() {
var initFlag = flag.Bool("init", false, "init")
var subArgs []string
for i := len(os.Args) - 1; i > 0; i-- {
if os.Args[i] == "--" {
subArgs = os.Args[i+1:]
os.Args = os.Args[:i]
break
}
}
flag.Parse()
fmt.Println(*initFlag)
fmt.Println(flag.Args())
fmt.Println(subArgs)
}
All tests succeeded! 👍
$ go run .
false
[]
[]
$ go run . foo
false
[foo]
[]
$ go run . -- bar
false
[]
[bar]
$ go run . foo -- bar
false
[foo]
[bar]
$ go run . -init foo -- bar
true
[foo]
[bar]
$ go run . -init -- bar
true
[]
[bar]
The flag package does not have an option to disable the flag terminator --.
Split the subprogram arguments from the main program arguments before calling flag.Parse():
var subArgs []string
for i := len(os.Args) - 1; i > 0; i-- {
if os.Args[i] == "--" {
subArgs = os.Args[i+1:]
os.Args = os.Args[:i]
break
}
}
flag.Parse()
Is it possible to prevent flag.Parse() from "swallowing" (removing) the -- from the flag.Args()?
No.
Related
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")
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)
}
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
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
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.