# params

* [shell parameter parsers](#shell-parameter-parsers)
* [pass parameters to another script](#pass-parameters-to-another-script)
* [Manual Case-Loop Parser](#manual-case-loop-parser)
  * [Bash Equals-Separated](#bash-equals-separated)
  * [with one or more values](#with-one-or-more-values)
  * [additional params on `--`](#additional-params-on---)
  * [shift with uncertain params](#shift-with-uncertain-params)
* [POSIX `getopts` Parser](#posix-getopts-parser)
* [GNU `getopt` Parser](#gnu-getopt-parser)
* [`bash-argparse` Library](#bash-argparse-library)
* [`docopts`](#docopts)
* [`argbash` Code Generator](#argbash-code-generator)
* [`shflags` (Google-style shell option lib)](#shflags-google-style-shell-option-lib)
* [references](#references)

> \[!TIP|label:see also:]
>
> * [\* iMarslo: parameter substitution](https://github.com/marslo/ibook/blob/marslo/docs/cheatsheet/bash/sugar.html#parameter-substitution)

### shell parameter parsers

| PARSER STYLE           | DESCRIPTION                                  | PROS                                                  | CONS                                                             | PORTABILITY          |
| ---------------------- | -------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------- | -------------------- |
| 🔸 case + shift loop   | Manual parsing with a `while` + `case` combo | <p>- Highly customizable<br>- Simple</p>              | <p>- Error-prone for complex flags<br>- No automatic help</p>    | ✅ POSIX-compliant    |
| 🔸 getopts (built-in)  | Built-in short-option parser                 | - Easy for short options (-x)                         | <p>- No long options<br>- No auto help</p>                       | ✅ POSIX-compliant    |
| 🔹 getopt (external)   | External tool to parse long/short flags      | <p>- Supports long & short<br>- Auto reordering</p>   | <p>- Not always installed<br>- Not portable across BSD/Linux</p> | ⚠️ Not portable      |
| 🔸 bash-argparse (lib) | Bash-based helper library                    | <p>- Structured, powerful<br>- Python-like syntax</p> | <p>- Adds dependency<br>- Not always installed</p>               | ❌ Bash-only          |
| 🔹 docopts (doc-style) | Parses from usage doc string                 | <p>- Clean UX<br>- Declarative</p>                    | <p>- Heavy<br>- Requires Python or external</p>                  | ❌ External           |
| 🔸 argbash (codegen)   | Generates parsing boilerplate                | <p>- Auto docs/help<br>- Safe/robust</p>              | - Needs build step                                               | ❌ Not runtime native |
| 🔹 shflags (Google)    | Lightweight Bash lib                         | <p>- Google-style<br>- Nice syntax</p>                | - Needs sourcing a lib                                           | ⚠️ Bash-only         |

| PARSER        | SHORT OPTS | LONG OPTS  | PORTABLE | HELP GEN | EXTERNAL?  |
| ------------- | ---------- | ---------- | -------- | -------- | ---------- |
| Manual Case   | ✅          | ✅ (manual) | ✅ POSIX  | ❌        | ❌          |
| getopts       | ✅          | ❌          | ✅ POSIX  | ❌        | ❌          |
| getopt        | ✅          | ✅          | ❌        | ❌        | ✅ (GNU)    |
| bash-argparse | ✅          | ✅          | ❌ Bash   | ✅        | ✅          |
| docopts       | ✅          | ✅          | ❌        | ✅        | ✅ (Python) |
| argbash       | ✅          | ✅          | ❌        | ✅        | ✅ (tool)   |
| shflags       | ✅          | ✅          | ❌        | ✅        | ✅ (lib)    |

### pass parameters to another script

> \[!NOTE]
>
> * objective:
>
>   ```bash
>   $ ./b.sh 1 2 3 4 5` -> $ ./a.sh 2 3 4 5
>   ```

* b.sh

  ```bash
  #!/bin/bash
  echo """
  b.sh:
    \$1                   : "$1"
    \$#                   : "$#"
    \$@                   : "$@"
    \${@: -1}             : ${@: -1}
    \${@: -2}             : ${@: -2}
    \${@: -3}             : ${@: -2}
    \${@: -\$(( \$#-1 ))} : ${@: -$(( $#-1 ))}
    \$(echo '\${@: -\$(( \$#-1 ))}' | cut -d' ' -f1-) : $(echo "${@: -$(( $#-1 ))}" | cut -d' ' -f1-)
  """

  echo -e "\n'~~> ./a.sh \"\${@: -1}\"': ~~~> ./a.sh ${@: -1}:"
  ./a.sh "${@: -1}"

  echo -e "\n'~~> ./a.sh \$(echo '\${@: -1}' | cut -d' ' -f1-)': ~~~> ./a.sh $(echo "${@: -1}" | cut -d' ' -f1-):"
  ./a.sh $(echo "${@: -1}" | cut -d' ' -f1-)

  echo -e "\n'~~> ./a.sh \"\${@: -4}\"': ~~~> ./a.sh ${@: -4}:"
  ./a.sh "${@: -4}"

  echo -e "\n'~~> ./a.sh \$(echo '\${@: -\$(( \$#-1 ))}' | cut -d' ' -f1-)': ~~~> ./a.sh $(echo "${@: -$(( $#-1 ))}" | cut -d' ' -f1-)"
  ./a.sh $(echo "${@: -$(( $#-1 ))}" | cut -d' ' -f1-)
  ```
* a.sh

  ```bash
  echo """
  a.sh:
    \$1: "$1"
    \$#: "$#"
    \$@: "$@"
    \${@: -$(( $#-2 ))}: ${@: -$(( $#-2 ))}
  """
  ```
* result

  ```bash
  $ ./b.sh 1 2 3 4 5

  b.sh:
    $1                 : 1
    $#                 : 5
    $@                 : 1 2 3 4 5
    ${@: -1}           : 5
    ${@: -2}           : 4 5
    ${@: -3}           : 4 5
    ${@: -$(( $#-1 ))} : 2 3 4 5
    $(echo '${@: -$(( $#-1 ))}' | cut -d' ' -f1-) : 2 3 4 5

  '~~> ./a.sh "${@: -1}"': ~~~> ./a.sh e:
  a.sh:
    $1: 5
    $#: 1
    $@: 5
    ${@: --1}: 5

  '~~> ./a.sh $(echo '${@: -1}' | cut -d' ' -f1-)': ~~~> ./a.sh 5:
  a.sh:
    $1: 5
    $#: 1
    $@: 5
    ${@: --1}: 5

  '~~> ./a.sh "${@: -4}"': ~~~> ./a.sh 2 3 4 5:
  a.sh:
    $1: b
    $#: 4
    $@: 2 3 4 5
    ${@: -2}: 4 5

  '~~> ./a.sh $(echo '${@: -$(( $#-1 ))}' | cut -d' ' -f1-)': ~~~> ./a.sh 2 3 4 5
  a.sh:
    $1: 2
    $#: 4
    $@: 2 3 4 5
    ${@: -2}: 4 5
  ```

### Manual Case-Loop Parser

> \[!NOTE]
>
> * ✅ Pros: Simple, portable
> * ❌ Cons: No built-in validation or help

```bash
#!/usr/bin/env bash
# shellcheck disable=SC1079,SC1078

usage="""USAGE
\t$0\t[-h|--help] [-c|--clean] [-t|--tag <tag>] [-i|--image <image>]
\t\t\t[-v|--ver <new-version>] [-n|--name <name>]
\t\t\t[-p|--prop <key=value>]
"""

while test -n "$1"; do
  case "$1" in
    -c | --clean    ) clean=true        ; shift   ;;
    -t | --tag      ) tag=$2            ; shift 2 ;;
    -i | --image    ) image=$2          ; shift 2 ;;
    -v | --ver      ) ver=$2            ; shift 2 ;;
    -n | --name     ) name=$2           ; shift 2 ;;
    -p | --prop     ) prop=$2           ; shift 2 ;;
    -h | --help | * ) echo -e "${usage}"; exit 0  ;;
  esac
done

echo """
  clean : ${clean}
    tag : ${tag}
  image : ${image}
    ver : ${ver}
   name : ${name}
   prop : ${prop}
"""
```

* result

  ```bash
  $ ./longopts.sh -h
  USAGE
    ./longopts.sh [-h|--help] [-c|--clean] [-t|--tag <tag>] [-i|--image <image>]
                  [-v|--ver <new-version>] [-n|--name <name>]
                  [-p|--prop <key=value>]

  $ ./longopts.sh -c
    clean : true
      tag :
    image :
      ver :
     name :
     prop :

  $ ./longopts.sh -c -t 'ttt' -i 'iii' --ver '1.1.1' --name 'name'
    clean : true
      tag : ttt
    image : iii
      ver : 1.1.1
     name : name
     prop :
  ```

```bash
until [ -z "$1" ]; do # Until all parameters used up
  echo "\$@  : $@ "; shift ;
done

# result
$ ./shift.sh 1 2 3 4 5
$@  : 1 2 3 4 5
$@  : 2 3 4 5
$@  : 3 4 5
$@  : 4 5
$@  : 5
```

#### Bash Equals-Separated

```bash
for i in "$@"; do
  case $i in
    -e=* | --extension=*  ) EXTENSION="${i#*=}"  ;  shift    ;;
    -s=* | --searchpath=* ) SEARCHPATH="${i#*=}" ;  shift    ;;
    --default             ) DEFAULT=YES          ;  shift    ;;
    -* | --*              ) echo "Unknown option $i"; exit 1 ;;
    *                     )                                  ;;
  esac
done

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
```

#### with one or more values

> \[!TIP]
>
> * `positional_args+=("$1")` to add the arguments to an array one by one

```bash
var1=""
flag_verbose=false

# POSIX format using `while [ $# -gt 0 ]`
while [[ $# -gt 0 ]]; do
  case "$1" in
    -v|--verbose ) flag_verbose=true             ;  shift   ;;
    -f|--file    ) var1="$2"                     ;  shift 2 ;;
    --dry-run    ) dryrun=true                   ;  shift   ;;
    -*           ) echo "Unknown option: $1" >&2 ;  exit 1  ;;
    *            ) positional_args+=("$1")       ;  shift   ;;
  esac
done
```

#### additional params on `--`

```bash
#!/usr/bin/env bash
# shellcheck disable=SC2051,SC2086

VERBOSE=false
DEBUG=false
MEMORY=
AOPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true ; shift   ;;
    -d | --debug   ) DEBUG=true   ; shift   ;;
    -m | --memory  ) MEMORY="$2"  ; shift 2 ;;
    --             ) shift        ; AOPT=$@  ;  break ;;
    *              ) break                  ;;
  esac
done

echo """
  VERBOSE       : ${VERBOSE}
  DEBUG         : ${DEBUG}
  MEMORY        : ${MEMORY}
  AOPT          : ${AOPT}
"""

# example
$ ./param.sh -v -m '256Gi' -- --author 'marslo'
  VERBOSE       : true
  DEBUG         : false
  MEMORY        : 256Gi
  AOPT          : --author marslo

$ ./param.sh -v -- -m '256Gi' --author 'marslo'
  VERBOSE       : true
  DEBUG         : false
  MEMORY        :
  AOPT          : -m 256Gi --author marslo
```

#### shift with uncertain params

```bash
echo '---------------- before shift -------------------'
echo ".. \$# : $#"
echo ".. \$@ : $@"
echo ".. \$* : $*"

echo '---------------- after shift -------------------'
opt=''

while [[ $# -gt 0 ]]; do
  case "$1" in
    -*) opt+="$1 "; shift;;
     *) break            ;;
  esac
done

echo ".. \$#   : $#"
echo ".. \$@   : $@"
echo ".. \$*   : $*"
echo ".. \$opt : $opt"

if [[ 0 = "$#" ]]; then
  echo -e "\033[0;33mERROR: must provide at least one non-opt param\033[0m"
  exit 2
elif [[ 1 = "$#" ]]; then
  path=''
  params="$1"
else
  path=${*: -1}
  params=${*: 1:$#-1}
fi

echo '---------------- result -------------------'
echo ">> opt    : ${opt}"
echo ">> params : ${params}"
echo ">> path   : ${path}"
```

### POSIX `getopts` Parser

> \[!NOTE]
>
> * ✅ Pros: POSIX, built-in
> * ❌ Cons: No long options, no multi-word values

```bash
while getopts "f:vd" opt; do
  case $opt in
    f  ) FILE="$OPTARG" ;;
    v  ) VERBOSE=true   ;;
    d  ) DRYRUN=true    ;;
    \? ) echo "Invalid option: -$OPTARG"; exit 1 ;;
  esac
done
shift $((OPTIND -1))
```

```bash
# Reset in case getopts has been used previously in the shell.
OPTIND=1
output_file=''
verbose=0

while getopts "h?vf:" opt; do
  case "$opt" in
    h|\? ) show_help; exit 0   ;;
    v    ) verbose=1           ;;
    f    ) output_file=$OPTARG ;;
  esac
