linux,

Arguments i modificadors en shell-scripts

Fèlix Fèlix Segueix 01/04/2021 · 10 minuts de lectura
Arguments i modificadors en shell-scripts
Compartir amb...

Recull de bones pràctiques en la construcció de shell-scripts amb uns exemples d’ús on es volen arguments, modificadors amb i sense arguments, així com variants de modificadors amb nom llarg i nom curt. Es tracta d’un punt de partida en la construcció de shell scripts de qualitat.

Requeriments

Un bon shell-script, o si més no aquell que incorpori unes quantes variacions del seu objectiu original, haurà de presentar diversos switch (modificadors).

Alguns d’aquests modificadors potser necessiten un valor o dada que els acompanyi (número de línies, color, longitud, ruta d’arxiu, etc.).

Fins i tot voldrem que els modificadors presentin diversos noms alternatius. Normalment una variant curta per als més comuns o utilitzats. Per exemple, si hem de definir un modificador de color, potser triarem el nom llarg --color, i per al cas curt una simple -c ja ens bastaria.

El shell-script potser també rebrà arguments convencionals, que no venen donats o emmarcats en un modificador, sinó que seran posicionals. Per exemple, si el programa sempre rep el nom d’un arxiu com a únic argument.

Cas bàsic -h, --help

Evidentment la forma d’implementar aquest cas és simple, podríem mirar el valor del primer argument i comparar-lo manualment amb les cadenes corresponents a aquests modificadors. Si hi són, mostrem l’ajuda. Si no hi són, el programa no els ha rebut i podem executar la seva funció principal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
function showHelp() {
  PROGNAME=`basename $0`
  echo "Ús: $PROGNAME [-h|--help]"
  # Més echo per a l'ajuda...
}

if [ "$1" == "-h" -o "$1" == "--help" ]
then
  showHelp
  exit 0
fi

# Resta del programa...

Aquesta implementació la deixaria només per al cas en què:

  1. El programa NO rep cap argument d’entrada (executa un procés fix, com un backup o una neteja d’arxius)
  2. Només volem donar un ÚNIC modificador d’ajuda que ens “recordi” què fa el programa i què esperar d’ell

I és que en el cas de diversos modificadors amb o sense dada, caldria programar un analitzador que comprovi els següents casos:

  • Sense modificador: programa
  • Amb modificador : programa --color red
  • Amb modificador curt : programa -c red
  • Amb el modificador mal escrit : programa --color o programa -c
  • Amb el modificador d’ajuda: programa -h o programa --help
  • Amb el modificador i un argument posicional: programa -c red "Hello"
  • Només l’argument posicional: programa "Hello"

Com es pot veure les opcions es disparen, sense pensar en el cas que s’hagi intentat escriure un modificador inexistent, que existeixin diversos modificadors que es puguin donar en qualsevol ordre, etc… I tot només mirant $1, $2, … Una barbaritat!

getopts al rescat!

La utilitat getopts normalment la trobarem com a shell built-in, és a dir, com a comanda integrada dins de la mateixa shell i no com a programa extern. Algunes implementacions de shell poden requerir que s’instal·li separadament, però ve integrada en la majoria de shells modernes.

Aquesta utilitat ens permet analitzar fàcilment els modificadors, quan aquests són d’un sol caràcter (modificadors curts), i normalment la trobem en blocs de codi com aquest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
function showHelp() {
    PROGNAME=`basename $0`
    echo "Ús: $PROGNAME [-h] [-v] [-c color]"
    # ...
}

COLOR=transparent
VERBOSITY=0
while getopts "hvc:" opt
do
  case $opt in
    h)	showHelp; exit;;
    v)  VERBOSITY=1;;
    c)	COLOR="$OPTARG";;
    ?)	showHelp;
        exit 2;;
  esac
done

echo "Verbosity level: $VERBOSITY"
echo "Requested color: $COLOR"
shift $(($OPTIND - 1))
echo "Remaining arguments are: $*"

Aquest programa tal i com està ara mateix ens permetrà executar-lo de les següents maneres:

programa -h
programa -v
programa -c red
programa -c red image.png
programa -v -c red image.png
programa -vc red image.png

És a dir, que ens acceptarà qualsevol de les combinacions habituals a què estem acostumats en programes i scripts de Linux, amb opcions curtes que podem escriure juntes (-vc red és equivalent a -v -c red). El programa mostrat també accepta arguments addicionals, que seran posicionals ($1, $2, …) i que seran la resta que quedi després de treure les opcions.

