¿Cómo puedo analizar los argumentos de la línea de comandos en Bash?


Digamos que tengo un script que se llama con esta línea:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

O este:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

¿Cuál es la forma aceptada de analizar esto de tal manera que en cada caso (o alguna combinación de los dos) $v, $f, y $d se pondrá todo a true y $outFile será igual a /fizz/someOtherFile?

Author: codeforester, 2008-10-10

29 answers

Método #1: Usando bash sin getopt[s]

Dos formas comunes de pasar argumentos clave-valor-par son:

Bash Separados por espacio (por ejemplo, --option argument) (sin getopt[s])

Uso ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo FILE EXTENSION  = "${EXTENSION}"
echo SEARCH PATH     = "${SEARCHPATH}"
echo LIBRARY PATH    = "${LIBPATH}"
echo DEFAULT         = "${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi

Bash Es igual a Separado (por ejemplo, --option=argument) (sin getopt[s])

Uso ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

Para comprender mejor ${i#*=} busque "Eliminación de subcadenas" en esta guía. Es funcionalmente equivalente a `sed 's/[^=]*=//' <<< "$i"` que llama a un subproceso innecesario o `echo "$i" | sed 's/[^=]*=//'` que llama a dos subprocesos innecesarios.

Método # 2: Usando bash con getopt[s]

De: http://mywiki.wooledge.org/BashFAQ/035#getopts

Limitaciones de Getopt (1) (versiones anteriores, relativamente recientes getopt):

  • no puede manejar argumentos que son cadenas vacías
  • no se pueden manejar argumentos con espacios en blanco incrustados

Las versiones más recientes getopt no tienen estos limitacion.

Además, el shell POSIX (y otros) ofrecen getopts que no tiene estas limitaciones. Aquí hay un ejemplo simplista getopts:

#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
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: $@"

# End of file

Las ventajas de getopts son:

  1. Es más portátil, y funcionará en otros shells como dash.
  2. Puede manejar múltiples opciones individuales como -vf filename de la manera típica de Unix, automáticamente.

La desventaja de getopts es que solo puede manejar opciones cortas (-h, no --help) sin código adicional.

Hay un tutorial getopts que explica lo que significan toda la sintaxis y las variables. En bash, también hay help getopts, que podría ser informativo.

 2021
Author: Bruno Bronosky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-18 07:27:42

Ninguna respuesta menciona getopt mejorado. Y la respuesta mejor votada es engañosa: Ignora las opciones cortas de estilo -⁠vfd (solicitadas por el OP), las opciones después de los argumentos posicionales (también solicitados por el OP) e ignora los errores de análisis. Lugar:

  • Use mejorado getopt desde util-linux o anteriormente GNU glibc.1
  • Funciona con getopt_long() la función C de GNU glibc.
  • Tiene todo útil características distintivas (los otros no las tienen):
    • maneja espacios, caracteres entre comillas e incluso binarios en argumentos2
    • puede manejar opciones al final: script.sh -o outFile file1 file2 -v
    • permite =-opciones largas de estilo: script.sh --outfile=fileOut --infile fileIn
  • Ya es tan viejo3 que a ningún sistema GNU le falta esto (por ejemplo, cualquier Linux lo tiene).
  • Puede probar su existencia con: getopt --test → devuelve el valor 4.
  • Los demás getopt o shell-builtin getopts son de uso limitado.

Las siguientes llamadas

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

Todos devuelven

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

Con lo siguiente myscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo "I’m sorry, `getopt --test` failed in this environment."
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -use ! and PIPESTATUS to get exit code with errexit set
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! 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"

1 getopt mejorado está disponible en la mayoría de los "bash-systems", incluyendo Cygwin; en OS X try brew install gnu-getopt o sudo port install getopt
2 las convenciones POSIX exec() no tienen una forma confiable de pasar NULL binario en argumentos de línea de comandos; esos bytes terminan prematuramente el argumento
3 primera versión lanzada en 1997 o antes (solo la rastreé hasta 1997)

 357
Author: Robert Siemer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-09 18:10:30

De : digitalpeer.com con pequeñas modificaciones

Uso myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

Para comprender mejor ${i#*=} busque "Eliminación de subcadenas" en esta guía. Es funcionalmente equivalente a `sed 's/[^=]*=//' <<< "$i"` que llama a un subproceso innecesario o `echo "$i" | sed 's/[^=]*=//'` que llama a dos subprocesos innecesarios.

 109
Author: guneysus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-20 18:35:46

getopt()/getopts() es una buena opción. Robado de aquí:

El simple uso de "getopt" se muestra en este mini-script:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

Lo que hemos dicho es que cualquiera de, -b, - c o-d serán permitidos, pero ese-c es seguido por un argumento (la" c: "dice eso).

Si llamamos a esto " g " y lo probamos:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

Empezamos con dos argumentos, y "getopt" rompe las opciones y pone a cada uno en su propio argumento. Se también añadir "--".

 102
Author: Matt J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-10 17:03:38

A riesgo de agregar otro ejemplo para ignorar, aquí está mi esquema.

  • maneja -n arg y --name=arg
  • permite argumentos al final
  • muestra errores cuerdos si algo está mal escrito
  • compatible, no usa bashisms
  • legible, no requiere mantener el estado en un bucle

Espero que sea útil para alguien.

while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done
 68
Author: bronson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-21 15:05:05

De manera más sucinta

Script.sh

#!/bin/bash

while [[ "$#" > 0 ]]; do case $1 in
  -d|--deploy) deploy="$2"; shift;;
  -u|--uglify) uglify=1;;
  *) echo "Unknown parameter passed: $1"; exit 1;;