done

shift $((OPTIND-1))
[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
```

### GNU `getopt` Parser

> \[!NOTE]
>
> * MacOS: `brew install gnu-getopt`
> * ✅ Pros: Short + long, nice user UX
> * ❌ Cons: Not portable across BSD/macOS by default (GNU-only)

```bash
#!/usr/bin/env bash

ARGS=$(getopt -o f:vd -l file:,verbose,dry-run -- "$@")
eval set -- "$ARGS"

while true; do
  case "$1" in
    -f|--file    ) FILE="$2"    ;  shift 2 ;;
    -v|--verbose ) VERBOSE=true ;  shift   ;;
    --dry-run    ) DRYRUN=true  ;  shift   ;;
    --           ) shift        ;  break   ;;
    *            ) break                   ;;
  esac
done
```

```bash
# option --output/-o requires 1 argument
LONGOPTS=debug,force,output:,verbose
OPTIONS=dfo:v

! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
  # e.g. return value is 1
  #  then getopt has complained about wrong arguments to stdout
  exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
  case "$1" in
    -d|--debug   ) d=y          ;  shift            ;;
    -f|--force   ) f=y          ;  shift            ;;
    -v|--verbose ) v=y          ;  shift            ;;
    -o|--output  ) outFile="$2" ;  shift 2          ;;
    --           ) shift        ;  break            ;;
    *            ) echo "Programming error"; exit 3 ;;
  esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
