Shell Scripts

Rafa Morales 17 Octubre 2013
25min
0
Linux

El shell de Linux es un intéprete de comandos que permite al administrador ejecutar determinadas tareas (leer más). Pero el shell no es únicamente eso, ya que los intérpretes de comandos son un auténtico lenguaje de programación que incorpora sentencias de control de flujo, sentencias de asignación, funciones, etc.

Los shell scripts o guiones, o simplemente scripts, son interpretados, es decir, no necesitan ser compilados, se ejecutan línea a línea. Son solamente un fichero con los comandos que necesitemos escritos en ASCII.

 

Cómo crear y ejecutar un script

Para crear un script solamente necesitamos un editor de texto, y comenzarlo siempre con una línea que indique el shell que se va a utilizar para ejecutar esos comandos:

#!/bin/bash

Una vez terminado editado, tendremos que otorgarle al fichero permisos de ejecución a los usuarios que puedan ejecutarlo:

chmod 755 miscript

Para ejecutar un script tenemos dos posibilidades. La primera es utilizando el intérprete de comandos (en este caso no hubiera sido necesario otorgarle permisos de ejecución):

sh miscript

La segunda es ejecutarlo directamente con su nombre y la ruta de donde se encuentra:

./miscript

Un ejemplo sencillo de script podría ser el siguiente:

#!/bin/bash
clear
date

 

Cómo depurar un script

Depurar un script consiste en ejecutarlo paso a paso para comprobar su funcionamiento o encontrar posibles errores.

Una buena idea para depurar los programas es la opción -x en la primera línea. Como consecuencia, durante la ejecución se va mostrando cada línea del script después de sustituir las variables por su valor, pero antes de ejecutarla.

#!/bin/bash -x

Otra posibilidad es utilizar la opción -v que muestra cada línea como aparece en el script (tal como está en el fichero), antes de ejecutarla:

#!/bin/bash -v

Otra opción es llamar al programa usando el ejecutable bash. Por ejemplo, si nuestro programa se llama prac1, se podría invocar como:

$ bash -x prac1
$ bash -v prac1

Ambas opciones pueden ser utilizadas de forma conjunta:

#!/bin/bash -xv

ó

$ bash -xv prac1

 

Recomendaciones a la hora de editar el código de un script

Una práctica ordenada permite una verificación y comprensión más cómoda y rápida, para realizar las modificaciones de forma más segura y ayudar al usuario a ejecutar el programa correctamente. Pare ello, seguir las siguientes recomendaciones:

  • El código debe ser fácilmente legible, incluyendo espacios y sangrías que separen claramente los bloques de código.
  • Deben añadirse comentarios claros sobre el funcionamiento general del programa principal y de las funciones, que contengan: autor, descripción, modo de uso del programa, versión y fechas de modificaciones.
  • Incluir comentarios para los bloques o mandatos importantes, que requieran cierta aclaración.
  • Agregar comentarios y ayudas sobre la ejecución del programa.
  • Depurar el código para evitar errores, procesando correctamente los parámetros de ejecución.
  • No desarrollar un código excesivamente enrevesado, ni complicado de leer, aunque esto haga ahorrar líneas de programa.
  • Utilizar funciones y las estructuras de programación más adecuadas para evitar repetir código reiterativo.
  • Los nombres de variables, funciones y programas deben ser descriptivos, pero no ha de confundirse con otras de ellas, ni con los mandatos internos o externos; no deben ser ni muy largos ni muy cortos.
  • Todos los nombres de funciones y de programas suelen escribirse en letras minúsculas, mientras que las variables acostumbran a definirse en mayúsculas.

 

Caracteres especiales

Comentarios #

Con el carácter # insertaremos un comentario en el script, todo lo que venga a continuación en la misma línea sera ignorado por el intérprete.

# Esto es un comentario

 

Separación de comandos ;

El intérprete ejecutará como un sólo comando toda la línea hasta encontrar un salto de línea. Si queremos escribir más de un comando en una misma línea tenemos que separarlos con un punto y coma.

echo “Un comando”
echo “Otro comando”
echo “Un comando”; echo “Otro comando”

 

Cadenas de texto " " ' '

Lo que incluyamos entre comillas dobles o comillas simples será interpretado literalmente como una cadena de texto, y será presentado tal cual.

echo “Texto por pantalla”
echo ‘Otro texto por pantalla’

 

Carácter de escape \

Una barra inclinada inversa (o invertida) no entrecomillada (\) es el carácter de escape, el cual preserva sin modificar el valor literal del siguiente carácter que lo acompaña.

