-
Notifications
You must be signed in to change notification settings - Fork 100
Description
Is your feature request related to a problem? Please describe.
When writing Bash scripts taking command line options, I use getopt
to preprocess the command line, and a while
loop and case
statement to handle individual options:
#!/usr/bin/env bash
# Parse command line or print help text.
script=""
files=()
title=""
items=()
depth=0
ultra=0
verbose=0
getopt=$(getopt --options=t:i:d:uv --longoptions=title:,items:,depth:,ultra,verbose,help -- "$@") || exit
eval set -- $getopt
while true; do
case "$1" in
-t|--title)
title="$2"
shift
shift
;;
-i|--items)
items+=("$2")
shift
shift
;;
-d|--depth)
depth="$2"
shift
shift
;;
-u|--ultra)
ultra=1
shift
;;
-v|--verbose)
verbose=1
shift
;;
--help)
cat <<EOF
Test Amber command line parser
Syntax: $(basename $0) [options]
SCRIPT: Text ......... Script path
FILES: [Text] ........ File paths
-t|--title: Text ..... Title text
-i|--items: [Text] ... Item text
-d|--depth: Num ...... File depth
-u|--ultra: Bool ..... Ultra flag
-v|--verbose: Bool ... Verbose flag
--help ............... Show help text
EOF
exit 1
;;
--)
shift
break
;;
*)
exit 1
;;
esac
done
# Copy positional parameters.
script="$1"
shift
files=("$@")
# Print positional parameters and options.
echo "Script: \"${script}\""
for file in "${files[@]}"; do
echo "File: \"${file}\""
done
echo "Title: \"${title}\""
for item in "${items[@]}"; do
echo "Item: \"${item}\""
done
echo "Depth: ${depth}"
echo "Ultra: ${ultra}"
echo "Verbose: ${verbose}"
This can handle:
- Long options with params
--label=zzz
or--label "zzz"
- Short options with params
-lzzz
- Long options for flags
--flag
- Short options for flags
-f
- Short options for multiple flags
-xyz
expanded to-x -y -z
- Positional arguments left over at the end of the
while
loop
$ ./cli.sh run.sh one.txt two.txt three.txt --title 'My Fruit' -iapple -ibanana -icherry -d4 -uv
Script: "run.sh"
File: "one.txt"
File: "two.txt"
File: "three.txt"
Title: "My Fruit"
Item: "apple"
Item: "banana"
Item: "cherry"
Depth: 4
Ultra: 1
Verbose: 1
Describe the solution you'd like
However, this is such a painful process, I have to copy and adapt a previous script every time. I would like to avoid this pain in Amber, by writing a set of builtin functions. This is my proposal:
#!/usr/bin/env amber
main(args) {
// Parse command line or print help text.
let parser = parser("Test Amber command line parser")
let script = param(parser, "script", "", "Script path")
let files = param(parser, "files", [Text], "File paths")
let title = param(parser, "-t|--title", "", "Title text")
let items = param(parser, "-i|--items", [Text], "Item text")
let depth = param(parser, "-d|--depth", 0, "File depth")
let ultra = param(parser, "-u|--ultra", false, "Ultra flag")
let verbose = param(parser, "-v|--verbose", false, "Verbose flag")
getopt(parser, args)
// Print positional parameters and options.
echo "Script: \"{script}\""
for index, file in files {
echo "File #{index}: \"{file}\""
}
echo "Title: \"{title}\""
for index, item in items {
echo "Item #{index}: \"{item}\""
}
echo "Depth: {depth}"
echo "Ultra: {ultra}"
echo "Verbose: {verbose}"
}
The parser
builtin creates a parser struct to hold the array of (reference counted) argument structs for Bash generation. The param
builtin parses the option match string, a default value (which also defines whether it takes a parameter at all, and the type of that parameter) and a help string, and creates the argument structs for the parser
. The getopt
builtin causes the command line to be parsed. Subsequently, the argument structs are responsible for returning the parsed values for use in the main script.
Describe alternatives you've considered
The only alternative would be to attempt to write this as a standard library function, but (i) it requires interaction between the various components, which is just not possible in pure Amber, and (ii) it would be unable to support typed arguments, which are necessary to (for example) iterate over array arguments.
Additional context
N/A