As a microservices author, I appreciate how powerful the standard library's flag pacakge is, providing a lightweight way to document command line flags. However, the built-in -help option appears to present documentation for only the flags themselves, while the rest of the command line arguments are often idiosyncratic, requiring documentation as well. What is a good way to document the rest of the CLI arguments, such as a Go application that accepts some flags, and then treats the rest of the arguments as file paths?
My preferred approach is just to set flag.Usage to a function which prints the additional documentation.
For example:
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s [flags] <paths...>\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "Argument documention goes here\n")
}
Related
I am using cobra to create a CLI application (app). I need to implement a behavior in which I can pass flags as arguments. These flags will be passed|used further to another application via exec.Command(). All flag arguments passed to app must be ignored by the app itself, i.e. not recognized as flags.
I do not rule out that this is strange behavior. But if there is an opportunity to implement I will be grateful.
Examples of interaction with the application:
> app --help
or
> app --first --second
I want the arguments (--help --first --second, and all others) to not be taken as flags for app.
You can pass them after a double dash -- which by convention indicates that following flags and args are not meant to be interpreted as args and flags for the main program. They are ignored unless you explicitly use them in some way. i.e.:
if len(pflag.Args()) != 0 {
afterDash := pflag.Args()[pflag.CommandLine.ArgsLenAtDash():]
fmt.Printf("args after dash: %v\n", afterDash)
}
$ go run ./cmd/tpl/ -d yaml -- --foo --bar
args after dash: [--foo --bar]
cobra is using the pflag package https://pkg.go.dev/github.com/spf13/pflag
I need to use lxd-p2c(https://github.com/lxc/lxd/tree/master/lxd-p2c) in golang code.
I try to pass password to the binary lxd-p2c built by the code above, which uses term.ReadPassword(0)(https://github.com/lxc/lxd/blob/master/lxd-p2c/utils.go#L166) to read password in Linux.
I did some search on the internet and tried following code but they just did not work.
# 1
cmd := exec.Command(command, args...)
cmd.Stdin = strings.NewReader(password)
# 2
cmd := exec.Command(command, args...)
stdin, _ := cmd.StdinPipe()
io.WriteString(stdin, password)
Similar but simple code to test: https://play.golang.org/p/l-9IP1mrhA (code from https://stackoverflow.com/a/32768479/9265302)
build the binary and call it in go.
edit:
No workaround found and I removed term.ReadPassword(0) in the source code.
Checking the error in your playground displays inappropriate ioctl for device.
Searching for the error message I found this thread, which notes that non-terminal input is not supported for terminal.ReadPassword. My guess is that passing Stdin input this way makes it pass the input with a character device instead of with the necessary terminal device like tty or any such, making the read fail. lxd-p2c can't read the password from such an input device.
I looked for some similar problems but I couldn't find anything except this: https://github.com/spf13/cobra/issues/1025
My problem is about inserting some string which contains a dash at the beginning like the following example,
go run myapp exampleCmd set "-Dexample"
Cobra seems to take the input -Dexample as internal parameter because returns this output:
Error: unknown shorthand flag: 'D' in -Dexample
Usage:
myapp exampleCmd set [flags]
Flags:
-h, --help help for set
Global Flags:
-s, --set string Set exampleCmd parameters. (default "default_param")
my init() function contains these two lines:
func init() {
...
exampleCmd.PersistentFlags().StringP("set", "s", defaultArgument, "Set parameters.")
exampleCmd.AddCommand(setCmd)
...
}
var exampleCmd = &cobra.Command{
Use: "set",
Short: "set parameter",
Long: `set parameter`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 && len(args) != 0 {
color.Red("Wrong usage, insert just a parameter")
} else if len(args) == 0 {
color.Yellow("Setting default parameter: " + defaultArgument)
internal.SetParams(defaultArgument)
} else {
internal.SetParams(args[0])
}
return nil
},
}
How can I accept parameters with dashes at beginning with cobra, if exists any solution?
As with virtual all Unix-style command line utilities and flag parsing libraries, Cobra separates flags from arguments with a --, after which no more arguments will be parsed as flags, even if they start with a -.
go run myapp exampleCmd set -- "-Dexample"
This is no different than how you interact with other CLI utilities. For example, rm -i sets the interactive flag, while rm -- -i removes a file named -i.
You definitely do not want to arbitrarily disable flags for certain commands or subcommands which is inconsistent (both within your own app and across all other apps), unnecessary, and breaks basic user expectations: Sometimes, -h will do what the user expects, but for some commands, for reasons the user cannot predict, -h will be treated as an argument and produce unexpected behavior.
Unix has solved this problem for more than 50 years. Let the user decide whether a argument is a flag via --.
Solved using this workaround (if it is)
I added to the end of the &cobra.Command{} this element:
DisableFlagParsing: true,
found here: https://github.com/spf13/cobra/issues/683
I don't think it is possible to pass an argument beginning with a dash symbol while using an utility like cobra. The dash is a flag indicator and it doesn't matter if it is enclosed in quotes, single dash is read as a shorthand flag, so the first letter of your input is getting interpreted as a flag and unrecognized, thus the program fails (and calls cmd.Help()).
You've set set as both command and a flag (--set -s), so it appears in your --help output.
I would consider using a different character for your command argument or adding it in another way internally.
I found the solution in the flagset interspersed option:
https://github.com/spf13/cobra/issues/1307#issue-777308697
This says to cobra that other flags after the first are also flags.
The multi sub MAIN() command line parsing in Perl6 is sweet!
As far as I can tell from the Command Line Interface docs there is only one option supported in the dynamic hash %*SUB-MAIN-OPTS to manipulate the option processing (that being :named-anywhere).
Perhaps I've missed the obvious, but is there an existing/supported option to take 'old fashioned' single dash options?
For example:
#Instead of this...
myprogram.p6 --alpha=value1 --beta==value2 --chi
#... short options like this
myprogram.p6 -a value1 -bvalue2 -c
Or is this best processed manually or with an external module?
You can sort of emulate this as-is, although you still have to an = ala -a=foo, and still technically have --a=foo in addition to --alpha and -a
sub MAIN(:a(:$alpha)!) {
say $alpha;
}
...so you probably want to use https://github.com/Leont/getopt-long6
use Getopt::Long;
get-options("alpha=a" => my $alpha);
I am trying to patch a file using the below command
patch -p0 < <file_path>
My runCommand syntax is as below:
func runCommand(cmd string, args ...string) error {
ecmd := exec.Command(cmd, args...)
ecmd.Stdout = os.Stdout
ecmd.Stderr = os.Stderr
ecmd.Stdin = os.Stdin
err := ecmd.Run()
return err
}
Now I am passing my patch command as below:
cmd = "patch"
args := []string{"-p0", "<", "/tmp/file"}
err = runCommand(cmd, args...)
But I am seeing the below error:
patch: **** Can't find file '<' : No such file or directory
Can you please let me know what I am missing here?
You're missing this paragraph from the documentation:
Unlike the "system" library call from C and other languages, the
os/exec package intentionally does not invoke the system shell and
does not expand any glob patterns or handle other expansions,
pipelines, or redirections typically done by shells. The package
behaves more like C's "exec" family of functions. To expand glob
patterns, either call the shell directly, taking care to escape any
dangerous input, or use the path/filepath package's Glob function. To
expand environment variables, use package os's ExpandEnv.
The shell is responsible for handling < operations. You can set stdin yourself with the file as input or you can use the shell. To use the shell, try something like:
runCommand("/bin/sh", "patch -p0 < /tmp/file")
Note that this won't work on Windows. Reading the file and writing to stdin yourself is a more easily portable solution.