Skip to content

Instantly share code, notes, and snippets.

@multun
Created April 17, 2020 17:22

Revisions

  1. multun created this gist Apr 17, 2020.
    155 changes: 155 additions & 0 deletions argparse.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,155 @@
    # parse --my_option into the MY_OPTION variable
    # also handles positional arguments

    __args_list=()
    __required_args_list=()
    __unexport_args_list=()

    normalize_argument() {
    # uppercase
    local argname="${1^^}"
    # replace - by _
    printf '%s\n' "${argname}" | tr - _
    }

    assign_var() {
    # printf can assign variables
    printf -v "$1" '%s' "$2"
    }

    # public arguments get forwarded through the environment
    public_optional_argument() {
    local argname="$(normalize_argument "$1")"
    __args_list+=( "${argname}" )
    assign_var "__args_${argname}" 1
    }

    public_required_argument() {
    local argname="$(normalize_argument "$1")"
    assign_var "__args_${argname}" 1
    __args_list+=( "${argname}" )
    __required_args_list+=( "${argname}" )
    }

    optional_argument() {
    local argname="$(normalize_argument "$1")"
    assign_var "__args_${argname}" 1
    __args_list+=( "${argname}" )
    __unexport_args_list+=( "${argname}" )
    }

    required_argument() {
    local argname="$(normalize_argument "$1")"
    assign_var "__args_${argname}" 1
    __args_list+=( "${argname}" )
    __required_args_list+=( "${argname}" )
    __unexport_args_list+=( "${argname}" )
    }

    undefined_variable() {
    [ "${!1+x}" == '' ]
    }

    __positional_arg_count=0
    positional_argument() {
    assign_var "__positional_arg_$((__positional_arg_count++))" "$(normalize_argument "$1")"
    }

    __arguments_parsed=0
    log_command() {
    if [ "${__arguments_parsed}" = 0 ]; then
    error "call parse_arguments before log_command"
    exit 1
    fi

    local argname

    # print the environment variables
    for argname in "${__args_list[@]}"; do
    if ! undefined_variable "$argname"; then
    printf '%s=%q ' "$argname" "${!argname}"
    fi
    done

    # print the script path
    printf '%s' "$0"

    # print the positional arguments
    if [ "${#positional_arguments[@]}" != 0 ]; then
    printf '%s' ' --'
    printf ' %s' "${positional_arguments[@]}"
    fi

    printf '\n'
    }

    parse_arguments() {
    __arguments_parsed=1
    while [ $# != 0 ]; do
    local argument="$1"
    if [ "$argument" = '--' ]; then
    shift
    positional_arguments=( "$@" )
    break
    fi

    case "$argument" in
    --*)
    local argname="$(normalize_argument "${argument#--}")"
    local slotname="__args_${argname}"
    if [ "${!slotname}" != '1' ]; then
    error "unknown named argument: $argument ($argname)"
    exit 1
    fi

    if [ $# -lt 2 ]; then
    error "missing value for parameter ${argname}"
    exit 1
    fi

    shift
    assign_var "$argname" "$1" ;;
    *) # when meeting a non option argument, stop parsing
    positional_arguments=( "$@" )
    break ;;
    esac

    shift
    done

    # error out when there are too many positional arguments
    local pos_arg_count="${#positional_arguments[@]}"
    if [ "${pos_arg_count}" -gt "${__positional_arg_count}" ]; then
    error "too many positional arguments"
    exit 1
    fi

    # error out when there are too few positional arguments
    if [ "${pos_arg_count}" -lt "${__positional_arg_count}" ]; then
    for ((i=pos_arg_count; i < __positional_arg_count; i++)); do
    local arg_name_var="__positional_arg_$i"
    error "missing positional argument: ${!arg_name_var}"
    done
    exit 1
    fi

    # assign positional argument variables
    for ((i=0; i < __positional_arg_count; i++)); do
    local arg_name_var="__positional_arg_$i"
    local arg_name="${!arg_name_var}"
    assign_var "${arg_name}" "${positional_arguments[i]}"
    done

    # detect missing mandatory arguments
    for arg in "${__required_args_list[@]}"; do
    if undefined_variable "$arg"; then
    error "missing required argument: $arg"
    exit 1
    fi
    done

    # unexport arguments
    for arg in "${__unexport_args_list[@]}"; do
    export -n "$arg"
    done
    }