echo " \" "

En el ejemplo anterior se imprimiría la comilla doble del interior, y no se confundiría con la comilla doble de cierre de cadena.

Además, según el carácter que le venga acompañado, puede significar alguno de los siguientes caracteres especiales:

  • \a: alerta (campana)
  • \b: espacio-atrás
  • \e: carácter de escape (ESC)
  • \f: nueva página
  • \n: nueva línea
  • \r: retorno de carro
  • \t: tabulación horizontal
  • \v: tabulación vertical
  • \\: barra invertida
  • \xnnn: carácter cuyo código es el valor hexadecimal nnn

 

Evaluación del contenido ` `

Lo que incluyamos entre comillas invertidas ` ` (tilde invertida) será evaluado en primer lugar, y luego el comando en el que se encuentra pero ya con el resultado del comando ejecutado en primer lugar.

echo `date`

 

Variables y arrays

Variables locales

Las variables locales se utilizan para poder guardar información y a partir de ella poder tomar decisiones o realizar operaciones. Las variables locales no pueden tener el nombre de ninguna palabra reservada (p.e. echo). No es necesario declarar previamente las variables, se pueden comenzar a utilizar en cualquier parte del script.

Para asignar un valor a una variable se utiliza directamente el nombre:

NUMERO=5

Para utilizar el valor de una variable se antepone el carácter $ al nombre de la variable:

echo $NUMERO

 

Variables de entorno

Las variables de entorno definidas en el sistema estarán a nuestra disposición en el script y podemos hacer uso de ellas para nuestros intereses (leer más).

echo $HOME

 

Variables de múltiples valores (Arrays)

El shell permite que se trabaje con arrays o matrices unidimensionales. Un array es una colección de elementos del mismo tipo, dotados de un nombre, y que se almacenan en posiciones contiguas de memoria. El primer subíndice del primer element del array es 0, y si no se utiliza subíndice, se considera también que se está referenciando a dicho elemento. No hay un tamaño máximo para un array, y la asignación de valores se puede hacer de forma alterna.

La sintaxis para crear e inicializar un array es la siguiente:

nombre_array=(val1 val2 val3 ...)

Para asignar un valor a un elemento concreto del array:

nombre_array[x]=valor

Para acceder a un elemento concreto del array se utiliza la siguiente sintaxis:

${nombre_array[x]}

Se pueden utilizar variables para recorrer los índices del array:

id=3
${nombre_array[$id]}

Para acceder a todos los elementos del array como si fueran todos una única palabra:

${nombre_array[*]} 

Para acceder a todos los elementos del array, cada uno como si fuera una palabra independiente:

${nombre_array[@]}

Para conocer el tamaño en bytes de un elemento dado del array:

