2016-07-11 7 views
1

Ich schrieb ein Skript in Bash dient als Vorlage für mehrere Monitore. Ich wähle getopt, um lange Optionen auf CLI verwenden zu können. Ich habe jedoch einige Probleme, es richtig zu implementieren.Getopt nicht gut analysieren Bash

Das gesamte Skript ist viel länger, aber das ist der relevante Teil:

#!/bin/bash 
# 
# FUNCTION 
# main 
# DESCRIPTION 
# Main function. Everything will be called from here 
# ARGS 
# Nothing 
# 
# RETURN CODE 
# Nothing 
# 
# Main function. Everything will be called from here 
main() { 
    # Parse the options and arguments 
    parse_options "${@}" 

    # Check if the interval is set to a valid number 
    check_interval 
} 

# 
# FUNCTION 
# check_interval 
# DESCRIPTION 
# Checks if a number is valid 
# ARGS 
# 1: number to be checked 
# RETURN CODE 
# 0: valid 
# 1: invalid 
# 
check_interval() { 
    # We don't have to worry if interval is set at all, because getopt is already doing this 
    if (! check_number_pos ${arginterval}); then 
    echo "Error: invalid interval: ${arginterval}" 
    show_usage 
    exit 2 
    fi 
} 

# 
# FUNCTION 
# show_usage 
# DESCRIPTION 
# This is the Usage section. We a showing the Usage according to docopt standards 
# ARGS 
# Nothing 
# RETURN CODE 
# Nothing 
# 
show_usage() { 
    echo "Usage:"                   >&2 
    echo " ${THIS_SCRIPT_NAME} -i|--interval=<interval in s> [-r | --random [--randomwait=<wait in s>]] [-v|--verbose] [-d|--debug] [--colors]" >&2 
    echo " ${THIS_SCRIPT_NAME} [-h|--help]"            >&2 
} 

# 
# FUNCTION 
# check_number_pos 
# DESCRIPTION 
# Checks if a number is valid and positive 
# ARGS 
# 1: number to be checked 
# 
# RETURN CODE 
# 0: valid and positive 
# 1: invalid or negative 
# 
check_number_pos() { 
    local returnval 
    if [[ "${1}" =~ ^[0-9]+$ ]]; then 
    returnval=0 
    else 
    returnval=1 
    fi 
    return ${returnval} 
} 

# 
# FUNCTION 
# parse_options 
# DESCRIPTION 
# Parse options from command line 
# ARGS 
# @: Arguments and options as given at CLI 
# RETURN CODE 
# Nothing 
# 
parse_options() { 
    # Use getopt(1) to parse options according to POSIX. If it fails, an error is shown, and we're showing the Usage and exit 
    # Add new options here and also in the case-statement below. 
    # Short options must be added in the 'options'-section 
    # Long options must be added in the 'longoptions'-section 
    # All short options must have a long equivalent 
    # The --name is set so the error-output will not show 'getopt'-errors but neat <application name>-errors 
    # Options and longoptions have the following format: 
    # <letter>  Option without argument 
    # <letter>:  Option with mandarory argument 
    # <letter>::  Option with optional argument <- this is broken for short options and long options without '='. Don't use it! 
    local -r GETOPT=$(getopt --name ${0} --options hrvdi: --longoptions help,random,verbose,debug,colors,randomwait:,interval: -- "${@}") 

    if [ ${?} != 0 ]; then 
    echo "Error: Error while getting arguments" 
    show_usage 
    exit 127; 
    fi 

    # No options or arguments given. Show Usage. 
    if [[ "${GETOPT}" == " --" ]]; then 
    show_usage 
    exit 127; 
    fi 

    # Evaluate GETOPT. We need this to have the quotes in the output of getopt(1) interpreted. 
    eval set -- "${GETOPT}" 

    # Walk through all the options. Don't put too much code in here, just point to a function or set a variable. 
    # Please note, all new options need to be added here but also in the GETOPT line above. 
    # Note: shift removes the first value from the string, so the option itself will be removed from the GETOPT-string, and the argument is available in $1 
    # After using an argument, please shift again, so the next option will be the first value in GETOPT 
    while true; 
    do 
    case "${1}" in 
     -i|--interval) 
     shift 
     arginterval=${1} 
     shift 
     ;; 
     -r|--random) 
     shift 
     flagrandom=1 
     ;; 
     --randomwait) 
     shift 
     flagrandom=1 
     argrandom=${1} 
     shift 
     ;; 
     -v|-d|--verbose|--debug) 
     flagdebug=1 
     shift 
     ;; 
     --colors) 
     flagcolors=1 
     shift 
     ;; 
     -h|--help) 
     #show_help 
     exit 0 
     ;; 
     --) 
     shift 
     break 
     ;; 
     -*) 
    echo "Error: unrecognized option ${1}" 
     show_usage 
     exit 127 
     ;; 
     *) 
     show_usage 
     exit 127 
     ;; 
    esac 
    done 
} 

