Pregunta ¿Cómo analizo los argumentos de línea de comando en Bash?


Diga, 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 forma tal que en cada caso (o una combinación de ambos) $v, $fy $d todo estará configurado para true y $outFile será igual a /fizz/someOtherFile ?


1340
2017-10-10 16:57


origen


Respuestas:


Método n. ° 1: Usar bash sin getopt [s]

Dos formas comunes de pasar los pares clave-valor-par son:

Bash Space-Separated (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 "$@"
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#*=} buscar "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 llamadas dos subprocesos innecesarios.

Usando getopt [s]

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

limitaciones de getopt (1) (más antiguo, relativamente reciente getopt versiones):

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

Más reciente getopt las versiones no tienen estas limitaciones.

Además, la oferta de shell POSIX (y otros) getopts que no tiene estas limitaciones Aquí hay un simplista getopts ejemplo:

#!/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 (-hno --help) sin código adicional.

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


1936
2018-01-07 20:01



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

  • Use mejorado getopt de util-linux o anteriormente glibc GNU.1
  • Funciona con getopt_long() la función C de GNU glibc.
  • Tiene todas características distintivas útiles (las otras no las tienen):
    • maneja espacios, citando caracteres e incluso binarios en argumentos2
    • puede manejar opciones al final: script.sh -o outFile file1 file2 -v
    • permite =estilo largo opciones: script.sh --outfile=fileOut --infile fileIn
  • Ya es muy viejo3 que ningún sistema GNU se está perdiendo esto (por ejemplo, cualquier Linux lo tiene).
  • Puedes probar su existencia con: getopt --test → devolver valor 4.
  • Otro 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

todo regresa

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 el getopt mejorado está disponible en la mayoría de los "sistemas bash", incluido Cygwin; en OS X probar brew instalar gnu-getopt
2 el POSIX exec() las convenciones no tienen una manera confiable de pasar NULL binario en argumentos de línea de comando; esos bytes terminan prematuramente el argumento
3 primera versión lanzada en 1997 o antes (solo la rastreé hasta 1997)


328
2018-04-20 17:47



de : digitalpeer.com con modificaciones menores

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#*=} buscar "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 llamadas dos subprocesos innecesarios.


103
2017-11-13 10:31



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

El uso simple de "getopt" se muestra en este mini guión:

#!/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 -a,   -b, -c o -d serán permitidos, pero esa -c es seguida 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
-->--

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


101
2017-10-10 17:03



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

  • maneja -n arg y --name=arg
  • permite argumentos al final
  • muestra errores correctos 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

59
2017-07-15 23:43



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

39
2017-11-20 12:28



Tengo aproximadamente 4 años de retraso en esta pregunta, pero quiero devolver algo. Utilicé las respuestas anteriores como punto de partida para ordenar mi antiguo análisis de parámtero adhoc. Luego refactoreé el siguiente código de plantilla. Maneja params largos y cortos, usando = o argumentos separados por espacios, así como múltiples params cortos agrupados. Finalmente vuelve a insertar cualquier argumento que no sea param en las variables $ 1, $ 2 .. 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

36
2017-07-01 01:20



Mi respuesta se basa en gran medida en la respuesta de Bruno Bronosky, pero de alguna manera aplasté sus dos implementaciones de bash puro 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

tanto como:

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

y ambos deberían tener el mismo resultado final.

PROS:

  • Permite tanto -arg = value como -arg value

  • Funciona con cualquier nombre arg que puedas usar en bash

    • Significado -a o -arg o --arg o -a-r-g o lo que sea
  • Bash puro. No es necesario aprender / usar getopt o getopts

CONTRAS:

  • No se puede combinar argumentos

    • Es decir, no -abc. Debes hacer -a -b -c

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


21
2017-09-08 18:59



He encontrado el asunto para escribir un análisis portátil en secuencias de comandos 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 interesantes:

https://argbash.io


21
2017-07-10 22:40