Why could not get the values that following bool type arg in the command line?
Here's my test code:
import (
"flag"
"fmt"
)
var stringVal string
var intVal int
var boolValue bool
func init() {
flag.StringVar(&stringVal, "s", "", "string value")
flag.BoolVar(&boolValue, "b", false, "bool value")
flag.IntVar(&intVal, "i", -1, "int value")
}
func main() {
flag.Parse()
fmt.Println("stringVal:", stringVal, ", boolValue:", boolValue, ", intVal:", intVal)
}
Run it as:
go run flag.go -s test -b true -i 10
Got: stringVal: test , boolValue: true , intVal: -1
go run flag.go -s test -b false -i 10
Got: stringVal: test , boolValue: true , intVal: -1
go run flag.go -b false -s test -i 10
Got: stringVal: , boolValue: true , intVal: -1
go run flag.go -s test -i 10 -b false
Got: stringVal: test , boolValue: true , intVal: 10
Go version: 1.16
Boolean flags are set based on the presence or absence of the flag. They typically default to false, and the presence of the flag is used to modify the default behavior.
For example...
to display the long listing of a directory with ls, you use ls -l, not ls -l true
to make rm safer by having it interactively prompt for deletes, you use rm -i, not rm -i true
to display human-readable output with df, you use df -h, not df -h true
The true and false you're placing after the -b are being provided to your program as arguments, not as flags, and their presence interrupts further processing of flags.
Add the following to the end of your main function:
fmt.Println("Remaining args", flag.Args())
Given an invocation such as go run flag.go -b false -s test -i 10, you'll see that flag processing stopped at false, and that the remaining arguments are passed to your program as non-flag arguments:
$ go run flag.go -b false -s test -i 10
stringVal: , boolValue: true , intVal: -1
Remaining args [false -s test -i 10]
As an aside, the general philosophy behind "boolean" flags is that you give your program some default behavior, and provide two flags: One which modifies that default, and an opposite flag that negates the modification, restoring the default. When processing flags, the last flag will win.
For example, rm provides -i to make the command interactive, and -f to negate the -i flag.
This allows you to set alias rm="rm -i" in your shell, meaning all invocations of rm will have the -i flag applied for safety, prompting you before removing files. However, if you want to run a non-interactive rm, you can still type rm -f, which expands to be rm -i -f, and the last flag (-f) wins. Otherwise, you'd have destroy the alias, run rm and then restore the alias every time you wanted a non-interactive invocation.
A boolean flag tests the existence of the flag, not the value of it. If you want to set a value, use:
go run flag.go -s test -b=false -i 10
Related
I have a golang binary named test. I have two flags -p and -s using golang argparse library. I want user to pass them like below scenarios:
./test -p
./test -s serviceName
./test -p -s serviceName
Means -p should be passed with empty value and -s should be passed with any service name. 1 and 2 are working fine but in third -p flag is taking value "-s". What i want from 3rd is -p should be empty value ("") and -s should be "serviceName".
Code:
parser := argparse.NewParser("./test", "Check status/version of test")
parser.DisableHelp()
platformFormatVal := parser.String("p", "platform", &argparse.Options{Required: false, Help: "Print status of platform", Default: "Inplatform"})
serviceFormatVal := parser.String("s", "service", &argparse.Options{Required: false, Help: "Print status of service", Default: "Inservice"})
err := parser.Parse(os.Args)
if err != nil {
//generic error
}
platformFormat = *platformFormatVal
serviceFormat = *serviceFormatVal
Haven't used argparse lib, only the standard flag, but according to the docs, you are configuring p as a string argument, while it is actually a boolean flag.
You should configure your arguments similar to this:
sArg := parser.String("s", ...)
pArg := parser.Flag("p", ...)
Then you can use it like this:
if *pArg {
...
}
How do I get -e / errexit to work in bash functions, so that the first failed command* within a function causes the function to return with an error code (just as -e works at top-level).
* not part of boolean expression, if/elif/while/etc etc etc
I ran the following test-script, and I expect any function containing f in its name (i.e. a false line in its source) to return error-code, but they don't if there's another command after. Even when I put the function body in a subshell with set -e specified again, it just blindly steamrolls on through the function after a command fails instead of exiting the subshell/function with the nonzero status code.
Environments / interpreters tested:
I get the same result in all of these envs/shells, which is to be expected I guess unless one was buggy.
Arch:
bash test.sh (bash 5.1)
zsh test.sh (zsh 5.8)
sh test.sh (just a symlink to bash)
Alpine:
docker run -v /test.sh:/test.sh alpine:latest sh /test.sh (BusyBox 1.34)
Ubuntu:
docker run -v /test.sh:/test.sh ubuntu:21.04 sh /test.sh
docker run -v /test.sh:/test.sh ubuntu:21.04 bash /test.sh (bash 5.1)
docker run -v /test.sh:/test.sh ubuntu:21.04 dash /test.sh (bash 5.1)
Test script
set -e
t() {
true
}
f() {
false
}
tf() {
true
false
}
ft() {
false
true
}
et() {
set -e
true
}
ef() {
set -e
false
}
etf() {
set -e
true
false
}
eft() {
set -e
false
true
}
st() {( set -e
true
)}
sf() {( set -e
false
)}
stf() {( set -e
true
false
)}
sft() {( set -e
false
true
)}
for test in t f tf ft _ et ef etf eft _ st sf stf sft; do
if [ "$test" = '_' ]; then
echo ""
elif "$test"; then
echo "$test: pass"
else
echo "$test: fail"
fi
done
Output on my machine
t: pass
f: fail
tf: fail
ft: pass
et: pass
ef: fail
etf: fail
eft: pass
st: pass
sf: fail
stf: fail
sft: pass
Desired output
Without significantly changing source in functions themselves, i.e. not adding an if/then or || return to every line in the function.
t: pass
f: fail
tf: fail
ft: fail
et: pass
ef: fail
etf: fail
eft: fail
st: pass
sf: fail
stf: fail
sft: fail
Or at least pass/fail/fail/fail in one group, so I can use that approach for writing robust functions.
With accepted solution
With the accepted solution, the first four tests give the desired result. The other two groups don't, but are irrelevant since those were my own attempts to work around the issue. The root cause is that the "don't errexit the script when if/while-condition fails" behaviour propagates into any functions called in the condition, rather than just applying to the end-result.
How to make errexit behaviour work in bash functions
Call the function not inside if.
I expect any function containing f in its name (i.e. a false line in its source) to return error-code
Weeeeeell, your expectancy does not match reality. It is not how it is implemented. And flag errexit will exit your script, not return error-code.
Desired output
You can:
Download Bash or other shell sources or write your own and implement the behavior that you want to have.
consider extending the behavior with like shopt -s errexit_but_return_nonzero_in_if_contexts or something shorter
Run it in a separate shell.
elif bash -ec "$(declare -f "$test"); $test"; then
Write a custom loadable to "clear" CMD_IGNORE_RETURN flag when Bash enters contexts in which set -e should be ignored. I think for that static COMMAND *currently_executing_command; needs to be extern and then just currently_executing_command.flags &= ~CMD_IGNORE_RETURN;.
You can do a wrapper where you temporarily disable errexit and get return status, but you have to call it outside of if:
errreturn() {
declare -g ERRRETURN
local flags=$(set +o)
set +e
(
set -e
"$#"
)
ERRRETURN=$?
eval "$flags"
}
....
echo ""
else
errreturn "$test"
if (( ERRRETURN == 0 )); then
echo "$test: pass"
else
echo "$test: fail"
fi
fi
For flags I can specify description which appers in --help command
flag.String("a", "", "Is is a flag")
But I don't have flags, only arguments, I use cli like this
mycommand start 4
Is it possible use --help to see description to "start" (and other) arguments?
Since this is not directly supported by flags, I know only of alecthomas/kong which does include argument usage:
package main
import "github.com/alecthomas/kong"
var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
}
You will get with shell --help rm:
$ shell --help rm
usage: shell rm <paths> ...
Remove files.
Arguments:
<paths> ... Paths to remove. <====== "usage" for cli arguments (not flags)!
Flags:
--debug Debug mode.
-f, --force Force removal.
-r, --recursive Recursively remove files.
When I am running below code
package main
import (
"flag"
"fmt"
)
func main() {
i := flag.Int("i", 0, "Any integer value")
b := flag.Bool("b", false, "Any boolean value")
s := flag.String("s", "Hello", "Any string value")
flag.Parse()
fmt.Println("-i", *i)
fmt.Println("-b", *b)
fmt.Println("-s", *s)
}
go run main.go -i 33 -b true -s hi
-i 33
-b true
-s Hello
go run main.go -i 33 -s hi
-i 33
-b false
-s hi
go run main.go -i 33 -s hi -b true
-i 33
-b true
-s hi
go run main.go -i 33 -b true -s hi
-i 33
-b true
-s Hello
Why "-s" command line argument not working when it passed at the end
Thanks in advance
It is because of the -b boolean flag. A boolean flag tests for the existence of flag, it does not test for the argument to the flag. That is:
go run main.go -b
will output -b true, and
go run main.go
will output -b false.
go run main.go -b false
will output -b true, because the -b flag is given. false is not a recognized argument, so it stops processing there.
If you want to use true/false, you have to use this format:
go run main.go -i 33 -b=false -s hi
This should also work (here, -b is true):
go run main.go -i 33 -b -s hi
I have a strange situation. For different parameters I always get the same result
function test
{
while getopts 'c:S:T:' opt ; do
case "$opt" in
c) STATEMENT=$OPTARG;;
S) SCHEMA=$OPTARG;;
T) TABLE=$OPTARG;;
esac
done
echo "$STATEMENT, $SCHEMA, $TABLE"
}
test -c CREATE -S schema1 -T tabela1
test -c TRUNCATE -S schema2 -T tabela2
test -c DROP -S schema3 -T tabela3
Result:
CREATE, schema1, tabela1
CREATE, schema1, tabela1
CREATE, schema1, tabela1
What is failed in my script?
In bash, you need to localize the $OPTIND variable.
function test () {
local OPTIND
Otherwise it's global and the next call to getopts returns false (i.e. all arguments processed). Consider localizing the other variables, too, if they're not used outside of the function.
You can also just set it to zero.