esac; shift; done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

Uso:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify
 52
Author: Inanc Gumus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-07 21:13:24

Llego unos 4 años tarde a esta pregunta, pero quiero devolver. Utilicé las respuestas anteriores como punto de partida para ordenar mi antiguo análisis de adhoc param. Entonces me refactorizado el siguiente código de la plantilla. Maneja tanto parámetros largos como cortos, usando argumentos = o separados por espacio, así como múltiples parámetros cortos agrupados. Finalmente vuelve a insertar cualquier argumento no param en el $1,$2.. variable. Espero que sea útil.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
 39
Author: Shane Day,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-01 01:20:22

Mi respuesta se basa en gran medida en la respuesta de Bruno Bronosky, pero en cierto modo trituré sus dos implementaciones pure bash en una que uso con bastante frecuencia.

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

Esto le permite tener opciones/valores separados por espacios, así como valores definidos iguales.

Para que pueda ejecutar su script usando:

./myscript --foo -b -o /fizz/file.txt

Así Como:

./myscript -f --bar -o=/fizz/file.txt

Y ambos deben tener el mismo resultado final.

VENTAJAS:

  • Permite tanto-arg=valor como-arg valor

  • Funciona con cualquier nombre arg que puedas usar en bash

    • Significa - a o-arg o ar arg o-a-r-g o lo que sea
  • Puro bash. No es necesario aprender / usar getopt o getopts

CONTRAS:

  • No se puede combinar args

    • Significa no-abc. Usted debe hacer-a-b-c

Estos son los únicos pros/contras que puedo pensar en la parte superior de mi cabeza

 23
Author: Ponyboy47,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:33

He encontrado el asunto de escribir análisis portable en scripts tan frustrante que he escrito Argbash - un generador de código FOSS que puede generar los argumentos-análisis de código para su script además de que tiene algunas características agradables:

Https://argbash.io

 23
Author: bubla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-09 10:39:37

Creo que este es bastante simple de usar:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Ejemplo de invocación:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile
 13
Author: Alek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-01 15:15:03

Ampliando la excelente respuesta de @ guneysus, aquí hay un ajuste que permite al usuario usar la sintaxis que prefiera, por ejemplo

command -x=myfilename.ext --another_switch 

Vs

command -x myfilename.ext --another_switch

Es decir, los iguales se pueden reemplazar con espacios en blanco.

Esta "interpretación difusa" puede no ser de su agrado, pero si está haciendo scripts que son intercambiables con otras utilidades (como es el caso de la mía, que debe funcionar con ffmpeg), la flexibilidad es útil.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done
 12
Author: unsynchronized,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-09 14:07:58

Getopts funciona muy bien si #1 lo tiene instalado y #2 tiene la intención de ejecutarlo en la misma plataforma. OSX y Linux (por ejemplo) se comportan de manera diferente a este respecto.

Aquí hay una solución (no getopts) que admite banderas iguales, no iguales y booleanas. Por ejemplo, podría ejecutar su script de esta manera:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done
 8
