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




script linux ejemplos (20)

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

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

Separación de espacio de Bash (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 Equals-Separated (por ejemplo, --option=argument ) (sin getopt [s])

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

#!/bin/bash

for i in "[email protected]"
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/[^=]*=//'` `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 de getopt ):

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

Las versiones más recientes de getopt no tienen estas limitaciones.

Además, el shell POSIX (y otros) ofrecen getopts que no tienen estas limitaciones. Aquí hay un ejemplo simplista de 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: [email protected]"

# 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 en la forma 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 de getopts que explica lo que significan todas las sintaxis y variables. En bash, también hay help getopts , que puede ser informativo.

https://code.i-harness.com

Digamos, 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 manera tal que en cada caso (o una combinación de los dos) $v , $f , y $d todos se configurarán como true y $outFile será igual a /fizz/someOtherFile ?


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

Solución que conserva los argumentos no manejados. Incluye demostraciones.

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

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

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

#!/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"

El código principal (versión corta, detallado con ejemplos más abajo, también una versión con error):

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

La versión detallada con demostraciones de eco incorporadas:

year=2017 month=12 day=22 flag=true

Una final, esta falla si se pasa un argumento no válido.

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

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "[email protected]" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"

Pros: Lo que hace, lo maneja muy bien. Conserva los 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 prepopulación de variables si no se da ningún argumento correspondiente. (Ver ejemplo detallado).

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


Mezclando argumentos posicionales y basados ​​en banderas.

--param = arg (es igual a delimitado)

Mezclando 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

--param arg (espacio delimitado)

Por lo general, es más claro no mezclar --flag=valuey --flag valueestilos.

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

Esto es un poco peligroso para 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

Ampliando la excelente respuesta de @guneysus, aquí hay un ajuste que le 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 pueden ser reemplazados por espacios en blanco.

Es posible que esta "interpretación difusa" no sea de su agrado, pero si está creando secuencias de comandos 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 "[email protected]"
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

Así es como lo hago en una función para evitar que los getopts de ruptura se ejecuten al mismo tiempo en un 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
...
}

He encontrado la cuestión de escribir un análisis portátil en scripts de manera tan frustrante que he escrito Argbash , un generador de código FOSS que puede generar el código de análisis de argumentos para su script, además de que tiene algunas características Argbash :

https://argbash.io


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

Debe decidir antes de usar si = se 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

Te 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, le devuelve variables fáciles de usar, sobre las que podría construir más lógica.
  4. La cantidad de guiones antes de los parámetros no importa ( --all es igual a - all=all es igual a all=all )

El siguiente script es una demostración de trabajo copiar y pegar. Vea la función show_use para entender cómo usar parse_params .

Limitaciones:

  1. No admite parámetros delimitados por espacios ( -d 1 )
  2. Los nombres de --any-param perderán guiones, por lo que --any-param y -anyparam son equivalentes
  3. eval $(parse_params "[email protected]") 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 "[email protected]")
    # --
    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

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

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

Una prueba del hecho de que getopt está muy desactualizado es que la página del manual de getopt(1) aún menciona "$*" lugar de "[email protected]" , que se agregó a Bourne Shell en 1986 junto con el getopts(1) incorporado. Para poder lidiar con argumentos con espacios en el interior.

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

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

getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"

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

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

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 comando man getopt (1) para verificar $ $ desactualizados:

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


de: digitalpeer.com con pequeñas modificaciones

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

#!/bin/bash
for i in "[email protected]"
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/[^=]*=//'` `echo "$i" | sed 's/[^=]*=//'` que llama a dos subprocesos innecesarios.


getopts funciona 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=("[email protected]")
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

getopt() / getopts() es una buena opción. Robado desde here :

El uso simple 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 se permitirá cualquiera de -a, -b, -c o -d, pero que -c es seguido por un argumento (el "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
-->--

Comenzamos con dos argumentos, y "getopt" rompe las opciones y pone cada uno en su propio argumento. También añadió "-".


No hay respuesta a las menciones mejoradas getopt . Y la respuesta más votada es engañosa: ignora las opciones cortas del estilo -⁠vfd (solicitadas por el OP), las opciones después de los argumentos posicionales (también solicitadas por el OP) e ignora los errores de análisis. En lugar:

  • Use getopt mejorado de util-linux o anteriormente GNU glibc . 1
  • Funciona con getopt_long() la función C de GNU glibc.
  • Tiene todas las características distintivas útiles (las otras no las tienen):
    • Maneja espacios, citando caracteres e incluso binarios en los argumentos 2.
    • puede manejar las opciones al final: script.sh -o outFile file1 file2 -v salida script.sh -o outFile file1 file2 -v
    • permite opciones de estilo largo: script.sh --outfile=fileOut --infile fileIn
  • Ya es tan viejo 3 que a ningún sistema GNU le falta esto (por ejemplo, cualquier Linux lo tiene).
  • Puede probar su existencia con: getopt --test → return value 4.
  • Otros getopt o shell- 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

todo regreso

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

con el 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   -- "[email protected]"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]")
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 "sistemas bash", incluido Cygwin; en OS X intente con brew install gnu-getopt o sudo port install getopt
2 las convenciones de exec() POSIX no tienen una manera confiable de pasar NULL binarios en los argumentos de la línea de comando; esos bytes terminan prematuramente el argumento
3 primera versión lanzada en 1997 o antes (solo la rastreé hasta 1997)


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 conserva el orden sin las opciones

#!/bin/bash

echo [email protected]

PARAMS=()
SOFT=0
SKIP=()
for i in "[email protected]"
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[@]}

Saldrá 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

He escrito 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 "[email protected]"

# 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:"")

disfrutar :)


También puede ser útil saberlo, puede establecer un valor y, si alguien proporciona una entrada, anule el valor predeterminado con ese valor.

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

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

Este ejemplo muestra cómo usar getopty evaly HEREDOCy shiftpara manejar parámetros cortos y largos con y sin el valor requerido que sigue. También la declaración de cambio / caso 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" -- "[email protected]")
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 anterior son las siguientes:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
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, hasta el punto, legible, y maneja casi todo (IMHO).

Espero que ayude a alguien.


Supongamos que creamos un script de shell llamado test_args.shcomo sigue

    #!/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}"

Después ejecutamos el siguiente comando:

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][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

La salida sería:

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

Utilice el módulo "argumentos" de bash-modules

Ejemplo:

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




arguments