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

Resuelto Redwood asked hace 16 años • 43 respuestas

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 manera que en cada caso (o alguna combinación de los dos) $v, $fy $dse establezcan en truey $outFilesean iguales a /fizz/someOtherFile?

Redwood avatar Oct 10 '08 23:10 Redwood
Aceptado

Bash separado por espacios (por ejemplo, --option argument)

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
  case $1 in
    -e|--extension)
      EXTENSION="$2"
      shift # past argument
      shift # past value
      ;;
    -s|--searchpath)
      SEARCHPATH="$2"
      shift # past argument
      shift # past value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument
      ;;
    -*|--*)
      echo "Unknown option $1"
      exit 1
      ;;
    *)
      POSITIONAL_ARGS+=("$1") # save positional arg
      shift # past argument
      ;;
  esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
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
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc /etc/hosts
Resultado de copiar y pegar el bloque de arriba
FILE EXTENSION  = conf
SEARCH PATH     = /etc
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Uso
demo-space-separated.sh -e conf -s /etc /etc/hosts

Bash separados por iguales (por ejemplo, --option=argument)

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/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
      ;;
    --default)
      DEFAULT=YES
      shift # past argument with no value
      ;;
    -*|--*)
      echo "Unknown option $i"
      exit 1
      ;;
    *)
      ;;
  esac
done

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
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
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
Resultado de copiar y pegar el bloque de arriba
FILE EXTENSION  = conf
SEARCH PATH     = /etc
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com
Uso
demo-equals-separated.sh -e=conf -s=/etc /etc/hosts

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.


Usando bash con getopt[s]

Limitaciones de getopt(1) (versiones más antiguas y relativamente recientes getopt):

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

Las versiones más recientes getoptno tienen estas limitaciones. Para obtener más información, consulte estos documentos .


opciones POSIX

Además, el shell POSIX y otras ofertas getoptsno tienen estas limitaciones. He incluido un getoptsejemplo simplista.

cat >/tmp/demo-getopts.sh <<'EOF'
#!/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: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar
Resultado de copiar y pegar el bloque de arriba
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Uso
demo-getopts.sh -vf /etc/hosts foo bar

Las ventajas de getoptsson:

  1. Es más portátil y funcionará en otros shells como dash.
  2. Puede manejar múltiples opciones únicas, como -vf filenameen la forma típica de Unix, de forma automática.

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

Hay un tutorial de getopts que explica qué significan todas las sintaxis y variables. En bash, también hay help getopts, que puede ser informativo.

Bruno Bronosky avatar Jan 07 '2013 20:01 Bruno Bronosky

Ninguna respuesta muestra getopt mejorado . Y la respuesta más votada es engañosa: ignora -⁠vfdlas opciones cortas de estilo (solicitadas por el OP) u opciones después de argumentos posicionales (también solicitadas por el OP); e ignora los errores de análisis. En cambio:

  • Uso mejorado getoptde util-linux o anteriormente GNU glibc . 1
  • Funciona con getopt_long()la función C de GNU glibc.
  • ninguna otra solución en esta página puede hacer todo esto :
    • maneja espacios, cita caracteres e incluso binarios en los argumentos 2 (los no mejorados getoptno pueden hacer esto)
    • puede manejar opciones al final: script.sh -o outFile file1 file2 -v( getoptsno hace esto)
    • permite =opciones largas de estilo: script.sh --outfile=fileOut --infile fileIn(permitir ambas es largo si se analiza automáticamente)
    • permite opciones cortas combinadas, por ejemplo -vfd(trabajo real si se analiza automáticamente)
    • permite tocar argumentos de opciones, por ejemplo -oOutfileo-vfdoOutfile
  • Ya es tan antiguo que viene preinstalado en cualquier sistema GNU (es decir, Linux principalmente); ver nota al pie 1
  • Puede probar su existencia con: getopt --test→ valor de retorno 4.
  • Otros getopto integrados en shell getoptsson 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 regresan

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

con lo siguientemyscript

#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# ${PIPESTATUS[0]} with a simple $?, but I prefer safety.
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

# option --output/-o requires 1 argument
LONGOPTS=debug,force,output:,verbose
OPTIONS=dfo:v

# -regarding ! and PIPESTATUS see above
# -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 “sistemas bash”, incluido Cygwin; en OS X, intentebrew install gnu-getopt, brew install util-linuxosudo port install getopt
2, las convenciones POSIXexec()no tienen una forma confiable de pasar NULL binario en los argumentos de la línea de comando; esos bytes terminan prematuramente la primera versión del argumento
3 lanzada en 1997 o antes (solo la rastreé hasta 1997)

Robert Siemer avatar Apr 20 '2015 17:04 Robert Siemer

desplegar.sh

#!/bin/bash

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

echo "Where to deploy: $target"
echo "Should uglify  : $uglify"

Uso:

./deploy.sh -t dev -u

# OR:

./deploy.sh --target dev --uglify
Inanc Gumus avatar Nov 20 '2015 12:11 Inanc Gumus

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#*=}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.

guneysus avatar Nov 13 '2012 10:11 guneysus
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

Esta solución:

  • manijas -n argy--name=arg
  • permite argumentos al final
  • muestra errores sensatos si algo está mal escrito
  • compatible, no usa bashismos
  • legible, no requiere mantener el estado en un bucle
bronson avatar Jul 15 '2015 23:07 bronson