Author: vangorra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-12 22:17:48

Les doy La Función parse_params que analizará los parámetros desde la línea de comandos.

  1. Es una solución Bash pura, sin utilidades adicionales.
  2. No contamina el alcance global.
  3. Sin esfuerzo te devuelve variables fáciles de usar, sobre las que podrías construir más lógica.
  4. La cantidad de guiones antes de los parámetros no importa (--all es igual a -all es igual a all=all)

El script de abajo es una demostración de trabajo copy-paste. Ver show_use función para entender cómo usar parse_params.

Limitaciones:

  1. No admite parámetros delimitados por espacios(-d 1)
  2. Los nombres de parámetros perderán guiones por lo que --any-param y -anyparam son equivalentes
  3. eval $(parse_params "$@") debe usarse dentro de la función bash (no funcionará en el ámbito global)

#!/bin/bash

# Universal Bash parameter parsing
# Parses equal sign separated params into local variables (--name=bob creates variable $name=="bob")
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Parses un-named params into ${ARGV[*]} array
# Additionally puts all named params raw into ${ARGN[*]} array
# Additionally puts all standalone "option" params raw into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4 (Jun-26-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion
        _escaped=${1/\*/\'\"*\"\'}
        # If equals delimited named parameter
        if [[ "$1" =~ ^..*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key=\"$_val\";"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "$1" =~ ^\-. ]]; then
            # remove dashes
            local _key=${1//\-}
            # skip when key is empty
            if [[ "$_key" == "" ]]; then
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
 8
Author: Oleksii Chekulaiev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-06 13:00:40

Así es como lo hago en una función para evitar romper la ejecución de getopts al mismo tiempo en algún lugar más alto en la pila:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}
 6
Author: akostadinov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-19 07:50:31

EasyOptions no requiere ningún análisis:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi
 6
Author: Renato Silva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-04 16:47:40

Me gustaría ofrecer mi versión de análisis de opciones, que permite lo siguiente:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

También permite esto (podría ser no deseado):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

Usted tiene que decidir antes de usar si = se va a usar en una opción o no. Esto es para mantener el código limpio (ish).

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done
 4
Author: galmok,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-24 10:54:23

Tenga en cuenta que getopt(1) fue un error de vida corta de AT & T.

Getopt fue creado en 1984 pero ya enterrado en 1986 porque no era realmente utilizable.

Una prueba del hecho de que getopt está muy desactualizado es que la página de manual de getopt(1) todavía menciona "$*" en lugar de "$@", que se agregó al Shell de Bourne en 1986 junto con el shell de getopts(1) para tratar con argumentos con espacios dentro.

Por cierto: si está interesado en analizar opciones largas en shell scripts, puede ser de interés saber que la implementación getopt(3) de libc (Solaris) y ksh93 agregaron una implementación de opción larga uniforme que admite opciones largas como alias para opciones cortas. Esto hace que ksh93 y Bourne Shell implementen una interfaz uniforme para opciones largas a través de getopts.

Un ejemplo para opciones largas tomadas de la página man de Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

Muestra cuánto tiempo se pueden usar los alias de las opciones tanto en Bourne Shell como en Bourne Shell. ksh93.

Vea la página de manual de un Shell Bourne reciente:

Http://schillix.sourceforge.net/man/man1/bosh.1.html

Y la página de manual para getopt (3) de OpenSolaris:

Http://schillix.sourceforge.net/man/man3c/getopt.3c.html

Y por último, la página de manual de getopt(1) para verificar el outdated*:

Http://schillix.sourceforge.net/man/man1/getopt.1.html

 4
Author: schily,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-20 21:59:55

Mezcla de argumentos posicionales y basados en flag

Par param = arg (es igual a delimitado)

Mezcla libremente banderas entre argumentos posicionales:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

Se puede lograr con un enfoque bastante conciso:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

Par param arg (espacio delimitado)

Es usualmente más claro no mezclar estilos --flag=value y --flag value.

./script.sh dumbo 127.0.0.1 --environment production -q -d

Esto es un poco arriesgado de leer, pero sigue siendo válido

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

Fuente

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2
 2
Author: Mark Fox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-27 02:42:47

Supongamos que creamos un script de shell llamado test_args.sh como sigue

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

Después de ejecutar el siguiente comando:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

La salida sería:

year=2017 month=12 day=22 flag=true
 2
Author: John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-10 22:49:30

Use los "argumentos" del módulo de bash-modules

Ejemplo:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"
 1
Author: Volodymyr M. Lisivka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-09 16:51:09

Esto también puede ser útil saber, puede establecer un valor y si alguien proporciona entrada, sobreescriba el valor predeterminado con ese valor..

Myscript.sh -f ./ serverlist.txt o simplemente ./myscript.sh (y toma valores predeterminados)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"
 1
Author: Mike Q,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-14 18:01:08

Otra solución sin getopt[s], POSIX, antiguo estilo Unix

Similar a la solución Bruno Bronosky publicó esta aquí es una sin el uso de getopt(s).

La principal característica diferenciadora de mi solución es que permite tener opciones concatenadas juntas al igual que tar -xzf foo.tar.gz es igual a tar -x -z -f foo.tar.gz. Y al igual que en tar, ps etc. el guion inicial es opcional para un bloque de opciones cortas (pero esto se puede cambiar fácilmente). Las opciones largas también son compatibles (pero cuando un bloque comienza con uno, entonces se requieren dos guiones iniciales).

Código con opciones de ejemplo

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

Para el uso de ejemplo por favor vea los ejemplos más abajo.

Posición de las opciones con argumentos

Por lo que vale, las opciones con argumentos no son las últimas (solo las opciones largas tienen que serlo). Por lo tanto, mientras que por ejemplo en tar (al menos en algunas implementaciones) las opciones f deben ser las últimas porque el nombre del archivo sigue (tar xzf bar.tar.gz funciona pero tar xfz bar.tar.gz no) este no es el caso aquí (ver los ejemplos posteriores).

Múltiples opciones con argumentos

Como otra ventaja, los parámetros de la opción se consumen en el orden de las opciones por los parámetros con opciones requeridas. Basta con mirar la salida de mi script aquí con la línea de comandos abc X Y Z (o -abc X Y Z):

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

Opciones largas concatenadas también

También puede tener opciones largas en el bloque de opciones dado que se producen en último lugar en el bloque. Así que el las siguientes líneas de comando son todas equivalentes (incluyendo el orden en el que se procesan las opciones y sus argumentos):

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

Todos estos conducen a:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

No en esta solución

Argumentos opcionales

Las opciones con argumentos opcionales deberían ser posibles con un poco de trabajo, por ejemplo, mirando reenviar si hay un bloque sin guion; el usuario tendría que poner un guion delante de cada bloque después de un bloque con un parámetro que tiene un parámetro opcional. Tal vez esto es demasiado complicado para comunicarse con el usuario, así que mejor solo requiere un guion principal por completo en este caso.

Las cosas se complican aún más con múltiples parámetros posibles. Yo aconsejaría en contra de hacer las opciones tratando de ser inteligente al determinar si el argumento an podría ser para it o no (por ejemplo, con una opción solo toma un número como un argumento opcional) porque esto podría romperse en el futuro.

Personalmente estoy a favor de opciones adicionales en lugar de argumentos opcionales.

Argumentos de opción introducidos con un signo igual

Al igual que con los argumentos opcionales, no soy fan de esto (POR cierto, ¿hay un hilo para discutir los pros/contras de diferentes estilos de parámetros?) pero si quieres esto, probablemente podrías implementarlo tú mismo como lo hiciste en http://mywiki.wooledge.org/BashFAQ/035#Manual_loop con una sentencia de caso --long-with-arg=?* y luego quitar el signo igual (este es, por cierto, el sitio que dice que hacer concatenación de parámetros es posible con un poco de esfuerzo, pero "lo dejó como un ejercicio para el lector", lo que me hizo tomarlos en su palabra, pero empecé desde cero).

Otras notas

Compatible con POSIX, funciona incluso en configuraciones de Busybox antiguas con las que tuve que lidiar (con p.ej. cut, head y getopts faltar).

 1
Author: phk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:36

Solución que conserva argumentos no manejados. Demos Incluidos.

Aquí está mi solución. Es MUY flexible y a diferencia de otros, no debería requerir paquetes externos y maneja los argumentos sobrantes limpiamente.

El uso es: ./myscript -flag flagvariable -otherflag flagvar2

Todo lo que tienes que hacer es editar la línea validflags. Antepone un guion y busca en todos los argumentos. Luego define el siguiente argumento como el nombre de la bandera, por ejemplo,

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

El código principal (versión corta, detallada con ejemplos más abajo, también un versión con errores):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

La versión detallada con demos de echo integradas:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

El último, este error se produce si se pasa un argumento inválido.

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

Pros: Lo que hace, lo maneja muy bien. Conserva argumentos no utilizados que muchas de las otras soluciones aquí no. También permite que las variables sean llamadas sin ser definidas a mano en el script. También permite la prepoblación de variables si no se da ningún argumento correspondiente. (Ver verbose ejemplo).

Contras: No se puede analizar una sola cadena arg compleja e.g.- xcvf procesaría como un solo argumento. Sin embargo, podría escribir código adicional en el mío que agregue esta funcionalidad.

 1
Author: Noah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-29 04:07:33

La respuesta principal a esta pregunta parecía un poco complicada cuando la probé here aquí está mi solución que he encontrado que es más robusta:

boolean_arg=""
arg_with_value=""

while [[ $# -gt 0 ]]
do
key="$1"
case $key in
    -b|--boolean-arg)
    boolean_arg=true
    shift
    ;;
    -a|--arg-with-value)
    arg_with_value="$2"
    shift
    shift
    ;;
    -*)
    echo "Unknown option: $1"
    exit 1
    ;;
    *)
    arg_num=$(( $arg_num + 1 ))
    case $arg_num in
        1)
        first_normal_arg="$1"
        shift
        ;;
        2)
        second_normal_arg="$1"
        shift
        ;;
        *)
        bad_args=TRUE
    esac
    ;;
