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.
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
My Problem
I have an AppleScript script file called script.scpt.
This is the first line of the script where I'm intending to use labeled parameters (AKA named parameters):
on run given foo:fooStr
I open Terminal and attempt to run the script by typing the following:
osascript "/Users/UserName/Library/Mobile Documents/com~apple~ScriptEditor2/Documents/script.scpt" given foo:"bar"
I get the resulting error:
execution error: The foo parameter is missing for run. (-1701)
I have tried every other variation I can think of for supplying the labeled/named parameter to the script within the command line with no success.
My Question
How do I pass labeled/named parameters to an existing AppleScript script file via the command line? Thanks in advance.
Background
I previously had the script using the following line:
on run argv
And then did things the "typical" way by getting values I needed within my script based on their predetermined sequential position when supplied on the command line as such:
set fooStr to item 1 of argv
Where the command line was:
osascript "/Users/UserName/Library/Mobile Documents/com~apple~ScriptEditor2/Documents/script.scpt" "bar"
Use standard *nix shell conventions and parse any options in the arguments list yourself.
Simple hand-rolled example:
property _syntax : "cmd [-A] [-B VALUE] [-h] [-] [ARG ...]"
on run argv
-- parse options
set opt_A to false
set opt_B to ""
considering case
repeat while argv is not {} and argv's first item begins with "-"
set flag to argv's first item
set argv to rest of argv
if flag is "-A" then
set opt_A to true
else if flag is "-B" then
set opt_B to argv's first item
set argv to rest of argv
else if flag is "-h" then
log _syntax
return
else if flag is "-" then
exit repeat
else
error "Unknown option: " & flag
end if
end repeat
end considering
-- do any validation here
doStuff(opt_A, opt_B, argv)
end run
to doStuff(foo, bar, baz)
-- do work here
end doStuff
(If your options are particularly complex, or you write a lot of these scripts, you may prefer to use an existing options parser, e.g. NSArgumentDomain of NSUserDefaults via AppleScript-ObjC, or parse command line arguments in File library.)
The run handler is a little different than a regular user-defined handler - as an event handler, it takes parameters as a single list. You can define it with a labeled parameter, but when something executes the script, standard POSIX conventions for command line arguments are used.
The command line pre-dates labeled parameters by a few decades; it uses option switches instead. You can also implement something like that, but whether that is worth it or not would depend on exactly what you are trying to do.
An alternative to switches would be to call a handler that runs the target after arranging the arguments (a script's run handler would be declared as usual). One way to do this would be to merge an argument record with a default and then pass a list of keys in the desired order, for example:
to runWithArgs given args:argRecord -- run shell script with arguments from merged records
set defaults to {foo:"foo", bar:"bar", baz:"baz", another:"four"} -- or whatever
if class of argRecord is not record then set argRecord to {}
set merged to {foo, bar, baz, another} of (argRecord & defaults) -- the desired keys and order
set arguments to ""
repeat with anItem in merged
set arguments to arguments & space & quoted form of (anItem as text)
end repeat
do shell script "echo" & arguments -- osascript, etc
end runWithArgs
runWithArgs given args:{whatever:"no, not this one", another:4, foo:"this is a test"}
An alternative that doesn't use the command line would be to use load script, and call the individual handlers (you wouldn't necessarily need to use a run handler). For example:
set scpt to load script "/path/to/foo.scpt"
tell scpt to run given foo:"bar"
tell scpt to someHandler given someLabel:"whatever"
I have to pass a command with its arguments in a scheduled task, while separating the arguments from the command. I used:
split(/(?=\s-)/)
to do this, but it won't work when the argument is not passed as -arg format.
Example of commands can be passed in format:
"ping http://www.google.com" here url is argument
"abc-abc -V"
"abc-abc -L c:\\folder name\\test.log"
'"C:\\Program Files\\example\\program.exe" -arg1 -arg2'
"C:\\Program Files\\example\\program.exe"
To make this more clear these commands are not passed as command line argument which can get in ARGV
The command gets set in command property which accepts input in string format
command '"C:\\Program Files\\example\\program.exe" -arg1 -arg2'
Use Shellwords.split, from the standard library:
Shellwords.split("ping http:\\www.google.com here url is argument")
#=> ["ping", "http:www.google.com", "here", "url", "is", "argument"]
Shellwords.split("abc-abc -V")
#=> ["abc-abc", "-V"]
Shellwords.split("abc-abc -L c:\\folder name\\test.log")
#=> ["abc-abc", "-L", "c:folder", "nametest.log"]
Shellwords.split('"C:\\Program Files\\example\\program.exe" -arg1 -arg2')
#=> ["C:\\Program Files\\example\\program.exe", "-arg1", "-arg2"]
Shellwords.split('"C:\\Program Files\\example\\program.exe"')
#=> ["C:\\Program Files\\example\\program.exe"]
No need to reinvent the wheel with a custom regex/splitter, or an external system call.
It seems to me that if there's no consistent pattern to your command syntax, then any regex based approach will inevitably fail. It seems better instead to solve this problem the way a human would, i.e. with some knowledge of context.
In a *nix terminal, you can use the compgen command to list available commands. This Ruby script invokes that command to print the first 5 options from that list:
list = `cd ~ && compgen -c`
list_arr = list.split("\n")
list_arr[0,6].each{|x| puts x }
(The cd in the first line seems to be needed because of the context in which my Ruby is running with rvm.) For Windows, you may find this thread a useful starting point.
I'd match against the elements of this list to identify my commands, and take it from there.
Tom Lord's answer is far better than this one.
You probably want to look at OptionParser or GetOptLong if you need parsing of command line arguments provided to a ruby program.
If you are interested in parsing some strings that may or may not be commands with arguments, here's a quick-and-dirty:
I'd use scan instead of split with the following regex: /(".*"|[\w\:\:\.\-\\]+)/.
Best results come from: 'some string'.scan(/(".*"|[\w\:\:\.\-\\]+)/).flatten:
["ping", "http:\\www.google.com"]
["abc-abc", "-V"]
["abc-abc", "-L", "c:\\folder\\", "name\\test.log"]
# Technically, this is wrong, but so is the non-escaped whitespace.
["\"C:\\Program Files\\example\\program.exe\"", "-arg1", "-arg2"]
["\"C:\\Program Files\\example\\program.exe\""]
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")
}
I wrote a simple script that writes all given arguments to a single text file, separated by newline. I'd like to pass a list of files to it using OptionParser. I would like to add a couple of files using wildcards like /dir/*.
I tried this:
opts = OptionParser.new
opts.on('-a', '--add FILE') do |s|
puts "DEBUG: before #{s}"
#options.add = s
puts "DEBUG: after #{#options.add}"
end
...
def process_arguments
#lines_to_add = Dir.glob #options.add
end
Put when I add files like this:
./script.rb -a /path/*
I always get only the first file in the directory. All the debug outputs show only the first file of directory, and it seems as if OptionParser does some magic interpretations
Does anyone know how to handle this?
You didn't mention which operating system you are using (it matters).
On Windows, whatever you type on the command line gets passed to the program without modification. So if you type
./script.rb -a /path/*
then the arguments to the program contain "-a" and "/path/*".
On Unix and other systems with similar shells, the shell does argument expansion that automatically expands wildcards in the command line. So when you type the same command above, the shell looks to find the files in the /path/* directory and expands the command line arguments before your program runs. So the arguments to your program might be "-a", "/path/file1", and "/path/file2".
An important point is that the script cannot find out whether argument expansion happened, or whether the user actually typed all those filenames out on the command line.
As mentioned above, the command-line is being parsed before the OS hands off the command to Ruby. The wildcard is being expanded into a list of space-delimited filenames.
You can see what will happen if you type something like echo * at the command-line, then, instead of hitting Return, instead hit Esc then *. You should see the * expanded into the list of matching files.
After hitting Return those names will be added to the ARGV array. OptionParser will walk through ARGV and find the flags you defined, grab the following elements if necessary, then remove them from ARGV. When OptionParser is finished any ARGV elements that didn't fit into the options will remain in the ARGV array where you can get at them.
In your code, you are looking for a single parameter for the '-a' or '--add FILE' option. OptionParser has an Array option which will grab comma-separated elements from the command line but will subsequent space-delimited ones.
require 'optparse'
options = []
opts = OptionParser.new
opts.on('-a', '--add FILE', Array) do |s|
options << s
end.parse!
print "options => ", options.join(', '), "\n"
print "ARGV => ", ARGV.join(', '), "\n"
Save that to a file and try your command line with -a one two three, then with -a one,two,three. You'll see how the Array option grabs the elements differently depending on whether there are commas or spaces between the parameters.
Because the * wildcard gets replaced with space delimited filenames you'll have to post-process ARGV after OptionParser has run against it, or programmatically glob the directory and build the list that way. ARGV has all the files except the one picked up in the -a option so, personally, I'd drop the -a option and let ARGV contain all the files.
You will have to glob the directory if * has to too many files and exceeds the buffer size. You'll know if that happens because the OS will complain.
The shell is expanding the argument before it gets passed to your program. Either keep consuming filenames until you reach another option, or have the user escape the wildcards (e.g. ./script.rb -a '/path/*') and glob them yourself.
What's happening is the shell is expanding the wildcard before Ruby gets to it. So really you are processing:
./script.rb -a /path/file1 /path/file2 ......
Put quotes around /path/* to avoid the shell expansion and pass the wildcard to Ruby:
./script.rb -a '/path/*'