{"id":17941789,"url":"https://github.com/bashtools/kubash","last_synced_at":"2026-04-29T15:02:32.157Z","repository":{"id":258853551,"uuid":"875388192","full_name":"bashtools/Kubash","owner":"bashtools","description":"Advanced Bash command line parser library","archived":false,"fork":false,"pushed_at":"2024-11-23T23:57:49.000Z","size":29,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-03T13:19:58.554Z","etag":null,"topics":["bash","library"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bashtools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-19T20:53:54.000Z","updated_at":"2024-11-23T23:57:53.000Z","dependencies_parsed_at":"2024-10-21T01:50:33.395Z","dependency_job_id":null,"html_url":"https://github.com/bashtools/Kubash","commit_stats":null,"previous_names":["bashtools/kubash"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashtools%2FKubash","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashtools%2FKubash/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashtools%2FKubash/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashtools%2FKubash/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bashtools","download_url":"https://codeload.github.com/bashtools/Kubash/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247006668,"owners_count":20868033,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","library"],"created_at":"2024-10-29T02:04:11.906Z","updated_at":"2026-04-29T15:02:27.137Z","avatar_url":"https://github.com/bashtools.png","language":"Shell","readme":"# Kubash\n\nKubash implements an interleaved state machine to process the command line\noptions. It allows for strict checking of arguments and args. All command line\narguments are processed in order from left to right.\n\nIt supports a single COMMAND followed by a single SUBCOMMAND, then any number\nof ARGs.\n\nEach COMMAND can have a different set of requirements which are controlled by\nsetting the next state at each transition.\n\nprogram --global-options COMMAND --command-options\n SUBCOMMAND --subcommand-options\n ARG1 --subcommand-options ARG2 --subcommand-options ...\n\n--global-options are those before COMMAND.\n--command-options can be after the COMMAND but before SUBCOMMAND.\n--subcommand-options can be anywhere after the SUBCOMMAND.\n\n[MOK](https://github.com/bashtools/mok) uses Kubash for command line parsing\nand uses the same Golang style of code.\n\n# How to use the Kubash Parser in your own Code\n\n## Overview\n\nThe command `mok` uses a command-line parser that follows the format used by\nmost tools found in the kubernetes ecosystem. The parser has been made into a\nlibrary so anyone can use it in their own Bash project and this page is\nintended to help with that.\n\nThe parser is programmed externally by supplying it with callbacks and state\ndefinitions.\n\nIt supports command lines in the following format:\n\n\u003e ./theprogram --global-options COMMAND --command-options SUBCOMMAND --subcommand-options ARG1 --subcommand-options ARG2 --subcommand-options ...\n\nA '-h' in the global options position will show global help.\n\nA '-h' in the command options position will show help for the COMMAND.\n\nA '-h' in the subcommand options positions will show help for the SUBCOMMAND.\n\nThere is no limit to the number of ARGs a command line can have.\n\nThere are four functions for programming the parser:\n\n* PA_add_option_callback()\n  \n  Add a callback to your code that processes options.\n\n  ```bash\n  # PA_add_option_callback adds a callback to the list of callbacks used for\n  # processing options.\n  # Args: arg1 - Null string (for global options), COMMAND or COMMANDSUBCOMMAND.\n  #       arg2 - The function to call.\n  PA_add_option_callback() {\n    _PA[optscallbacks]+=\"$1,$2 \"\n  }\n  ```\n\n* PA_add_usage_callback()\n  \n  Add a callback to your code that shows COMMAND or SUBCOMMAND usage.\n\n  ```bash\n  # PA_add_usage_callback adds a callback to the list of callbacks used for\n  # output of help text.\n  # Args: arg1 - Null string (for global help), COMMAND or COMMANDSUBCOMMAND.\n  #       arg2 - The function to call.\n  PA_add_usage_callback() {\n    _PA[usagecallbacks]+=\"$1,$2 \"\n  }\n  ```\n\n* PA_add_state()\n  \n  Add states using this command, along with optional callbacks for what to call\n  if the state matches. The parser will add the COMMAND and SUBCOMMAND for the\n  program to retrieve via the getters, PA_command() and PA_subcommand().\n\n  ```bash\n  # PA_add_state adds a callback to the list of callbacks used for\n  # programming the state machine.\n  # Args: arg1 - Current state to match.\n  #       arg2 - The value of the state to match.\n  #       arg3 - The new state if arg1 and arg2 match.\n  #       arg4 - The function to call, optional.\n  PA_add_state() {\n    _PA[statecallbacks]+=\"$1,$2,$3,$4 \"\n  }\n  ```\n* PA_set_state()\n  \n  This defaults to COMMAND, but can be set to SUBCOMMAND, or ARG1 for a more\n  traditional Unix style of option processing, shown later.\n\n  ```bash\n  # PA_set_state setter sets the initial state of the parser, which should be one\n  # of COMMAND, SUBCOMMAND, or ARG1.\n  # Args: arg1 - the initial state to set.\n  PA_set_state() {\n    _PA[state]=\"$1\"\n  }\n  ```\n\n## MOK\n\nI gathered all the parser programming commands used in\n[mok](https://github.com/bashtools/mok) in the next code block so it's easy to\nsee the full user interface definition. The callback functions can be viewed by\ndoing `make tags` and then, for example, `vim -t BI_process_options` will take\nyou directly to that function.\n\n```bash\n  # Program the parser's state machine\n  PA_add_state \"COMMAND\" \"build\" \"SUBCOMMAND\" \"\"\n  PA_add_state \"SUBCOMMAND\" \"buildimage\" \"END\" \"\"\n  PA_add_state \"COMMAND\" \"create\" \"SUBCOMMAND\" \"\"\n  PA_add_state \"SUBCOMMAND\" \"createcluster\" \"ARG2\" \"\"\n  PA_add_state \"ARG1\" \"createcluster\" \"ARG2\" \"CC_set_clustername\"\n  PA_add_state \"ARG2\" \"createcluster\" \"ARG3\" \"CC_set_nummasters\"\n  PA_add_state \"ARG3\" \"createcluster\" \"END\" \"CC_set_numworkers\"\n  PA_add_state \"COMMAND\" \"exec\" \"ARG1\" \"\"\n  PA_add_state \"ARG1\" \"exec\" \"END\" \"EX_set_containername\"\n  PA_add_state \"COMMAND\" \"get\" \"SUBCOMMAND\" \"\"\n  PA_add_state \"SUBCOMMAND\" \"getcluster\" \"ARG1\" \"\"\n  PA_add_state \"ARG1\" \"getcluster\" \"END\" \"GC_set_clustername\"\n  PA_add_state \"COMMAND\" \"delete\" \"SUBCOMMAND\" \"\"\n  PA_add_state \"SUBCOMMAND\" \"deletecluster\" \"ARG1\" \"\"\n  PA_add_state \"ARG1\" \"deletecluster\" \"END\" \"DC_set_clustername\"\n  PA_add_state \"COMMAND\" \"build\" \"SUBCOMMAND\" \"\"\n  PA_add_state \"SUBCOMMAND\" \"buildimage\" \"END\" \"\"\n\n  # Set up the parser's option callbacks\n  PA_add_option_callback \"build\" \"BI_process_options\" || return\n  PA_add_option_callback \"buildimage\" \"BI_process_options\" || return\n  PA_add_option_callback \"create\" \"CC_process_options\" || return\n  PA_add_option_callback \"createcluster\" \"CC_process_options\" || return\n  PA_add_option_callback \"exec\" \"EX_process_options\" || return\n  PA_add_usage_callback \"exec\" \"EX_usage\" || return\n  PA_add_option_callback \"get\" \"GC_process_options\" || return\n  PA_add_option_callback \"getcluster\" \"GC_process_options\" || return\n  PA_add_option_callback \"delete\" \"DC_process_options\" || return\n  PA_add_option_callback \"deletecluster\" \"DC_process_options\" || return\n  PA_add_option_callback \"build\" \"BI_process_options\" || return\n  PA_add_option_callback \"buildimage\" \"BI_process_options\" || return\n\n  # Set up the parser's usage callbacks\n  PA_add_usage_callback \"build\" \"BI_usage\" || return\n  PA_add_usage_callback \"buildimage\" \"BI_usage\" || return\n  PA_add_usage_callback \"create\" \"CC_usage\" || return\n  PA_add_usage_callback \"createcluster\" \"CC_usage\" || return\n  PA_add_usage_callback \"get\" \"GC_usage\" || return\n  PA_add_usage_callback \"getcluster\" \"GC_usage\" || return\n  PA_add_usage_callback \"delete\" \"DC_usage\" || return\n  PA_add_usage_callback \"deletecluster\" \"DC_usage\" || return\n  PA_add_usage_callback \"build\" \"BI_usage\" || return\n  PA_add_usage_callback \"buildimage\" \"BI_usage\" || return\n```\n\n## Example\n\nI have a small Bash script called cmdline-player that has grown complex enough\nto need a few command line options. Cmdline-player is used to create the\nscreencasts in [kubernetes-the-hard-way-in-containers](https://github.com/bashtools/kubernetes-the-hard-way-in-containers).\n\nI was lazy when writing cmdline-player. I thought it would be easier and\nshorter than it is now, even though it's only about 260 lines long.\n\nI don't want to worry too much about code style, I just want to bolt on the\nparser and get on with other things. If cmdline-player grows more, then\nfunctions can be split out into single files later and global variables can be\ncleaned up.\n\nMake sure shellcheck is integrated into whatever IDE is being used. It helps\nalot if shellcheck passes with all optional checks. The first thing is to get\nall those variables surrounded by double quotes and curly braces where it tells\nus. The following commands will do that for us, otherwise doing this is a real\npain.\n\n```bash\ncd cmdline-player\nshellcheck --shell bash -o all -i 2250 -f diff cmdline-player | git apply\nshellcheck --shell bash -o all -i 2248 -f diff cmdline-player | git apply\n```\n\n`-i 2250` and `-i 2248` are the codes for checking the format of the variables.\nShellcheck tries to fix the code, with `-f diff`, and uses `git apply` to apply\nthe diff output to the code.\n\nI don't expect this file to grow any more so I'll just append\n`src/lib/parser.sh` close to the end of the cmdline-player file.\n\nThe read-only globals, `OK`, `ERROR`, and `STDERR` need to be defined, as they\nare only declared in parser.sh. Add the following lines close to the top of\ncmdline-player:\n\n```bash\ndeclare -rg STOP=3 OK=0 ERROR=1 STDERR='/dev/stderr'\n```\n\nThat's it for importing the parser library, next let's program the parser.\n\n## Programming the Parser\n\n### Designing the UI\n\nUsing the command line format shown in Overview above the following commands\nwould allow the use of all of the cmdline-player's features:\n\n```none\n# Play and record choosing the window with a click\ncmdline-player FILENAME.scr\n\n# long and short options\ncmdline-player -w \"Name of window\" FILENAME.scr\ncmdline-player --window \"Name of window\" FILENAME.scr\n\n# long and short options\ncmdline-player -n FILENAME.scr\ncmdline-player -norecord FILENAME.scr\n\n# long and short options\ncmdline-player -q\ncmdline-player --query\n```\n\nThe command line interface in the previous code block uses global options only.\nThere are no COMMANDS or SUBCOMMANDS, just options and an ARG1.\n\n### Programming the UI\n\nI will present the changes and explain them as we go.\n\n```bash\n  # Set up the parser\n  setup_parser                             # \u003c- 1\n\n  local retval=\"${OK}\"\n  PA_run \"$@\" || retval=$?                 # \u003c- 2\n  if [[ ${retval} -eq ${ERROR} ]]; then\n    return \"${ERROR}\"\n  elif [[ ${retval} -eq ${STOP} ]]; then\n    return \"${OK}\"\n  fi\n\n  sanity_checks                            # \u003c- 3\n```\n\nThose lines were added to the top of the `main()` function - the first function\nthat cmdline-player calls. The three lines call three functions shown next.\n\n```bash\nsetup_parser() {\n  # Program the parser's state machine\n  PA_set_state \"ARG1\"\n  PA_add_state \"ARG1\" \"\" \"END\" \"set_filename\"\n\n  # Set up the parser's option callbacks\n  PA_add_option_callback \"\" \"process_options\" || return\n\n  # Set up the parser's usage callbacks\n  PA_add_usage_callback \"\" \"usage\" || return\n}\n```\n\nSince we have no COMMAND or SUBCOMMAND states for the Unix-like version,\neverything is set up in the global scope (\"\").\n\n* 'PA_set state \"ARG1\"' sets the initial state to ARG1, completely bypassing\nthe COMMAND and SUBCOMMAND states.\n\n* PA_add_state takes four arguments:\n  \n  1. The current state, in this case ARG1.\n  \n  2. The token for the current state, in this case \"\" as it's the global state.\n  \n  3. The next state to transition to, in this case END since everything is in\n     the global state.\n  \n  4. An optional callback used to set variables, or more.\n\n* PA_add_option_callback takes two arguments:\n  \n  1. The COMMAND/SUBCOMMAND token to process options for.\n  \n  2. The function to call that will process those options.\n\n* PA_add_usage_callback takes two arguments:\n  \n  1. The COMMAND/SUBCOMMAND token for which this usage will apply.\n  \n  2. The function to call when usage needs to be shown.\n\n`PA_run` is the entrypoint for the parser. This will parse the command line\ncalling callbacks as required to process options, show usage, and set other\nvariables from the arguments.\n\n```bash\nsanity_checks() {\n  [[ -z ${FILE} ]] \u0026\u0026 {\n    usage\n    printf 'ERROR: Please provide the file name to play.\\n'\n    exit 1\n  }\n}\n```\n\n`sanity_checks` is run next, to make sure anything that needed to be set is\nactually set.\n\nThat's it for main() and the support functions there.\n\nThe callback functions that were set in `setup_parser` need to be written,\nshown next:\n\n```bash\nset_filename() {\n  FILE=\"$1\"\n}\n```\n\nThe parser calls `set_filename` when ARG1 is found.\n\n```bash\nusage() {\n  printf 'cmdline-player - play commands from a .scr file.'\n}\n```\n\nThe `usage` function outputs the usage to the screen. You can see the full help\ntext in [cmdline-player](/cmdline-player/cmdline-player).\n\n```bash\nprocess_options() {\n  case \"$1\" in\n  -h | --help)\n    usage\n    return \"${STOP}\"\n    ;;\n  -w | --window)\n    WINDOWNAME=\"$2\"\n    return \"$(PA_shift)\"\n    ;;\n  -n | --norecord)\n    E2E=\"yes\"\n    return \"$(PA_shift)\"\n    ;;\n  -q | --query)\n    xwininfo | awk '/xwininfo: Window id:/ { print $NF; }'\n    exit 0\n    ;;\n  *)\n    usage\n    printf 'ERROR: \"%s\" is not a valid option.\\n' \"${1}\" \\\n      \u003e\"${STDERR}\"\n    return \"${ERROR}\"\n    ;;\n  esac\n}\n```\n\nThe `process_options()` function is called by the parser each time a global option is encountered. Key points to note:\n\n* `-h` or `--help` will output usage then return STOP. When the parser sees STOP it will stop parsing and return without error. In this program 'exit 0' could have been called instead of `return \"${STOP}\"`. In `mok` the `exit` command is never used.\n\n* `-w` or `--window` sets the WINDOWNAME global variable to the second argument. If the second argument is used then an extra shift is required. You signal an extra shift by returning PA_shift (a getter - a function call).\n\nThat's all that is required to get the parser working. A parser that can grow\nwithout worry or needing to change lots of code later on.\n\nIf you want to take a look it's here:\n[cmdline-player](https://github.com/my-own-kind/command-line-player).\n\nThat's it!\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbashtools%2Fkubash","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbashtools%2Fkubash","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbashtools%2Fkubash/lists"}