```

### `bash-argparse` Library

> \[!NOTE]
>
> * [Anvil/bash-argsparse](https://github.com/Anvil/bash-argsparse)
> * ✅ Pros: Clean, descriptive, built-in help
> * ❌ Cons: Bash-only, external lib needed

```bash
# requires sourcing the library
source argparse.sh

argparse "$@" <<EOF
--file -f [arg]    Path to the file
--verbose -v       Enable verbose output
--dry-run          Enable dry-run mode
EOF
```

### `docopts`

> \[!NOTE]
>
> * [docopt/docopt](https://github.com/docopt/docopt)
> * ✅ Pros: Elegant, self-documenting
> * ❌ Cons: Requires Python + docopts installed

```bash
# usage section (docopts parses this!)
read -r -d '' usage <<EOF
Usage:
  script.sh [-v|--verbose] [--dry-run] -f <file>
Options:
  -f --file=<file>     File to process
  -v --verbose         Verbose output
  --dry-run            Simulate actions
EOF

eval "$(docopts -A args -h "$usage" : "$@")"

# access like $args_file, $args_verbose, $args_dry_run
```

### `argbash` Code Generator

> \[!NOTE]
>
> * [argbash.dev](https://argbash.dev/generate) | [matejak/argbash](https://github.com/matejak/argbash)
> * ✅ Pros: Auto-generates robust scripts
> * ❌ Cons: Requires preprocessing step

```bash
# ARG_POSITIONAL_SINGLE([file], [File to process])
# ARG_OPTIONAL_BOOLEAN([verbose], [v], [Enable verbose])
# ARG_OPTIONAL_BOOLEAN([dry-run], [], [Dry run mode])
# ARG_HELP([Script to demonstrate argbash])
# ARGBASH_GO()

# The above is preprocessed by `argbash` into full Bash script
```

### `shflags` (Google-style shell option lib)

> \[!NOTE]
>
> * [shflags](https://code.google.com/archive/p/shflags/) | [lib/shflags/shflags](https://chromium.googlesource.com/chromiumos/platform/crosutils/+/master/lib/shflags/shflags) | [kward/shflags](https://github.com/kward/shflags)
> * ✅ Pros: Easy-to-read, Google-style
> * ❌ Cons: Needs external lib and sourcing

```bash
. ./shflags

DEFINE_string 'file' '' 'file to use' 'f'
DEFINE_boolean 'verbose' false 'verbose mode' 'v'
DEFINE_boolean 'dry-run' false 'simulate mode' ''

FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

echo "File: ${FLAGS_file}"
```

### references

> * [如何在 Bash 中解析命令行参数？](https://blog.csdn.net/kalman2019/article/details/128575319)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://imarslo.gitbook.io/handbook/basic/util/params.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