${#nombre_array[x]}

Si queremos saber el número total de elementos del array:

${#nombre_array[*]}
${#nombre_array[@]}

 

Reglas de evaluación de variables

Nos permite sustituir el contenido de la variable siguiendo una amplia variedad de reglas.

Regla

Descripción

$var

Si está definida se devuelve su contenido, si no no se devuelve nada

${var}

Igual que la anterior, pero evita errores cuando se concatena con otras cadenas o variables

${var-value}

Si está definida se devuelve su contenido, si no se devuelve "value"

${var=value}

Si está definida se devuelve su contenido, si no se asigna "value" a la variable

${var?message}

Si está definida se devuelve su contenido, si no se envía un mensaje de error con el texto "message"

${var+value}

Si está definida se devuelve "value", si no se devuelve el contenido de la variable

${var:inic:long}

Devuelve la subcadena del contenido de la variable desde la posición "inic" y hasta completa "long" caracteres (si "long" no se especifica se devolverá hasta el final de la misma)
${!pref*} Devuelve los nombres de las variables que empiezan por "pref"
${#var} Devuelve el tamaño en caracteres del contenido de la variable

${var#patron}

${var##patron}

Se elimina del contenido de la variable la mínima (o la máxima) comparación del patrón, comenzando por el principio.

${var%patron}

${var%%patron}

Se elimina del contenido de la variable la mínima (o la máxima) comparación del patrón, comenzando por el final.

${var/patron/cadena}

${var//patron/cadena}

Se reemplaza en el contenido de la variable los caracteres que corresponden con el "patrón" por el valor de "cadena" (o todas las comparaciones)

 

Algunos ejemplos de uso serían:

#Si no existe el primer parámetro, muestra un mensaje de error
${1?"Error: No existe el parámetro}

 

 

Parámetros de entrada y Variables internas

Al ejecutar un script podemos pasarle valores que luego podremos utilizar a nuestro antojo durante el desarrollo del mismo. Esto es lo que se conoce como parámetros de entrada del script.

Por ejemplo, podríamos ejecutar un script indicándole como primer parámetro un mes y como segundo un año.

./miscript mayo 2012

Los valores de estos parámetros lo utilizaremos en el script como variables, y siempre tendrán el mismo nombre, según la posición en la que se encuentren $1, $2... $9.

En nuestro ejemplo $1 tendrá el valor “mayo” y $2 tendrá “2012”.

Pero además tenemos otras variables que nos ayudarán a trabajar con los parámetros de entrada y otros aspectos del script:

Variable

Descripción

$1 $2 $3 ... $9

Valores de los parámetros de entrada

$0

Ruta y nombre del script

$#

Número de parámetros de entrada

$*

Cadena con todos los parámetros separados por espacio en blanco

$@

Cadena con todos los parámetros separados por palabras

$$

Identificador del proceso (PID)

&?

Código de retorno del último comando (0=correcto, >0=error)

$!

Último identificador de proceso ejecutado en segundo plano

$_

Valor del último argumento del comando ejecutado previamente

 

La orden shift mueve toda la lista de parámetros de entrada una posición a la izquierda, esto hace que el contenido del parámetro $1 desaparezca, y sea reemplazado por el contenido de $2, que $2 sea reemplazado por $3, y así sucesivamente.

shift

 

Si ejecutamos comandos encadenados mediante tuberías (pipe), tenemos a nuestra disposición un array con el código de salida de cada uno de los comandos por separado.

Variable

Descripción

${PIPESTATUS[0]}

Código de salida del primer comando encadenado

${PIPESTATUS[1]}

Código de salida del segundo comando encadenado
...  
${PIPESTATUS[*]} Código de salida de todos los comandos encadenados

 

Un ejemplo sería el siguiente, en el que queremos comprobar el código de salida del segundo comando:

ls /dev | grep a | sort
echo ${PIPESTATUS[1]}

Entrada y salida de datos

Para imprimir un dato por pantalla desde el script utilizamos el comando echo, en el que podemos concatenar todas las cadenas y variables que deseemos.

echo “Valor del primer parámetro: “$1"(fin)"

Con -n no se hace el salto de línea por defecto.

echo -n `date`

Para pedir al usuario un dato desde el teclado utilizamos el comando read. El script se detendrá hasta que el usuario en la terminal introduzca un valor con el teclado. Este valor se introducirá en la variable indicada a continuación del comando (sin el carácter $), y podremos trabajar con él como con cualquier otra variable.

read numero1
read numero1,numero2,numero3

Con -p incluimos una cadena de caracteres al pedirle el dato al usuario:

read -p "Introduce un número" numero1

Nos puede servir también como un pause si no incluimos ninguna variable:

read -p "Pulsa una tecla para continuar..."

 

Expresiones

Expresiones aritméticas

El shell permite que se evalúen expresiones aritméticas, bajo ciertas circunstancias. La evaluación se hace con enteros largos sin comprobación de desbordamiento, aunque la división por 0 se atrapa y se señala como un error. La lista siguiente de operadores se agrupa en niveles de operadores de igual precedencia, se listan en orden de precedencia decreciente.

-, +  Menos y más unarios
~ Negación lógica y de bits
** Exponenciación
*, /,% Multiplicación, división, resto
+, - Adición, sustracción
<<, >> Desplazamientos de bits a izquierda y derecha
<=, >= , <, > Comparación
==, != Igualdad y desigualdad
& Y de bits (AND)
^ O exclusivo de bits (XOR)
| O inclusivo de bits (OR)
&& Y lógico (AND)
|| O lógico (OR)
expre?expre:expre Evaluación condicional
=, +=, -=, *=, /=,%=, &=, ^=, |= <<=, >>=  Asignación: simple, después de la suma, de la resta, de la multiplicación, de la división, del resto,del AND bit a bit, del XOR bit a bit, del OR bit a bit, del desplazamiento a la izquierda bit a bit y del desplazamiento a la derecha bit a bit.

Los operadores se evalúan en orden de precedencia. Las subexpresiones entre paréntesis se evalúan primero y pueden sustituir a las reglas de precedencia anteriores.

Existen tres maneras de realizar operaciones aritméticas:

 

1. Con let lista_expresiones, como se ha dicho anteriormente, se pueden evaluar las expresiones aritméticas dadas como argumentos. Es interesante destacar que esta orden no es estándar, sino que es específica del bash. A continuación se muestra un ejemplo de su uso:

let a=6+7
echo El resultado de la suma es $a

$ El resultado de la suma es: 13

let b=7%5
echo El resto de la división es: $b

$El resto de la división es: 2

let c=2#101\|2#10
echo El valor de c es $c

$ El valor de c es 7

 

2. La orden expr sirve para evaluar expresiones aritméticas. Puede incluir los siguientes operadores: \(, \), \*, \\, \+, \-, donde el carácter ’\’ se introduce para quitar el significado especial que pueda tener el carácter siguiente. Por ejemplo:

expr 10 \* \( 5 \+ 2 \)

i=‘expr $i - 1‘ #restará 1 a la variable i

 

3. Mediante $(( expresión )) también se pueden evaluar expresiones. Varios ejemplos de su uso serían:

echo El resultado de la suma es $((6+7))
$ El resultado de la suma es: 13

echo El resto de la división es: $((7%5))
$ El resto de la división es: 2

echo El valor es $((2#101|2#10))
$ El valor de c es 7

 

Expresiones condicionales

Las expresiones condicionales nos permiten evaluar si una expresión es verdadera o falsa. No sólo operan sobre los valores de las variables, también permiten conocer, por ejemplo, las propiedades de un fichero.

Para ello, tenemos dos sintaxis diferentes:

test expresión
[ expresión ]

¡OJO! Los espacios en blanco entre la expresión y los corchetes son obligatorios.

 

Para números:

-eq Igual a
-ne Distinto de
-lt Menor que
-le Menor o igual que
-gt  Mayor que
-ge Mayor o igual que

Es importante destacar que en las comparaciones con números si utilizamos una variable y no está definida, saldrá un mensaje de error.

[ $e -eq 1 ]
[ ${e=0} -eq 1 ]

 

Para caracteres alfabéticos o cadenas:

-z cadena Verdad si la longitud de cadena es cero
-n cadena Verdad si la longitud de cadena no es cero
cadena1 == cadena2 Verdad si las cadenas son iguales. Se puede emplear =en vez de ==
cadena1 != cadena2 Verdad si las cadenas no son iguales
cadena1 < cadena2 Verdad si cadena1 se ordena lexicográficamente antesde cadena2 en la localización en curso
cadena1 > cadena2 Verdad si cadena1 se clasifica lexicográficamente despuésde cadena2 en la localización en curso

 

Para operaciones con ficheros:

-e fichero El fichero existe
-r fichero El fichero existe y tengo permiso de lectura
-w fichero El fichero existe y tengo permiso de escritura
-x fichero El fichero existe y tengo permiso de ejecución
-f fichero El fichero existe y es regular
-s fichero El fichero existe y es de tamaño mayor a cero
-d fichero El fichero existe y es un directorio
-L fichero El fichero existe y es un enlace simbólico

 

Además se pueden incluir operadores lógicos y paréntesis:

-o OR
-a AND
! NOT
\( Paréntesis izquierdo
\) Paréntesis derecho

 

Algunos ejemplos serían:

test $# -ne 2
test -f "$1"
test -d "$1"
[ $S1!=$S2 ]

 

Listas de órdenes y grupos de órdenes

Carácter ;  o salto de línea

Nos permite separar una orden de otra. El punto y coma nos permite además incluir más de una orden en la misma línea.

ls $HOME; pwd

 

Carácter \

Permite escribir una misma orden en más de una línea, normalmente cuando son muy largos.

mdadm --create /dev/md0 --metadata=0.90 \
  --level=1 --raid-devices=2 /dev/sdb1 /dev/sdc1

 

Carácter &

Ejecuta un comando en segundo plano y sigue con la ejecución del siguiente (leer más).

cp -axv / /mnt/disco2 &

 

orden1 && orden2

Nos permite simular una operación Y (AND). orden2 se ejecuta si y sólo si orden1 devuelve un código de error 0.

test -e leeme.txt && echo "El fichero leeme.txt sí existe"

 

orden1 || orden2

Nos permite simular una operación O (OR). orden2 se ejecuta si y sólo si orden1 devuelve un código de error distinto a 0.

test -e leeme.txt || echo "El fichero leeme.txt no existe"

 

{ orden1; orden2; ... ordenx; }

Todas las órdenes se ejecutan simplemente en el mismo entorno shell (terminal), sin crear un shell nuevo. Se conoce como orden de grupo.

{ cd bin; ls; }
{ echo a; echo b; } | sort

 

( orden1; orden2; ... ordenx; )

Todas las órdenes se ejecutan en un shell nuevo, diferente al que se lanzan las órdenes, se hace un fork (hijo).

( cd bin; ls; )
( echo a; echo b; ) | sort

 

Estructuras de control

NOTA: Las palabras reservadas then, in, do... para ir en la misma línea que la palabra reservada del bucle deben estar precedidas de un punto y coma que los separe de la condición o las variables.

 

Condicional simple (if)

Nos permite bifurcar la ejecución del script en caso de que la condición sea verdadera o falsa.

if expresión
then
  comandos1
else
  comandos2
fi

 

if expresión ; then
  comandos1
else
  comandos2
fi

 

if expresión1
then
  comandos1
elif expresión2
  comandos2
fi

 

A continuación mostramos un ejemplo:

 

if [ "$HOME" == "/root" ]
then
  echo "Eres el usuario ROOT"
else
  echo "Eres un usuario normal"
fi

 

Condicional múltiple (case)

Nos permite bifurcar la ejecución del script en función del valor de una variable. Se ejecutarán los comandos que existan desde la línea que posea el mismo valor que el almacenado en la variable hasta encontrar el siguiente valor. Podemos separar diferentes comandos al situarlos en líneas diferentes o con puntos y coma, teniendo en cuenta que siempre el último comando estará seguido de dos puntos y comas.

También podemos escribir varios valores separados por el carácter |. La opción * se ejecutará cuando el valor no coincida con ninguno de los expresados.

Las cadenas las podemos expresar con comillas o sin comillas, menos cuando contenga espacios en blanco, donde sí tendremos siempre que utilizar comillas.

 

case $variable in
  valor1) instrucción1; instrucción2;;
  valor2 | valor3) instrucción4;
                   instrucción5;;
  *) intrucción_por_defecto;;
esac

 

Un ejemplo sería:

 

read ELECCION

case $ELECCION in
  a|A)
    programa1
    programa2
    programa3
    ;;
  b|B)
    programa4
    programa5
    ;;
  c|C)
    programa3
    ;;
  *)
    echo "No eligió ninguna opción válida"
    ;;
esac

 

Bucle condicional mientras (while)

Los comandos incluidos en el interior del bucle se ejecutarán indefinidamente mientras las condiciones sean verdaderas. La sintaxis es:

 

while condicion
do
  instrucción1
  instrucción2
  ...
done

 

Un ejemplo:

 

opcion=1

while [ $opcion -ge 1 ] && [ $opcion -le 3 ]
do
  clear
  echo "Menu"
  echo "1) Opcion 1."
  echo "2) Opcion 2."
  echo "3) Opcion 3."
  echo "4) Salir."
  echo "Introduzca una opcion: "
  read opcion

  case $opcion in
    1) echo "Ejecutando 1."
       echo "Espere"
       sleep 2;;
    2) echo "Ejecutando 2."
       echo "Espere"
       sleep 2;;
    3) echo "Ejecutando 3."
       echo "Espere"
       sleep 2;;
    4 | 0) echo "Saliendo.";;
    *) echo "Ejecutando no valido.";;
       echo "Espere"
       sleep 2;;
  esac