Però com és que funciona així? Què són OPTARG i OPTIND? Què representa la línia del while? Analitzem com funciona:

  • getopts permet l’execució while tal i com es mostra, rebent dos arguments: una cadena que especifica les opcions curtes que volem analitzar i el nom d’una variable de shell que és on es guardarà l’opció “descoberta”.
  • En cada iteració del while la variable opt (el nom es podria canviar si es desitgés) valdrà un dels modificadors passats per l’usuari al programa, per això hem fet un case per analitzar quin dels modificadors és i fer l’acció que correspongui.
  • El primer argument de getopts conté els modificadors permesos, tots seguits. Com que volem els modificadors -h, -v i -c, els indiquem seguits. El signe de : indica que el modificador anterior (en aquest cas la c) rebrà un argument. Això vol dir que podríem haver escrit la cadena de modificadors com s’ha fet (hvc:) o en un altre ordre (c:hv, hc:v o vc:h, per exemple). L’important és que el dos punts sempre segueixi la c, per indicar que aquest modificador requereix d’un argument.
  • Quan un modificador té argument (indicat pel signe : en la cadena de modificadors de getopts), disposarem d’aquest argument en la variable d’entorn OPTARGS. Per això veieu que s’extreu el color mitjançant aquesta variable. getopts s’encarrega de gestionar-la.
  • L’altra variable que getopts gestiona per nosaltres és OPTIND, que és un índex que apunta al següent argument passat al programa que s’haurà d’analitzar. Així podem saber en quin punt exacte de la llista d’arguments està analitzant en tot moment.
  • Usem OPTIND per “saltar” fins a ell i eliminar així tots els modificadors i valors indicats i quedar-nos amb la resta d’arguments del programa, que correspondran als arguments posicionals clàssics (el nom de l’arxiu a processar, el directori on fer l’operació, la cadena de text a tractar, etc.).

I què passa amb les opcions llargues?

La utilitat getopts malauradament no és capaç de gestionar modificadors llargs. Però podem fer un “truc” bastant interessant, tot i que ens fa més llarga l’escriptura del shell script. Ho veurem amb el mateix exemple ara ampliat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
function showHelp() {
    PROGNAME=`basename $0`
    echo "Ús: $PROGNAME [-h|--help] [-v|--verbose] [-c|--color color]"
    # ...
}

COLOR=transparent
VERBOSITY=0
while getopts "hvc:-:" opt
do
  case $opt in
    -)  case "${OPTARG}" in
          help)     showHelp; exit;;
          verbose)  VERBOSITY=1;;
          color)      
            COLOR="${!OPTIND}"
            OPTIND=$(( $OPTIND + 1 ));;
          *)
            echo "Unknown option '--${OPTARG}'" >&2
            exit -1;;
        esac;;
    h)	showHelp; exit;;
    v)  VERBOSITY=1;;
    c)	COLOR="$OPTARG";;
    *)	echo "Unknown option '-$opt'" >&2
        exit -1;;
  esac
done

echo "Verbosity level: $VERBOSITY"
echo "Requested color: $COLOR"
shift $(($OPTIND - 1))
echo "Remaining arguments are: $*"

Ara el programa podrà executar-se amb totes les formes i variants (combinades o no):

programa --help
programa --verbose --color blue image.png
programa -v --color blue image.png

Dependrà de com vulguem que el programa estigui definit que farem una combinació o altra dels case tant per a la part de modificadors “llargs” com a la part de modificadors “curts”. Dit d’una altra manera: ningú ens obliga a implementar els modificadors en els dos modes. Podem crear modificadors només curts, modificadors només llargs i modificadors que tinguin les dues variants. Tot dependrà de nosaltres.

Observi’s que ara s’utilitza OPTARG per a analitzar les opcions llargues, perquè quan escrivim --color en realitat ho processarà com un modificador curt -- que ha rebut com a argument la cadena color. De la mateixa manera, usem OPTIND per treure la dada subministrada després a través de la indexació ${!OPTIND} com a argument del modificador, i incrementem aquest comptador per saltar l’argument ja capturat.

Veiem doncs una manera bastant senzilla de crear shell scripts que compleixin amb un mínim estàndard de qualitat en la forma en què donem opcions i variacions a l’usuari, apropant els nostres scripts una mica al que s’esperaria d’un programa de shell mínimament professional.

Ara ja sabeu com fer correctament l’estructuració d’un shell script de qualitat… però encara us quedarà donar sentit al programa i fer la funcionalitat original que teniu pensada!


Fèlix
Written by Fèlix Segueix
Sóc el Fèlix! 42!