├── LICENSE.txt ├── Makefile ├── README.md ├── UPGRADING.md ├── cmdarg.sh ├── cmdarg.spec ├── test.sh └── tests ├── test_clean_state.sh ├── test_dashdash.sh ├── test_equals.sh ├── test_helpers.sh ├── test_info.sh ├── test_longopt.sh ├── test_types.sh └── test_validators.sh /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Andrew Kesterson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION:=$(shell if [ -d .git ]; then bash -c 'gitversion.sh | grep "^MAJOR=" | cut -d = -f 2'; else source version.sh && echo $$MAJOR ; fi) 2 | RELEASE:=$(shell if [ -d .git ]; then bash -c 'gitversion.sh | grep "^BUILD=" | cut -d = -f 2'; else source version.sh && echo $$BUILD ; fi) 3 | DISTFILE=./dist/cmdarg-$(VERSION)-$(RELEASE).tar.gz 4 | SPECFILE=cmdarg.spec 5 | ifndef RHEL_VERSION 6 | RHEL_VERSION=5 7 | endif 8 | ifeq ($(RHEL_VERSION),5) 9 | MOCKFLAGS=--define "_source_filedigest_algorithm md5" --define "_binary_filedigest_algorithm md5" 10 | endif 11 | 12 | RHEL_RELEASE:=$(RELEASE).el$(RHEL_VERSION) 13 | SRPM=cmdarg-$(VERSION)-$(RHEL_RELEASE).src.rpm 14 | RPM=cmdarg-$(VERSION)-$(RHEL_RELEASE).noarch.rpm 15 | RHEL_DISTFILE=./dist/cmdarg-$(VERSION)-$(RHEL_RELEASE).tar.gz 16 | 17 | ifndef PREFIX 18 | PREFIX='' 19 | endif 20 | 21 | DISTFILE_DEPS=$(shell find . -type f | grep -Ev '\.git|\./dist/|$(DISTFILE)') 22 | 23 | all: ./dist/$(RPM) 24 | 25 | # --- PHONY targets 26 | 27 | .PHONY: clean srpm rpm gitclean dist 28 | clean: 29 | rm -f $(DISTFILE) 30 | rm -fr dist/cmdarg-$(VERSION)-$(RELEASE)* 31 | 32 | dist: $(DISTFILE) 33 | 34 | srpm: ./dist/$(SRPM) 35 | 36 | rpm: ./dist/$(RPM) ./dist/$(SRPM) 37 | 38 | gitclean: 39 | git clean -df 40 | 41 | # --- End phony targets 42 | 43 | version.sh: 44 | gitversion.sh > version.sh 45 | 46 | $(DISTFILE): version.sh 47 | mkdir -p dist/ 48 | mkdir dist/cmdarg-$(VERSION)-$(RELEASE) || rm -fr dist/cmdarg-$(VERSION)-$(RELEASE) 49 | rsync -aWH . --exclude=.git --exclude=dist ./dist/cmdarg-$(VERSION)-$(RELEASE)/ 50 | cd dist && tar -czvf ../$@ cmdarg-$(VERSION)-$(RELEASE) 51 | 52 | $(RHEL_DISTFILE): $(DISTFILE) 53 | cd dist && \ 54 | cp -R cmdarg-$(VERSION)-$(RELEASE) cmdarg-$(VERSION)-$(RHEL_RELEASE) && \ 55 | tar -czvf ../$@ cmdarg-$(VERSION)-$(RHEL_RELEASE) 56 | 57 | ./dist/$(SRPM): $(RHEL_DISTFILE) 58 | rm -fr ./dist/$(SRPM) 59 | mock -r epel-$(RHEL_VERSION)-noarch --buildsrpm --verbose --spec $(SPECFILE) $(MOCKFLAGS) --sources ./dist/ --resultdir ./dist/ --define "version $(VERSION)" --define "release $(RHEL_RELEASE)" 60 | 61 | ./dist/$(RPM): ./dist/$(SRPM) 62 | rm -fr ./dist/$(RPM) 63 | mock --verbose -r epel-$(RHEL_VERSION)-noarch ./dist/$(SRPM) --resultdir ./dist/ --define "version $(VERSION)" --define "release $(RHEL_RELEASE)" 64 | 65 | uninstall: 66 | rm -f $(PREFIX)/usr/lib/cmdarg.sh 67 | 68 | 69 | install: 70 | mkdir -p $(PREFIX)/usr/lib 71 | install ./cmdarg.sh $(PREFIX)/usr/lib/cmdarg.sh 72 | 73 | MANIFEST: 74 | echo /usr/lib/cmdarg.sh > MANIFEST 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cmdarg 2 | ====== 3 | 4 | [![Build Status](http://jenkins.aklabs.net/buildStatus/icon?job=cmdarg-test)](http://jenkins.aklabs.net/job/cmdarg-test/) 5 | 6 | Requires bash >= 4. 7 | 8 | source cmdarg.sh 9 | 10 | Enjoy 11 | 12 | Installation 13 | ============ 14 | 15 | From source 16 | 17 | cd cmdarg 18 | make install 19 | 20 | From RPM 21 | 22 | # add http://yum.aklabs.net/el/[5|6]/noarch as a yum repo for your system 23 | yum install cmdarg 24 | 25 | Usage 26 | ===== 27 | 28 | cmdarg is a helper library I wrote for bash scripts because, at current, option parsing in bash (-foo bar, etc) is really hard, lots harder than it should be, given bash's target audience. So here's my solution. There are 4 functions you will care about: 29 | 30 | cmdarg 31 | cmdarg_info 32 | cmdarg_parse 33 | cmdarg_usage 34 | 35 | TL;DR 36 | ===== 37 | 38 | Cmdarg lets you specify arguments (things you require), options (things you don't require), and lets you easily parse them. The arguments can be set on the command line either via '-X' or '--Y', where X is the short option and Y is the long option. 39 | 40 | cmdarg 'r:' 'required-thing' 'Some thing I require' 41 | cmdarg 'o?' 'optional-thing' 'Some optional thing' 42 | cmdarg 'b' 'boolean-thing' 'Some boolean thing' 43 | cmdarg_parse "$@" 44 | 45 | echo ${cmdarg_cfg['required-thing']} 46 | echo ${cmdarg_cfg['optional-thing']} 47 | echo ${cmdarg_cfg['boolean-thing']} 48 | 49 | # your_script.sh -r some_thingy -b -o optional_thing 50 | # your_script.sh --required-thing some_thingy --boolean-thing 51 | 52 | Because cmdarg does key off of the short options, you are limited to as many options as you have unique single characters in your character set (likely 61 - 26 lower & upper alpha, +9 numerics). 53 | 54 | cmdarg 55 | ====== 56 | 57 | This function is used to tell the library what command line arguments you accept. 58 | 59 | cmdarg FLAGS LONGOPT DESCRIPTION DEFAULT VALIDATOR 60 | 61 | Examples: 62 | 63 | cmdarg 'f' 'boolean-flag' 'Some boolean flag' 64 | cmdarg 'a:' 'required-arg' 'Some required arg' 65 | cmdarg 'a?' 'optional-arg' 'Some optional arg with a default' 'default_value' 66 | cmdarg 'a:' 'required-validated-arg' 'Some required argument with a validator' '' validator_function 67 | 68 | *FLAGS* : The first argument to cmdarg must be an argument specification. Argument specifications take the form 'NOT', where: 69 | 70 | - N : The single letter Name of the argument 71 | - O : Whether the option is optional or not. Use ':' here for a required argument, '?' for an optional argument. If you provide a default value for a required argument (:), then it becomes optional. 72 | - T : The type. Leave empty for a string argument, use '[]' for an array argument, use '{}' for a hash argument. 73 | 74 | If O and T are both unset, and only the single letter N is provided, then the argument is a boolean argument which will default to false. 75 | 76 | *LONGOPT* is a long option name (such as long-option-name) that can be used to set your argument via --LONGOPT instead of via -N (from your FLAGS). 77 | 78 | *DESCRIPTION* is a string that describes what this argument is for. 79 | 80 | *DEFAULT* is any default value that you want to be set for this option if the user does not specify one 81 | 82 | *VALIDATOR* The name of a bash function which will validate this argument (see VALIDATORS below). 83 | 84 | 85 | Validators 86 | ========== 87 | 88 | Validators must be bash function names - not bash statements - and they must accept one argument, being the value to validate. Validators are not told the name of the option, only the value. Validator functions must return 0 if they value they are given is valid, and 1 if it is invalid. Validators should refrain from producing output on stdout or stderr. 89 | 90 | For example, this is a valid validator: 91 | 92 | function validate_int 93 | { 94 | if [[ "$1" =~ ^[0-9]+$ ]] ; then 95 | return 0 96 | fi 97 | return 1 98 | } 99 | 100 | cmdarg 'x' 'x-option' 'some opt' '' validate_int 101 | 102 | ... While this is not: 103 | 104 | cmdarg 'x' 'x-option' 'some opt' '' "grep -E '^[0-9]+$'" 105 | 106 | There is an exception to this form, and that is for hash arguments (e.g. 'x:{}'). In this instance, the key for the argument (e.g. -x key=value) is to be considered a part of the value, and the user may want to validate this as well as the value. In this instance, when calling a validator against a hash argument, the validator will receive a second argument, which is the key of the hash being validated. For example: 107 | 108 | # When we receive 109 | cmdarg 'x:{}' 'something' 'something' my_validator 110 | cmdarg_parse -x hashkey=hashvalue 111 | # ... we will call 112 | my_validator hashvalue hashkey 113 | 114 | cmdarg_info 115 | =========== 116 | 117 | This function sets up information about your program for use when printing the help/usage message. Again, see cmdarg.sh for the latest syntax. 118 | 119 | cmdarg_info "header" "Some script that needed argument parsing" 120 | cmdarg_info "author" "Some Poor Bastard " 121 | cmdarg_info "copyright" "(C) 2013" 122 | 123 | cmdarg_parse 124 | ============ 125 | 126 | This command does what you expect, parsing your command line arguments. However you must pass your command line arguments to it. Generally this means: 127 | 128 | cmdarg_parse "$@" 129 | 130 | ... Beware that "$@" will change depending on your context. So if you have a main() function called in your script, you need to make sure that you pass "$@" from the toplevel script in to it, otherwise the options will be blank when you pass them to cmdarg_parse. 131 | 132 | Any argument parsed that has a validator assigned, and whose validator returns nonzero, is considered a failure. Any REQUIRED argument that is not specified is considered a failure. However, it is worth noting that if a required argument has a default value, and you provide an empty value to it, we won't know any better and that will be accepted (how do we know you didn't actually *mean* to do that?). 133 | 134 | For every argument integer, boolean or string argument, a global associative array "cmdarg_cfg" is populated with the long version of the option. E.g., in the example above, '-c' would become ${cmdarg_cfg['groupmap']}, for friendlier access during scripting. 135 | 136 | cmdarg 'x:' 'some required thing' 137 | cmdarg_parse "$@" 138 | echo ${cmdarg_cfg['x']} 139 | 140 | For array and hash arguments, you must declare the hash or array beforehand for population: 141 | 142 | declare -a myarray 143 | cmdarg 'a?[]' 'myarray' 'Some array of stuff' 144 | cmdarg_parse "$@" 145 | # Now you will be able to access ${myarray[0]}, ${myarray[1]}, etc. Similarly with hashes, just use declare -A and {}. 146 | 147 | Automatic help messages 148 | ======================= 149 | 150 | cmdarg takes the pain out of creating your --help messages. For example, consider you had this script: 151 | 152 | #!/bin/bash 153 | source /usr/lib/cmdarg.sh 154 | declare -a myarray 155 | 156 | cmdarg_info "header" "Some script that needed argument parsing" 157 | cmdarg_info "author" "Some Poor Bastard " 158 | cmdarg_info "copyright" "(C) 2013" 159 | cmdarg 'R:' 'required-thing' 'Some thing I REALLY require' 160 | cmdarg 'r:' 'required-thing-with-default' 'Some thing I require' 'Some default' 161 | cmdarg 'o?' 'optional-thing' 'Some optional thing' 162 | cmdarg 'b' 'boolean-thing' 'Some boolean thing' 163 | cmdarg 'a?[]' 'myarray' 'Some array of stuff' 164 | cmdarg_parse "$@" 165 | 166 | ... And you ran it with '--help', you would get a nice preformatted help message: 167 | 168 | test.sh (C) 2013 : Some Poor Bastard 169 | 170 | Some script that needed argument parsing 171 | 172 | Required Arguments: 173 | -R,--required-thing v : String. Some thing I REALLY require 174 | 175 | Optional Arguments: 176 | -r,--required-thing-with-default v : String. Some thing I require (Default "Some default") 177 | -o,--optional-thing v : String. Some optional thing 178 | -b,--boolean-thing : Boolean. Some boolean thing 179 | -a,--myarray v[, ...] : Array. Some array of stuff. Pass this argument multiple times for multiple values. 180 | 181 | You can change the formatting of help messages with helper functions. (see Helpers, below). 182 | 183 | Setting arrays and hashes 184 | ========================= 185 | 186 | You can use the cmdarg function to accept arrays and hashes from the command line as well. Consider: 187 | 188 | declare -a array 189 | declare -A hash 190 | cmdarg 'a?[]' 'array' 'Some array you can set indexes in' 191 | cmdarg 'H?{}' 'hash' 'Some hash you can set keys in' 192 | 193 | 194 | your_script -a 32 --array something -H key=value --hash other_key=value 195 | 196 | 197 | echo ${array[0]} 198 | echo ${array[1]} 199 | echo ${hash['key']} 200 | echo ${hash['other_key']} 201 | 202 | The long option names in this form must equal the name of a previously declared array or hash, appropriately. Cmdarg populates that variable directly with options for these arguments. Remember, arrays and hashes must be declared beforehand and must have the same name as the long argument given to their cmdarg option. 203 | 204 | Positional arguments and -- 205 | =========================== 206 | 207 | Like any good option parsing framework, cmdarg understands '--' and positional arguments that are meant to be provided without any kind of option parsing applied to them. So if you have: 208 | 209 | myscript.sh -x 0 --longopt thingy file1 file2 210 | 211 | ... It would seem reasonable to assume that -x and --longopt would be parsed as expected; with arguments of 0 and thingy. But what to do with file1 and file2? cmdarg puts those into a bash indexed array called cmdarg_argv. 212 | 213 | Similarly, cmdarg understands '--' which means "stop processing arguments, the rest of this stuff is just to be passed to the program directly". So in this case: 214 | 215 | myscript.sh -x 0 --longopt thingy -- --some-thing-with-dashes 216 | 217 | ... Cmdarg would parse -x and --longopt as expected, and then ${cmdarg_argv[0]} would hold "--some-thing-with-dashes", for your program to do with what it will. 218 | 219 | Helpers 220 | ======= 221 | 222 | cmdarg is meant to be extensible by default, so there are some places where you can hook into it to change cmdarg's behavior. By changing the members of the cmdarg_helpers hash, like this: 223 | 224 | # Change the way arguments are described in --help 225 | cmdarg_helpers['describe']=my_description_function 226 | # Completely replace cmdarg's builtin --help message generator with your own 227 | cmdarg_helpers['usage']=my_usage_function 228 | 229 | ## Description Helper 230 | 231 | The description helper is used when you are happy with the overall structure of how cmdarg prints your usage message (header, required, optional, footer), but you want to change the way that individual arguments are described. You can do this by setting cmdarg_helpers['describe'] to the name of a bash function which accepts the following parameters (in order): 232 | 233 | * $1 : long option to be described 234 | * $2 : short option to be described 235 | * $3 : argument type being described (will be one of ${CMDARG_TYPE_STRING}, ${CMDARG_TYPE_BOOLEAN}, ${CMDARG_TYPE_ARRAY} or ${CMDARG_TYPE_HASH}) 236 | * $4 : any default value that is set for the option being described 237 | * $5 : The description for the option being described (as provided to 'cmdarg' previously) 238 | * $6 : Flags for the option being described (a logically OR'ed bitmask of ${CMDARG_FLAG_NOARG}, ${CMDARG_FLAG_REQARG}, or ${CMDARG_FLAG_OPTARG} - although we specify this as a bitmask and advise you to treat it as such, in practice, this is usually an assignment of one of those 3 values) 239 | * $7 : The name of any validator (if any) set for the option being described 240 | 241 | This is every piece of information cmdarg keeps related to an argument (aside from its value). You can use these to describe the argument however you please. Your function must print the text description to stdout. The return value of your function is ignored. 242 | 243 | For examples of this behavior, please see ./tests/test_helpers.sh 244 | 245 | ## Usage Helper 246 | 247 | The usage helper is used when you want to completely override cmdarg's built in --help handler. Note that, when you override the usage helper, you will no longer benefit from the description helper, since that is called from inside of the default usage handler. If you override the usage helper, you will have to implement 100% of --help functionality on your own. 248 | 249 | The short options for all specified arguments in cmdarg are kept in a hash ${CMDARG} which maps short arguments (-x) to long arguments (--long-version-of-x). However, it is not recommended that you iterate over this hash directly, as the order of hash key iteration is not guaranteed, so your --help message will change every time. To help with this, cmdarg populates two one-dimensional arrays, CMDARG_OPTIONAL and CMDARG_REQUIRED with the short options of all optional and require arguments, respectively. It is recommended that you iterate over these arrays instead of CMDARG to ensure an ordered output. It is further recommended that you still utilize cmdarg_describe to describe each individual argument, since this abstracts away the logic of how to get the flags, the type, etc of the argument, and lets you continue to provide a standard interface for your API developer(s). 250 | 251 | For examples of this behavior, please see ./tests/test_helpers.sh, the "shunittest_test_describe_and_usage_helper" function. 252 | 253 | Controlling cmdarg's behavior on error 254 | ====================================== 255 | 256 | By default, whenever something happens that cmdarg doesn't like, it will 'return 1' up the stack to the caller. This is different from the old behavior in v1.0, which would 'exit 1'. You can control cmdarg's error behavior by setting the CMDARG_ERROR_BEHAVIOR variable to the function/builtin you want called whenever an error is encountered. 257 | 258 | To get the old v1.0 behavior back, you can, before calling any cmdarg functions: 259 | 260 | CMDARG_ERROR_BEHAVIOR=exit 261 | 262 | If you want cmdarg to call some function of your own when it encounters an error, you could: 263 | 264 | CMDARG_ERROR_BEHAVIOR=my_error_function 265 | 266 | CMDARG_ERROR_BEHAVIOR is treated as a function call (e.g. return or exit) with one argument, the value to return. You will be given no more context regarding the error (and, in fact, you should not expect this to be called unless a fatal error has been encountered, whether during setup or parsing). 267 | 268 | getopt vs getopts 269 | ================= 270 | 271 | cmdarg does not use getopt or getopts for option parsing. Its parser is written in 100% pure bash, and is self contained in cmdarg_parse. It will run the same way anywhere you have bash4. 272 | 273 | Tests 274 | ===== 275 | 276 | cmdarg is testable by the shunit bash unit testing tool (https://www.github.com/akesterson/shunit/). See the tests/ directory. 277 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | Upgrade notes for 1.0 users 2 | =========================== 3 | 4 | cmdarg 2.0 has refined some features that will break your 1.0 software if you do not prepare your codebase for this new functionality. 5 | 6 | ## argument validators 7 | 8 | In 1.0, argument validators were a somewhat documented, but unpublished feature of the 'cmdarg' function. In cmdarg 2.0, they are fully documented and supported. However, their usage has changed, in that free-form bash statements are no longer accepted for validation, and OPTARG is no longer guaranteed to be set in the environment. You should transform any and all validation expressions from this format: 9 | 10 | cmdarg 'x' 'some-arg' 'some description' '' 'echo $OPTARG | grep ...' 11 | 12 | ... To this format, which will work in both 1.0 and 2.0 versions: 13 | 14 | function my_grep_validator { 15 | echo ${1:-$OPTARG} | grep ... 16 | } 17 | 18 | cmdarg 'x' 'some-arg' 'some description' '' my_grep_validator 19 | 20 | Failure to do this will result in cmdarg 2.0 refusing to parse your argument descriptions, preventing your scripts from running. 21 | -------------------------------------------------------------------------------- /cmdarg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if (( BASH_VERSINFO[0] < 4 )); then 4 | echo "cmdarg is incompatible with bash versions < 4, please upgrade bash" >&2 5 | exit 1 6 | fi 7 | 8 | CMDARG_ERROR_BEHAVIOR=return 9 | 10 | CMDARG_FLAG_NOARG=0 11 | CMDARG_FLAG_REQARG=2 12 | CMDARG_FLAG_OPTARG=4 13 | 14 | CMDARG_TYPE_ARRAY=1 15 | CMDARG_TYPE_HASH=2 16 | CMDARG_TYPE_STRING=3 17 | CMDARG_TYPE_BOOLEAN=4 18 | 19 | function cmdarg 20 | { 21 | # cmdarg