Guíones del intérprete de comandos (shell scripts o archivos por lotes)

per Victor Carceler darrera modificació 2020-03-25T15:30:24+01:00

El intérprete de comandos puede funcionar en modo interactivo o bien ejecutar guíones (shell scripts), también llamados archivos por lotes. Un shell script es un fichero de texto que contiene sentencias para el intérprete de comandos. Se puede pedir al shell que interprete un script, y en tal caso, leerá y ejecutará cada sentencia del fichero sin esperar intervención humana.

Gracias a esta característica se pueden escribir scripts que automatizan tareas llevadas a cabo en el sistema informático.

Hay que observar que al utilizarse lenguajes intérpretados, normalmente se consigue un menor tiempo de desarrollo (el ciclo de desarrollo y prueba es más rápido pues no se necesita compilar) pero normalmente se consiguen tiempos de ejecución mayores que si se utilizase un lenguaje compilado. Normalmente los lenguajes interpretados presentan un entorno de más alto nivel que los lenguajes compilados.

Cuando se ejecuta un script, se crea un nuevo proceso que ejecuta el script en un subshell. Al terminar el script termina el subshell, y el proceso, que lo ejecutaba. De este modo, si por ejemplo en un script se cambia el directorio actual (con el comando cd), no se está cambiando el directorio actual del shell desde el que se invoca el script, sinó el del shell que ejecuta el script. Si se desea permitir que la ejecución del script afecte al shell desde el que fué invocado, debe utilizarse el carácter . de la siguiente manera:

$ script_ejecutado_en_un_subshell
$ . script_ejecutado_en_este_shell

Sintáxis básica

Un shell script no es más que un fichero con sentencias para el intérprete que se esté utilizando, por lo tanto se podrá escribir cualquier cosa que el intérprete entienda. Entre los intérpretes más utilizados en la administración de sistemas se encuentran: Bash, Perl y Python. En el entorno web está muy extendido el uso de PHP.

En el caso de Unix, en la primera línea del shell script se debe indicar el intérprete que se va a utilizar, precedido por el prefijo '#!'.

Ejemplo:

[vcarceler@valinor bin]$ cat -n saluda
1 #!/bin/bash
2
3 # Símplemente muestra un mensaje de saludo
4
5 usuario=`whoami`
6 echo "Hola $usuario"
[vcarceler@valinor bin]$ saluda
Hola vcarceler
[vcarceler@valinor bin]$

En el ejemplo se puede ver:

  1. La primera línea del script indica el intérprete a utilizar
  2. El carácter '#' permite introducir comentarios hasta el final de línea
  3. El shell script debe tener activado el permiso de ejecución, y si se quiere ejecutar automáticamente al escribir su nombre, debe encontrarse en uno de los directorios incluidos en la variable de entorno PATH

 

Argumentos posicionales

Cuando se invoca un script se pueden especificar argumentos en la línea de comandos. El script puede acceder a estos parámetros mediante las variables $0, $1, $2 .. $9 y el comando shift.

variable/comandoFunciónEjemplo
$0 Nombre del script.
[vcarceler@valinor bin]$ cat -n argumentos
1 #!/bin/bash
2
3 echo Nombre del script: $0
4 echo Número de argumentos: $#
5 echo Cadena con todos los argumentos: $*
6 echo Primer argumento: $1
7 echo Segundo argumento: $2
8 shift # Desplazamos los argumentos
9 echo Primer argumento: $1
[vcarceler@valinor bin]$ argumentos hola que tal?
Nombre del script: /home/vcarceler/bin/argumentos
Número de argumentos: 3
Cadena con todos los argumentos: hola que tal?
Primer argumento: hola
Segundo argumento: que
Primer argumento: que
[vcarceler@valinor bin]$
$1 Primer argumento posicional
$2 Segundo argumento posicional
$3 Tercer argumento posicional
$4 Cuarto argumento posicional
$5 Quinto argumento posicional
$6 Sexto argumento posicional
$7 Séptimo argumento posicional
$8 Octavo argumento posicional
$9 Noveno argumento posicional
$#
Número de argumentos
$*
Cadena con todos los argumentos
shift
Desplaza hacia la izquiera los argumentos.
El argumento número 10 pasa a la posición 9,
el argumento 9 a la 8 y así sucesivamente.

 

