getopt, getopts o análisis manual: ¿qué debo usar cuando quiero admitir opciones cortas y largas?


32

Actualmente estoy escribiendo un script Bash que tiene los siguientes requisitos:

  • debería ejecutarse en una amplia variedad de plataformas Unix / Linux
  • debería admitir opciones cortas y largas (GNU)

Sé que getoptssería la forma preferida en términos de portabilidad, pero AFAIK no admite opciones largas.

getoptadmite opciones largas, pero BashGuide recomienda enfáticamente que no lo haga :

Nunca use getopt (1). getopt no puede manejar cadenas de argumentos vacías o argumentos con espacios en blanco incrustados. Por favor, olvide que alguna vez existió.

Por lo tanto, todavía existe la opción de análisis manual. Esto es propenso a errores, produce bastante código repetitivo y necesito manejar los errores por mí mismo (supongo getopt(s)que los manejo de errores por sí mismos).

Entonces, ¿cuál sería la opción preferida en este caso?


Respuestas:


9

Si tiene que ser portátil para una gama de Unices, debería atenerse a POSIX sh. Y AFAIU allí no tiene más remedio que manejar el argumento a mano.


25

getoptvs getoptsparece ser un problema religioso. En cuanto a los argumentos getopten contra en las preguntas frecuentes de Bash :

  • " getoptno se pueden manejar cadenas de argumentos vacías" parece referirse a un problema conocido con argumentos opcionales , que parece que getoptsno es compatible en absoluto (al menos al leer help getoptsBash 4.2.24). De man getopt:

    getopt (3) puede analizar opciones largas con argumentos opcionales que reciben un argumento opcional vacío (pero no puede hacer esto para opciones cortas). Este getopt (1) trata los argumentos opcionales que están vacíos como si no estuvieran presentes.

No sé de dónde getoptviene el " no puede manejar [...] argumentos con espacios en blanco incrustados", pero probémoslo:

  • test.sh:

    #!/usr/bin/env bash
    set -o errexit -o noclobber -o nounset -o pipefail
    params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "$@")"
    eval set -- "$params"
    
    while true
    do
        case "$1" in
            -a|--alpha)
                echo alpha
                shift
                ;;
            -b|--bravo)
                echo "bravo=$2"
                shift 2
                ;;
            -c|--charlie)
                echo charlie
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Not implemented: $1" >&2
                exit 1
                ;;
        esac
    done
  • correr:

    $ ./test.sh -
    $ ./test.sh -acb '   whitespace   FTW   '
    alpha
    charlie
    bravo=   whitespace   FTW   
    $ ./test.sh -ab '' -c
    alpha
    bravo=
    charlie
    $ ./test.sh --alpha --bravo '   whitespace   FTW   ' --charlie
    alpha
    bravo=   whitespace   FTW   
    charlie

Me parece un jaque y un compañero, pero estoy seguro de que alguien me mostrará cómo entendí mal la oración. Por supuesto, el problema de la portabilidad sigue en pie; tendrá que decidir cuánto tiempo vale la pena invertir en plataformas con un Bash antiguo o sin Bash disponible. Mi propio consejo es utilizar las pautas de YAGNI y KISS : desarrolle solo para aquellas plataformas específicas que sabe que se utilizarán. La portabilidad del código Shell generalmente llega al 100% a medida que el tiempo de desarrollo llega al infinito.


11
El OP mencionó la necesidad de ser portátil para muchas plataformas Unix, mientras que la getoptcita aquí es específica de Linux. Tenga en cuenta que getoptno forma parte bash, ni siquiera es una utilidad GNU y en Linux se envía con el paquete util-linux.
Stéphane Chazelas

2
La mayoría de las plataformas tienen getopt, solo Linux AFAIK viene con una que admite opciones largas o espacios en blanco en los argumentos. Los otros solo admitirían la sintaxis del Sistema V.
Stéphane Chazelas

77
getoptes un comando tradicional que proviene del Sistema V mucho antes de que se lanzara Linux. getoptnunca fue estandarizado. Ninguno de POSIX, Unix o Linux (LSB) nunca estandarizó el getoptcomando. getoptsse especifica en los tres pero sin soporte para opciones largas.
Stéphane Chazelas

1
Solo para agregar a esta discusión: esto no es lo tradicional getopt. Es el sabor de linux-utils como lo indica @ StéphaneChazelas. Tiene opciones heredadas que deshabilitarán la sintaxis descrita anteriormente, en particular la página de manual dice "GETOPT_COMPATIBLE obliga a getopt a usar el primer formato de llamada como se especifica en la SINOPSIS". Sin embargo, si puede esperar que los sistemas de destino tengan este paquete instalado, este es el camino a seguir, ya que el getopt original es horrible y el getopt de Bash es muy limitado
n.caillou

1
El enlace de OP que dice "Las versiones tradicionales de getopt no pueden manejar cadenas de argumentos vacías o argumentos con espacios en blanco incrustados". y que la versión util-linux de getopt no debe usarse no tiene evidencia y ya no es precisa. Una encuesta rápida (más de 5 años después) muestra que la versión predeterminada de getopt disponible en ArchLinux, SUSE, Ubuntu, RedHat, Fedora y CentOS (así como la mayoría de las variantes derivadas) son compatibles con argumentos y argumentos opcionales con espacios en blanco.
mtalexan

10

Existe este getopts_long escrito como una función de shell POSIX que puede incrustar dentro de su script.

Tenga en cuenta que Linux getopt(from util-linux) funciona correctamente cuando no está en modo tradicional y admite opciones largas, pero probablemente no sea una opción para usted si necesita ser portátil a otros Unices.

Las versiones recientes de ksh93 ( getopts) y zsh ( zparseopts) tienen soporte incorporado para analizar opciones largas que podrían ser una opción para usted, ya que están disponibles para la mayoría de los Unices (aunque a menudo no están instaladas de manera predeterminada).

Otra opción sería usar perly su Getopt::Longmódulo, que debería estar disponible en la mayoría de los Unices hoy en día, ya sea escribiendo todo el script perlo simplemente llamando a perl solo para analizar la opción y alimentar la información extraída al shell. Algo como:

parsed_ops=$(
  perl -MGetopt::Long -le '

    @options = (
      "foo=s", "bar", "neg!"
    );

    Getopt::Long::Configure "bundling";
    $q="'\''";
    GetOptions(@options) or exit 1;
    for (map /(\w+)/, @options) {
      eval "\$o=\$opt_$_";
      $o =~ s/$q/$q\\$q$q/g;
      print "opt_$_=$q$o$q"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...

Vea perldoc Getopt::Longqué puede hacer y en qué se diferencia de otros analizadores de opciones.


2

Cada discusión sobre este asunto resalta la opción de escribir el código de análisis manualmente, solo entonces puede estar seguro de la funcionalidad y portabilidad. Le aconsejo que no escriba código que pueda haber generado y regenerado mediante generadores de código abierto fáciles de usar. Use Argbash , que ha sido diseñado para proporcionar la respuesta definitiva a su problema. Es un generador de código bien documentado disponible como una aplicación de línea de comandos , en línea o como una imagen de Docker .

Aconsejo contra las bibliotecas de bash, algunas de ellas usan getopt(lo que hace que sea bastante inportable) y es difícil agrupar un blob de shell ilegible gigantesco con su script.


0

Puede usarlo getopten sistemas que lo admitan y usar una alternativa para sistemas que no lo admiten.

Por ejemplo, pure-getoptse implementa en Bash puro para ser un reemplazo directo de GNU getopt.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.