Guíones del intérprete de comandos (shell scripts o archivos por lotes)
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:
- La primera línea del script indica el intérprete a utilizar
- El carácter '#' permite introducir comentarios hasta el final de línea
- 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/comando | Función | Ejemplo |
---|---|---|
$0 | Nombre del script. |
[vcarceler@valinor bin]$ cat -n argumentos |
$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 01 | Col 11 | Col 21 |
---|---|---|
if expresión |
if expresión |
if expresión1 |
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:
Operador | Funció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:
while | until |
---|---|
while expresión |
until expresión |
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:
Comando | Resultado |
---|---|
kdialog --title "Aplicación prueba" --yesno "Quiere borrar todos los datos ?" |
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.