Código de retorno

Cada programa que se lanza desde el shell termina guarda su código de retorno en la variable $?. El código de retorno es un valor numérico entero que indica si el programa ha terminado de manera correcta o bien ha terminado porque se ha producido un error.

El código de retorno de valor 0 indica una ejecución correcta, cualquier otro valor indica un error.

Ejemplo:

ubuntu@bilbo:~$ cat datos
1 2 3
ubuntu@bilbo:~$ echo $?
0
ubuntu@bilbo:~$ cat fichero_que_no_existe
cat: fichero_que_no_existe: No such file or directory
ubuntu@bilbo:~$ echo $?
1
ubuntu@bilbo:~$

Cuando se está programando un shell script, es posible terminar la ejecución del mismo indicando el código de retorno con el comando exit.

ubuntu@bilbo:~/bin$ cat -n script
1 #!/bin/bash
2 echo "Soy un script de prueba"
3 # Suponiendo que se produce un error y queremos terminar
4 # la ejecución de nuestro script.
5 exit 23
6 echo "Esta línea no se vé nunca"
ubuntu@bilbo:~/bin$ ./script
Soy un script de prueba
ubuntu@bilbo:~/bin$ echo $?
23
ubuntu@bilbo:~/bin$

Un script que termina sin utilizar exit devuelve el código de retorno del último comando ejecutado

 

El operador if

Como en otros lenguajes, en los guiones de Bash también aparece la estructura condicional if.

Sus posibles formas son:

Col 01Col 11Col 21
if expresión
then lista_órdenes
fi
if expresión
then lista_órdenes
else otras_órdenes
fi
if expresión1
then órden1
elif expresión2
then órden2
else órden3
fi

Hay que recordar que se considera cierto el valor 0 y falso cualquier otro valor.

Ejemplo:

[vcarceler@localhost ~]$ if true
> then
> echo cierto
> else
> echo falso
> fi
cierto
[vcarceler@localhost ~]$ if true; then echo cierto; else echo falso; fi
cierto
[vcarceler@localhost ~]$

En el anterior ejemplo se muestra cómo escribir la misma sentencia condicional en su versión multilínea y en una sóla línea. El comando true símplemente devuelve el código de retorno cierto (0 en este caso).

test y [ ]

test evalúa una condición y puede retornar:

  • 0 si la condición se cumple
  • null en caso contrario

Un modo alternativo de evaluar condiciones es utilizar los corchetes. Por ejemplo, para comprobar si existe el fichero local.css podemos hacer:

[vcarceler@localhost ~]$ if test -f local.css; then echo 'Existe !!!'; fi
Existe !!!
[vcarceler@localhost ~]$ if [ -f local.css ]; then echo 'Existe !!!'; fi
Existe !!!

Si se utilizan los corchetes, es muy importante colocar un espacio entre los corchetes y la condición. En este ejemplo se ha utilizado el operador -f para comprobar la existencia de un fichero, pero existen más posibilidades:

OperadorFunción:
[ -f fichero ] Cierto si el fichero regular existe
[ -x fichero ]
Cierto si el fichero existe y es ejecutable
[ -r fichero ] Cierto si el fichero existe y se puede leer
[ -w fichero ] Cierto si el fichero existe y se puede escribir
[ -d fichero ] Cierto si el fichero existe y es un directorio
[ -s fichero ] Cierto si el fichero existe y no está vacío
[ cadena ] Cierto si no es la cadena vacía
[ -z cadema ]
Cierto si la longitud de la cadena es cero
[ -n cadena ] Cierto si la longitud de la cadena no es cero
[ cadena1 = cadena2 ] Cierto si las cadenas son iguales
[ cadena1 != cadena2 ] Cierto si las cadenas son diferentes
[ num1 -eq num2 ] Cierto si los números son iguales
[ num1 -ne num2 ] Cierto si los números son diferentes
[ num1 -gt num2 ] Cierto si num1 > num2
[ num1 -ge num2 ] Cierto si num1 >= num2
[ num1 -lt num2 ] Cierto si num1 < num2
[ num1 -le num2 ] Cierto si num1 <= num2