done

 

Bucle condicional hasta (until)

Los comandos incluidos en el interior del bucle se ejecutarán indefinidamente hasta que las condiciones sean verdaderas. La sintaxis es:

 

until condicion
do
  instrucción1
  instrucción2
  ...
done

 

Un ejemplo:

 

opcion=1

until [ $opcion -eq 4 ] || [ $opcion -eq 0 ]
do
  clear
  echo "Menu"
  echo "1) Opcion 1."
  echo "2) Opcion 2."
  echo "3) Opcion 3."
  echo "4) Salir."
  echo "Introduzca una opcion: "
  read opcion

  case $opcion in
    1) echo "Ejecutando 1."
       echo "Espere"
       sleep 2;;
    2) echo "Ejecutando 2."
       echo "Espere"
       sleep 2;;
    3) echo "Ejecutando 3."
       echo "Espere"
       sleep 2;;
    4 | 0) echo "Saliendo.";;
    *) echo "Ejecutando no valido.";;
       echo "Espere"
       sleep 2;;
    esac
done

 

Bucle repetitivo para (for)

Los comandos incluidos en el interior del bucle se ejecutarán una vez por cada uno de los valores incluidos en la lista. Para crear una lista numérica podemos utilizar el comando seq.

 

for variable in lista
do
instrucción1
instrucción2
...
done

 