esac
done

# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
    echo "first_normal_arg: $first_normal_arg"
    echo "second_normal_arg: $second_normal_arg"
    echo "boolean_arg: $boolean_arg"
    echo "arg_with_value: $arg_with_value"
    exit 0
fi

if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
    echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi
 1
Author: Daniel Bigham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-01 12:09:07

Este ejemplo muestra cómo usar getopt y eval y HEREDOC y shift para manejar parámetros cortos y largos con y sin un valor requerido que sigue. También la instrucción switch/case es concisa y fácil de seguir.

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, don't change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

Las líneas más significativas del script de arriba son estas:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

Corto, directo, legible y maneja casi todo (en mi humilde opinión).

Espero que eso ayude a alguien.

 1
Author: phyatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-07 18:25:44

Tengo que escribir un ayudante de bash para escribir una buena herramienta de bash

Inicio del proyecto: https://gitlab.mbedsys.org/mbedsys/bashopts

Ejemplo:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

Dará ayuda:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

Disfruta :)

 1
Author: Emeric Verschuur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-20 21:30:19

Aquí está mi enfoque - usando regexp.

  • no hay getopts
  • maneja bloque de parámetros cortos -qwerty
  • maneja parámetros cortos -q -w -e
  • maneja opciones largas --qwerty
  • puede pasar el atributo a la opción corta o larga (si está utilizando el bloque de opciones cortas, el atributo se adjunta a la última opción)
  • puede usar espacios o = para proporcionar atributos, pero los atributos coinciden hasta encontrar guion + espacio "delimitador", por lo que en --q=qwe ty qwe ty es un atributo
  • maneja la mezcla de todos los anteriores, por lo que -o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ribute es válido

Script:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done
 1
Author: a_z,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-17 16:48:58

Aquí está mi solución mejorada de la respuesta de Bruno Bronosky usando matrices variables.

Le permite mezclar la posición de los parámetros y darle una matriz de parámetros que preserva el orden sin las opciones

#!/bin/bash

echo $@

PARAMS=()
SOFT=0
SKIP=()
for i in "$@"
do
case $i in
    -n=*|--skip=*)
    SKIP+=("${i#*=}")
    ;;
    -s|--soft)
    SOFT=1
    ;;
    *)
        # unknown option
        PARAMS+=("$i")
    ;;
esac
done
echo "SKIP            = ${SKIP[@]}"
echo "SOFT            = $SOFT"
    echo "Parameters:"
    echo ${PARAMS[@]}

Mostrará por ejemplo:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile
 1
Author: Masadow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-05 09:17:12

Quiero enviar mi proyecto: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"

Así de simple. El entorno se rellenará con variables con el mismo nombre que los argumentos

 0
Author: Thanh Trung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-16 17:45:50