Se puede consultar la lista completa de expresiones condicionales en la documentación oficial de Bash.


Es posible utilizar operadores lógicos ! (not), -a (and) y -o (or) para construir una expresión lógica:

f1=fichero1; f2=fichero2
if [ ! -f $f1 -a -f $f2 ]
then
echo "$f1 no existe pero $f2 si existe"
fi

Operaciones aritméticas


El shell proporciona un soporte básico para las expresiones aritméticas con enteros mediante el comando interno let. El comando expr también permite calcular expresiones con enteros además de proporcionar algunas operaciones con cadenas.

[vcarceler@localhost ~]$ let a="2 + 3 + 1"
[vcarceler@localhost ~]$ echo $a
6
[vcarceler@localhost ~]$ b=`expr 10 % 3`
[vcarceler@localhost ~]$ echo $b
1
[vcarceler@localhost ~]$

Pero con frecuencia estas herramientas se quedan pequeñas y es necesario recurrir a:

 

dc
Una de las utilidades más antiguas de Unix. Implementa una calculadora de precisión arbitraria con notación polaca inversa.
bc
Igual que dc pero utilizando la notación convencional. También tiene precisión arbitraria y se puede utilizar de forma interactiva o bien desde un script.

 

Ejemplos:

[vcarceler@localhost ~]$ dc -e '4 5 * p'
20
[vcarceler@localhost ~]$ echo 22 33 | dc -e '?[dSarLa%d0<a]dsax+p' # MCD de 22 y 33
11
[vcarceler@localhost ~]$ echo "(10 + 5) / 2" | bc
7
[vcarceler@localhost ~]$ echo "scale=10; (10 + 5) / 2" | bc
7.5000000000

Si se quiere pasar de la aritmética al álgebra, Maxima puede ser un buen punto de partida.

El operador case

La sentencia case toma una cadena de caracteres y la compara con varios patrones, ejecutando las sentencias asociadas con el patrón que coincide con la cadena. Puede utilizarse el carácter '|' para especificar varios patrones equivalentes.

Ejemplo:

[vcarceler@valinor bin]$ cat -n borra
1 #!/bin/bash
2
3 if [ $# -ne 1 ]
4 then
5 echo "$0 -> Debe especificar un fichero"
6 echo "Uso: $0 <fichero>"
7 exit 1
8 fi
9
10 if [ ! -f $1 ]
11 then
12 echo "$0 -> El fichero $1 no existe"
13 exit 1
14 fi
15
16 echo -n "Borro el fichero $1 ? "
17 read respuesta
18
19 case $respuesta in
20 [Ss]* | [Oo][Kk])
21 echo -n "Borrando el fichero... "
22 rm $1
23 echo "Borrado"
24 ;;
25 *)
26 echo "El fichero no será borrado"
27 ;;
28 esac

 

Operadores condicionales && y ||

Los operadores condicionales && y || sirven para escribir condiciones cortas que son equivalentes a algunas sentencias if.
Se intercalan entre dos sentencias, la primera de las cuales siempre se ejecuta y en función de su valor de retorno se ejectua la segunda.

orden1 && orden2
orden1 || orden2

El operador && (AND lógico) ejecuta la primera orden, si su valor de retorno es cierto (valor 0) entonces se evalúa la segunda orden. El operador || (OR lógico) ejecuta la primera orden, sólo si esta retorna falso (valor no nulo) se ejecuta la segunda orden.

Ejemplos:

gcc holamundo.c && echo "No hubo problemas al compilar"
gcc holamundo.c || echo "Hubo problemas al compilar"

El operador for

En el shell scripts también se pueden construir bucles, pero a diferencia de C, en Bash se debe especificar una lista de elementos para iterar. Se realizarán tantas iteraciones como elementos tenga la lista, en cada iteración la variable índice tomará el valor de uno de los elementos de la lista.

Ejemplo:

[vcarceler@valinor bin]$ cat -n bucle_for && bucle_for
1 #!/bin/bash
2 for x in a e i o u
3 do
4 echo "x vale $x"
5 done
x vale a
x vale e
x vale i
x vale o
x vale u
[vcarceler@valinor bin]$