Una serie de ejemplos de utilización serían:

 

for numero in 1 2 3 4 5 6
do
  echo $numero
done

 

for numero in `seq 1 6`
do
  echo $numero
done

 

for www in www.google.com www.cisco.com
do
  echo $www
done

 

Ruptura de bucles: break y continue

Las órdenes break y continue sirven para interrumpir la ejecución secuencial del cuerpo de un bucle. La orden break transfiere el control a la orden que sigue a done, haciendo que el bucle termine antes de tiempo. La orden continue, por el contrario, transfiere el control a done, haciendo que se evalúe de nuevo la condición, es decir, la ejecución del bucle continúa en la siguiente iteración. En ambos casos, las órdenes del cuerpo del bucle siguientes a estas sentencias no se ejecutan. Lo normal es que formen parte de una sentencia condicional.

Un par de ejemplos de su uso serían:

# Muestra todos los parámetros, si encuentra una "f" finaliza
while [ $# -gt 0 ]
do
  if [ $1 = "f" ]
  then
    break
  fi
  echo Parámetro: $1
  shift
done

# Muestra todos los parámetros, si encuentra una "f"
# se lo salta y continúa el bucle
while [ $# -gt 0 ]
do
  if [ $1 = "f" ]
  then
    shift
    continue
  fi
  echo Parámetro: $1
  shift
done

 

Soluciones a problemas típicos

Comprobación de cadena vacía

La cadena vacía a veces da algún problema al tratar con ella. Por ejemplo, considérese el siguiente trozo de código:

if [ $a = "" ] ; then echo "cadena vacia" ; fi

¿Qué pasa si la variable a es vacía? pues que la orden se convierte en:

if [ = "" ] ; then echo "cadena vacia" ; fi

Lo cual no es sintácticamente correcto (falta un operador a la izquierda de “=”). La solución es utilizar comillas dobles para rodear la variable:

if [ "$a" = "" ] ; then echo "cadena vacia" ; fi

o incluso mejor, utilizar la construcción:

if [ "x$a" = x ] ;then echo "cadena vacia" ; fi

La “x” inicial impide que el valor de la variable se pudiera tomar como una opción.

 

 

Leer un fichero línea a línea

A veces surge la necesidad de leer y procesar un fichero línea a línea. La mayoría de las utilidades de UNIX tratan con el fichero como un todo, y aunque permiten separar un conjunto de líneas, no permiten actuar una a una. La orden read ya se vió para leer desde el teclado variables, pero gracias a la redirección se puede utilizar para leer un fichero.

while read i ; do
   echo "Línea: $i"
   ...
done < $fichero

El bucle termina cuando la función read llega al final del fichero de forma automática.

 

Leer argumentos opcionales de la línea de comandos

Aunque puede hacerse mediante programación “convencional”, el bash ofrece una alternativa interesante para esta tarea. Se trata de la orden getopts. La mejor manera de ver cómo se utiliza es con un ejemplo:

while getopts t:r:m MYOPTION; do
  case $MYOPTION in
    t) echo "El argumento para la opción -t es $OPTARG"
    ;;
    r) echo "El índice siguiente al argumento de -r es $OPTIND"
    ;;
    m) echo "El flag -m ha sido activado"
    ;;
    ?) echo "Lo siento, se ha intentado una opción no existente";
    exit 1;
    ;;
  esac
done

Podemos ahora probar el efecto de una invocación del guión como ésta:

./script -m -r hola -t adios -l

La salida sería la siguiente:

El flag -m ha sido activado
El índice siguiente al argumento de -r es 4
El argumento para la opción -t es adios
./guion: opción ilegal -- l
Lo siento, se ha intentado una opción no existente

El mensaje de la cuarta línea es en realidad enviado a la salida estándar de error, por lo que si se quisiera se podría eliminar redireccionando con 2> a /dev/null.

Como puede observarse, getopts comprueba si las opciones utilizadas están en la lista permitida o no, y si han sido llamadas con un argumento adicional (indicado por los :) en la cadena de entrada “t:r:m”. Las variables $MYOPTION, $OPTIND y $OPTARG contienen en cada paso del bucle, respectivamente, el carácter con la opción reconocida, el lugar que ocupa el siguiente argumento a procesar, y el parámetro correspondiente a la opción reconocida (si ésta iba seguida de : en la cadena de entrada.