#Call main function after all 
main "${@}" 

Nun, wenn ich das Skript den richtigen Weg nennen, geht alles glatt:

$ ./test1.sh -i 10 

Als ich vergiss das argument, es macht auch was ich will:

$ ./test1.sh -i 
./test1.sh: option requires an argument -- 'i' 
Usage: 
    -i|--interval=<interval in s> [-r | --random [--randomwait=<wait in s>]] [-v|--verbose] [-d|--debug] [--colors] 
    [-h|--help] 

Aber wenn ich einfach das Argument vergesse und ein addiere andere, es scheitert:

$ ./test1.sh -i --colors 
Error: invalid interval: --colors 
Usage: 
    -i|--interval=<interval in s> [-r | --random [--randomwait=<wait in s>]] [-v|--verbose] [-d|--debug] [--colors] 
    [-h|--help] 

Dies geschieht, weil ich, wenn das Intervall prüfe eine ganze Zahl ist, sondern auch für andere Zwecke, dies ist eine gefährliche Sache Wie kann ich den Fall zu ändern, so dass es die Optionen als Argumente nicht gelesen werden ? Bisher dient mir getopt nicht sehr gut, da ich auch auf einen anderen Bug/Feature stieß: das 'optionale Argument' (: :) funktioniert nicht so, wie ich es erwartet habe, da es nur mit einem '=' zwischen Option funktioniert und Argument.

Versionen:

$ getopt -V 
getopt (enhanced) 1.1.4 
$ bash --version 
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu) 

Antwort

2

getopt weiß nicht, über die Semantik der Optionen. Für alles, was es weiß, --colors ist ein gültiges Argument für die -i Option. Sie müssen diese Art von Fehlern leider selbst überprüfen, wenn Sie mit ihnen umgehen wollen.

while true; 
    do 
    case "${1}" in 
     -i|--interval) 
     shift 
     arginterval=${1} 
     if [[ $arginterval = -* ]]; then 
      printf 'You appear to have forgotten the interval argument before the %s option\n' "$arginterval" >&2 
      exit 1 
     fi 
     shift 
     ;; 

    ... 
+2

In der Tat; in ähnlicher Weise erfordert 'getup', dass ein _optional_ option-Argument direkt an die Option _ angehängt wird - ohne Trennzeichen in der Kurzform (zB' -i10'), mit '=' als Trennzeichen in der langen Form (zB '- -interval = 10') - weil das der einzige unmissverständliche Weg ist, es zu tun. – mklement0

+0

Ok, vielleicht versuche ich, die Gedanken zu narrensicher zu machen, aber ich habe erwartet, dass getopt versteht, dass - Farben eine "Option" sind, weil ich es ihm gesagt habe. Ich werde dem Skript weitere Überprüfungen hinzufügen, um fehlerhafte Eingaben zu vermeiden. –

+0

'--colors' können eine Option sein, aber es gibt keine Möglichkeit, 'getopt' zu sagen, dass es nicht auch ein gültiges Argument für' -i' sein kann. – chepner