Generación automática de secuencias

Cuando se programe un script, pocas veces se va a conocer de antemano los elementos que componen la lista por la que se itera. Normalmente esta lista se generará dinámicamente (cuando se vaya a ejecutar el bucle) de una de estas dos maneras:

  • Utilizando las comillas invertidas para generar la lista a partir de la salida de un comando. Por ejemplo, para iterar por una lista que contiene el nombre de los elementos del directorio raíz: for x in `ls /`
  • Utilizando el comando seq que genera secuencias de números. En este comando se debe indicar: el primer número, el incremento, y el valor máximo. Por ejemplo: for x in `seq 1 1 100`

Los operadores while y until

Los bucles while y until evaluan una condición antes de interar. En el caso de while, se itera mientras la condición es cierta, y en el caso de until mientras es falsa.

Su estructura es:

whileuntil
while expresión
do
lista_órdenes
done
until expresión
do
lista_órdenes
done

Sentencias de control de iteración break y continue

Aunque no es una buena práctica, pues no se ajusta a los principios de la programación estructurada, es posible controlar la iteración de un bucle con break y continue.

Ambas se pueden utilizar en el interior de cualquier bucle. Su función es:

break
Se abandona el bucle que la contiene
continue
Fuerza una nueva iteración

Funciones

En los scripts bash también se pueden declarar funciones, de manera que no hay que repetir código en diferentes partes de un script. Hay que declarar una función y llamarla todas las veces que haga falta. Cada función tiene un identificador (nombre) diferente, y una lista de argumentos posicionales propia. El código de retorno de una función es el código de retorno de la última sentencia ejecutada en la función.

Ejemplo:

#!/bin/bash

function f1 () {
echo "Soy la función 1 y he recibido $# argumentos"
}

function f2 () {
echo "Soy la función 2 y he recibido $# argumentos"
}

f3 () {
echo "Soy la función 3 y he recibido $# argumentos"
}

function f4 {
echo "Soy la función 4 y he recibido $# argumentos"
}

f1
f2 a
f3 a b
f4 a b c

Cuando se declara una función se puede omitir la palabra reservada function o los paréntesis, pero no las dos cosas a la vez.

Al ejecutar el script de ejemplo, se obtiene:

Soy la función 1 y he recibido 0 argumentos
Soy la función 2 y he recibido 1 argumentos
Soy la función 3 y he recibido 2 argumentos
Soy la función 4 y he recibido 3 argumentos

Mejorando los scripts

El shell Bash permite automatizar cualquier tarea, pero hay muchos elementos que se pueden utilizar para mejorar la solución, por ejemplo, haciendo que el uso del script sea más intuitivo para el usuario.

Utilizando dialog

El comando dialog permite crear diferentes diálogos (información, si/no, calendario para seleccionar fechas,...) en la cónsola desde un script. De esta manera la comunicación entre el script y el usuario se puede hacer más intuitiva. Por defecto, dialog deja su salida en stderr, pero existe una opción para hacer que la deje en stdout.

[vcarceler@valinor ~]$ fecha=`dialog --stdout --calendar "Seleccione la fecha en la que se realizará la copia de seguridad."  5 20`
#Se muestra el diálogo y el usuario puede seleccionar la fecha
[vcarceler@valinor ~]$ echo $fecha
18/01/2006
[vcarceler@valinor ~]$

Utilizando kdialog y zenity

El comando kdialog forma parte del escritorio KDE y permite crear desde un script diálogos gráficos.

Ejemplo:

ComandoResultado
kdialog --title "Aplicación prueba" --yesno "Quiere borrar todos los datos ?" kdialog.png

En el escritorio Gnome, el comando zenity es la alternativa a kdialog.

Otros lenguajes

Aunque es posible hacer muchas cosas con Bash, no siempre es la elección más cómoda. Dos de los lenguajes más potentes en la administración de sistemas son Perl y Python. Ambos son lenguajes interpretados, portables y de muy alto nivel. Cuentan con una amplia comunidad y escribiendo muy poco código permiten hacer mucho trabajo.

Más información: