Tema 7 Linux Shell Scripts
Tema 7 Linux Shell Scripts
Tema 7 Linux Shell Scripts
CURSO 2012/2013
Sistemas
informáticos
Tema 7
Los scripts no son más que ficheros de texto ASCII puro, que pueden ser creados con cualquier
editor del que dispongamos (vi, nano, gedit, emacs, etc.). Crea un fichero de texto de nombre
primero.sh, con el siguiente contenido:
#!/bin/bash
echo “Hola Mundo”
La primera línea sirve para indicar que shell utilizamos (en nuestro caso bash) y donde puede
ser encontrado en nuestro sistema (para saberlo, podemos hacer locate bash ). Esta línea
debe ser la primera de todos los scripts que realicemos.
La segunda línea de nuestro script, simplemente utiliza el comando para escribir en pantalla
(echo) y escribe la línea Hola Mundo.
Una vez creado el fichero, debemos darle permisos de ejecución, mediante el comando
Posteriormente para ejecutarlo debemos llamarlo como ./permiso.sh (el punto barra es para
indicarle que lo busque en el directorio actual, ya que dicho directorio no estará seguramente
incluido en el PATH del sistema).
Las comillas dobles que hemos usado para escribir Hola Mundo no son necesarias, y podéis
comprobar como quitándolas el proceso se ejecuta exactamente igual. Sin embargo, es una
buena práctica encerrar siempre los textos entre comillas dobles, y en caso de que contengan
caracteres especiales (como el *, el $, etc.), es mejor usar comillas simples, que son más
potentes que las comillas dobles. Prueba lo siguiente:
Si tenemos que ejecutar varias líneas y queremos escribirlas en una sola, podemos hacerlo
usando el símbolo punto y coma para indicar que lo siguiente es otra línea, aunque este en la
misma:
echo Hola ; pwd ; echo Adios # esto son tres líneas en una sola.
También podemos hacer lo contrario, escribir una sola línea en varias. Para ello usamos el
carácter contrabarra cuando queramos que nuestra línea se “rompa” y continué en la línea de
abajo.
echo esto \
es una sola línea \
aunque ocupe 3 en pantalla. # esto es una línea escrita en tres.
En estos últimos ejemplos mostramos como se pueden usar comentarios en los scripts. Basta
con usar el símbolo almohadilla (#) donde queramos, todo lo que quede a la derecha de dicho
Página 5 Linux. Shell Scripts.
IMPLANTACIÓN DE SISTEMAS OPERATIVOS.CURSO 2012/2013
símbolo es un comentario.
Si usamos # como primer carácter de una línea, toda la línea es de comentario.
Las variables de los shell scripts son muy simples, ya que no tienen tipo definido ni necesitan ser
declaradas antes de poder ser usadas. Para introducir valor en una variable simplemente se usa
su nombre, y para obtener el valor de una variable se le antepone un símbolo dólar.
#!/bin/bash
DECIR=”Hola Mundo”
echo $DECIR
Este script realiza exactamente la misma función que el anterior, pero usando una variable.
Cualquier valor introducido en una variable se considera alfanumérico, así que si realizamos lo
siguiente:
$(ORDEN)
En Linux podemos usar varias expansiones en las líneas de comandos, que son especialmente
útiles en los scripts. La primera expansión consiste en usar $( ) . Esta expansión permite
ejecutar lo que se encuentre entre los paréntesis, y devuelve su salida.
NOMBRE_FICHERO=”red”$(date +%d%m%y)”.conf”
cp /etc/network/interfaces $NOMBRE_FICHERO
El efecto conseguido con $( orden ) se puede conseguir también usando la tilde invertida
`orden`.
$((OPERACIÓN ARITMÉTICA))
Otra expansión que podemos usar es $(( )) (símbolo dólar pero con dos paréntesis). Los dobles
paréntesis podemos sustituirlos si queremos por corchetes. $[ ].
Esta expansión va a tratar como una expresión aritmética lo que esté incluido entre los
paréntesis, va a evaluarla y devolvernos su valor.
NUMERO=4
echo $(($NUMERO+3)) # sería lo mismo poner echo $[$NUMERO+3]
El comando let, que nos permite realizar operaciones aritméticas como la anterior, pero sin
tener que usar expansiones ni dólares para las variables.
NUMERO=4
let SUMA=NUMERO+3
echo $SUMA
Los operadores aritméticos que podemos usar para realizar operaciones son:
Resta (-), Suma (+), División (/), Multiplicación (*) y Modulo o Resto (%).
LET. BC.
Cread con nano un fichero con nombre media.sh con el siguiente contenido:
Para hacer ejecutable media.sh y ejecutadlo. Si lo habéis escrito bien veréis como realmente os da la
media aritmética de las tres notas introducidas.
La línea
MEDIA=$[ $SUMA / 3 ]
Podría haberse escrito también como
let MEDIA=SUMA/3
Si ejecutamos el script, veremos que hay un problema, podéis comprobar como bash no trabaja
con decimales, de modo que, si introducimos por ejemplo 10, 10 y 6 nos dirá que la media es
8, mientras que la media realmente es 8,66.
Podemos obligar a que bash trabaje con decimales utilizando un comando que sirve como
calculadora en Linux, este comando es bc. Este comando admite un gran número de
parámetros, pero en estos apuntes vamos a usarlo simplemente para indicar que queremos
obtener decimales en las operaciones. Para ello simplemente haremos el siguiente cambio..
Vemos cómo debemos generar una salida con echo, el primer campo scale indica cuantos
decimales queremos obtener (4 en este caso), luego y separado por un punto y coma ponemos
la operación aritmética que deseamos realizar, sin necesidad de poner corchetes, dobles
paréntesis o usar let. El resultado de este echo lo enviamos al comando bc –l mediante una
tubería.
ESTRUCTURAS CONDICIONALES.
IF.
if [ expresión ]; then
Realizar si expresión es verdadera
fi
Los anteriores operadores sólo son válidos para comparar cadenas, si queremos comparar
valores numéricos, hemos de usar los siguientes operadores:
Si usamos operadores de comparación numéricos con valores de cadena, el sistema nos dará
un error como el siguiente:
IF..ELSE
La estructura if podemos ampliarla usando la construcción else (en caso contrario) y elif (en
caso contrario si…).
if [ expresión 1 ]; then
Realizar si expresión 1 es verdadera
else
IF..ELIF..ELSE
if [ expresión1 ]; then
Realizar si expresión1 es verdadera
else
realizar si todas las expresiones anteriores son falsas
fi
Hay que tener muchísimo cuidado con los espacios en blanco, y seguramente durante nuestros
primeros scripts casi todos los errores vendrán por haberlos usado mal en las estructuras if.
Hay que recordar que los corchetes llevan espacios en blanco tanto a izquierda como derecha,
que el punto y coma sin embargo va pegado al corchete cerrado, y que SIEMPRE hay que poner
espacios en blanco en las expresiones.
#!/bin/bash
PROFESOR=”Juana”
if [ $PROFSOR = “Juana” ]; then
echo “Hola Juana”
fi
Que traducido resulta, me he encontrado un [ (corchete abierto) y luego un operador (el =) sin
nada en medio, y eso no funciona.
Hemos visto operadores aritméticos y operadores para cadena, pero en las expresiones
podemos utilizar cualquier operación que nos devuelva un valor lógico (0 para verdadero). Por
ejemplo, podemos usar la función test del bash, que funciona de la siguiente forma:
Si lo necesitamos, podemos anidar expresiones usando tanto and (y, &&) como or (o, ||).
También podemos usar el operador not (!) para indicar una negación.
if ! [ expresión1 ]; then
se ejecuta si expresión1 NO es verdadera
fi
Para hacer algunos ejercicios, vamos a aprovechar para explicar mejor cómo le podemos pedir
datos al usuario. Se hace con la orden read y es muy simple de usar:
La ejecución del script se parará, mostrará por pantalla el texto de la pregunta, y dejará que el
usuario escriba la respuesta, cuando pulse INTRO la respuesta dada se introducirá como valor
de variable.
read también puede ser usada sin el parámetro -p, de la forma read variable. También podemos
hacer que lea un determinado número de caracteres, sin obligar a que el usuario pulse intro,
con el parámetro -n número_de_caracteres. El parámetro -s silencia el eco (no se ve por
pantalla lo que el usuario escribe).
Ahora que sabemos usar el read, hagamos por ejemplo un programa que nos permita indicar si
un número introducido es par o impar.
#!/bin/bash
# parimpar.sh - script que nos pide un número e indica si es par o impar.
clear
read -p “Introduzca un número: “ NUMERO
let RESTO=NUMERO%2
if [ $RESTO -eq 0 ]; then
echo “El número $NUMERO es par”
else
echo “El número $NUMERO es impar”
fi
Hagamos un script un poco más complicado… vamos a pedir al usuario un número de 3 cifras y
vamos a indicar si es capicúa.
#!/bin/bash
# capicua.sh - script que nos pide un número de tres cifras e indica si es
# capicúa o no.
clear
read -n 3 -p “Número entre 100 y 999 (no pulses INTRO) : “ NUMERO
echo # este echo sirve para introducir un retorno de línea
if [ $NUMERO -lt 100 ]; then
echo “Lo siento, has introducido un número menor de 100”
else
PRIMERA_CIFRA=$(echo $NUMERO | cut -c 1)
TERCERA_CIFRA=$(echo $NUMERO | cut -c 3)
if [ $PRIMERA_CIFRA = $TERCERA_CIFRA ]; then
echo “El número $NUMERO es capicúa.”
else
echo “El número $NUMERO ni es capicúa ni ná”.
fi
fi
Es evidente que podíamos haber hecho este último script mucho más corto, por ejemplo
usando una línea como:
Cuando hacemos un script de varias líneas como el anterior, es posible que cometamos algún
fallo. Una opción que podemos usar para depurar los scripts y encontrar rápidamente los
errores, es añadir un -x en la llamada al bash de la primera línea. Esto hará que cada línea antes
de ejecutarse sea mostrada por pantalla tal y como la está interpretando el bash.
#!/bin/bash -x
Para que esto funcione, es necesario que hagamos el script ejecutable, no es válido si
lanzamos el script con la orden source o con el punto. Hay que ejecutar el script haciéndolo
antes ejecutable con chmod y luego ejecutándolo con ./script.
CASE.
Hemos visto la principal estructura condicional que es el if, pero tenemos alguna otra a nuestra
disposición, como el case. Esta estructura nos permite ejecutar varias acciones, dependiendo del
valor de una variable o expresión.
case VARIABLE in
valor1)
se ejecuta si VARIABLE tiene el valor1;;
valor2)
se ejecuta si VARIABLE tiene el valor2;;
Esac
En el case, no solo podemos preguntar por valores directos, sino que también podemos utilizar
los comodines que vimos anteriormente. Veamos un par de ejemplos de case utilizado junto con
comodines.
Las principales estructuras iterativas que podemos usar en shell scripts son for, while, until y
select.
FOR.
Ese conjunto que aparece en la estructura del for, es normalmente un conjunto de valores
cualesquiera, separados por espacios en blanco o retornos de línea. Así, si queremos mostrar
los días de la semana por pantalla podríamos hacerlo mediante este script:
Así, por ejemplo, si queremos obtener por pantalla los números del 1 al 10 podríamos hacerlo
de la siguiente forma:
La potencia del comando for viene de la flexibilidad de valores que admite el conjunto de
valores, ya que podemos crear dicho conjunto con una orden del sistema operativo. En el
siguiente ejemplo vamos a usar como conjunto los nombres de los ficheros con extensión sh
del directorio actual:
El conjunto puede ser cualquier salida de cualquier orden, y formara elementos utilizando el
espacio en blanco como separador de elementos. Fijaros en el siguiente ejemplo:
Vemos como utilizamos como conjunto el contenido de un fichero. Vemos también como la
línea “jose antonio” la divide en 2 elementos distintos debido al espacio en blanco.
Existe una orden en GNU/Linux que nos permite obtener una secuencia de números como
salida de la orden, esta orden es seq.
seq último-número
seq primer-número último-número
seq primer-número incremento último-número
Vamos a realizar un ejemplo algo más complejo utilizando for y seq. Vamos a crear un script
llamado suma100.sh que nos va a decir por pantalla cuanto suman todos los números del 1 al
100, es decir, 1+2+3+4+5…+100.
Modifica el anterior ejercicio para que el script sume todos los números pero no entre 1 y 100,
sino entre dos números que pida el script por pantalla.
Desde la versión de bash 3.0 se introdujo un cambio en el for que permite utilizar directamente
rangos sin tener que usar la orden seq. Si estamos seguros de que contamos con un bash
moderno podemos utilizar la siguiente característica del for:
Desde la versión de bash 4.0 se introdujo otro cambio, que permite utilizar también incrementos
en los rangos, de la siguiente manera:
{INICIO..FINAL}
{INICIO..FINAL..INCREMENTO}
Si nuestro bash es de un sistema relativamente moderno, podemos usar estos rangos sin ningún
tipo de problemas, que tienen la ventaja adicional de ser algo más rápidos que la orden seq.
Si no estamos seguros de los sistemas sobre los que se ejecutara nuestro script podemos usar
el seq para conseguir una mayor compatibilidad, aunque hoy en día es muy difícil encontrar en
ningún sistema un bash inferior al 4.0.
El for de bash también permite utilizar el formato basado en trio de expresiones común al
lenguaje C.
Veamos un ejemplo, con un script que como salida nos muestra los números pares entre 2 y
40.
Podemos utilizar la instrucción break para salirnos inmediatamente de un bucle for. Fijaros en
el siguiente ejemplo:
Este tipo de elementos (bucles infinitos, break, etc.) se consideran como “poco elegantes”
desde el punto de vista de la programación y es mejor acostumbrarse a no usarlos, ya que
existen otro tipo de alternativas más refinadas. Sin embargo son herramientas potentes y es
conveniente conocerlas.
Imaginemos que queremos copiar a un llaverito USB (montado en /media/usbdisk por ejemplo)
todos los scripts que tengamos en nuestro directorio home, sin importar en que directorio estén,
podríamos hacerlo fácilmente con este script:
#!/bin/bash
for programa in $( find ~ -iname “*sh” 2> /dev/null ); do
echo “copiando el script :” $programa
cp $programa /media/usbdisk
done
Ya que estamos, mejoremos el script anterior para que cree un directorio scripts en nuestro
llaverito, pero únicamente si no existe.
#!/bin/bash
if ! [ -d /media/usbdisk/scripts ]; then
mkdir /media/usbdisk/scripts
fi
for programa in $( find ~ -iname “*sh” 2> /dev/null ); do
echo “copiando el script :” $programa
cp $programa /media/usbdisk
done
WHILE Y UNTIL
Cuando no queremos recorrer un conjunto de valores, sino repetir algo mientras se cumpla una
condición, o hasta que se cumpla una condición, podemos usar las estructuras while y until.
while [ expresión ]; do
estas líneas se repiten MIENTRAS la expresión sea verdadera
done
until [ expresión ]; do
estas líneas se repiten HASTA que la expresión sea verdadera
done
Ambas estructuras, tanto while como until realmente realizan exactamente lo mismo, al
efectuar la comprobación de la expresión en la primera línea, no como en otros lenguajes.
#!/bin/bash
#doble.sh - script que pide números y muestra el doble de dichos números.
# el script continua ejecutándose mientras que no se introduzca 0.
read -p “Dime un número (0 para salir) : “ NUMERO
while [ $NUMERO -ne 0 ]; do
echo “El doble de $NUMERO es :” $(($NUMERO*2))
read -p “Dime un número (0 para salir) : “ NUMERO
done
#!/bin/bash
#doble.sh - script que pide números y muestra el doble de dichos números.
# el script continua ejecutándose mientras que no se introduzca 0.
read -p “Dime un número (0 para salir) : “ NUMERO
until [ $NUMERO -eq 0 ]; do
echo “El doble de $NUMERO es :” $(($NUMERO*2))
read -p “Dime un número (0 para salir) : “ NUMERO
done
#!/bin/bash
NUMERO=1
until [ $NUMERO -gt 20 ]; do
echo “Número vale :” $NUMERO
let NUMERO=NUMERO+1
done
SELECT
La última estructura iterativa que vamos a ver es select. Esta nos permite realizar una iteración
o bucle, pero presentando un menú por pantalla para que el usuario escoja una opción. Su
estructura general es la siguiente:
Esta estructura como vemos es muy parecida a la del for, pero presenta la principal diferencia
en que por definición se crea un bucle sin final, no hay un valor inicial y un valor límite, el bucle
se repetirá eternamente, lo que nos obliga a salirnos del mismo por las bravas, bien con break
que nos permite salirnos del bucle o con exit que nos permite salirnos del script entero.
Veamos un ejemplo:
Al ejecutar el script por pantalla nos presentará un menú automáticamente formado por el
conjunto de opciones que hemos puesto en el select.
Podemos comprobar como el bucle es infinito, y la única forma de salir es usando la opción Salir
que en el script ejecuta un break.
Al igual que sucedía con el for, es perfectamente posible crear el conjunto mediante una
instrucción. Así por ejemplo, la instrucción ls nos devuelve un conjunto formado por todos los
ficheros del directorio actual. Vamos a trabajar sobre esta idea:
#!/bin/bash
select FICHERO in $( ls ); do
echo Has seleccionado el fichero $FICHERO
# Ahora podríamos borrarlo, copiarlo, visualizarlo, etc.
Done
Si ejecutáis ese script, veréis dos cosas: Como el conjunto está formado por todos los ficheros
del directorio actual, y como es imposible detener la ejecución del script, como no sea matando
el proceso en primer plano con Control + C
Por cierto, si en el conjunto ponemos directamente el símbolo asterisco ( * ) veremos que tiene
la misma función que un ls, devuelve el listado de ficheros del directorio actual.
select FICHERO in *; do
Hagamos otro ejemplo sobre el select, un poco más avanzado. Vamos a mostrar por pantalla
un menú con todos los mp3 que existan en el directorio home del usuario actual, y vamos a
dejar que escoja uno de ellos para reproducirlo. (Para ello uso un reproductor de mp3 desde
línea de comandos podemos usar la orden mpg321, si no la tenéis instalado lo podéis instalar
con un apt-get install mpg321).
#!/bin/bash
clear
select MP3 in $( find . -iname "*mp3" ); do
echo "Voy a reproducir el mp3 : " $MP3
mpg321 $MP3 &> /dev/null
done
#!/bin/bash
clear
CONJUNTO=$(find . -iname "*mp3")
CONJUNTO=$CONJUNTO" Salir"
select MP3 in $CONJUNTO; do
if [ $MP3 = "Salir" ]; then
break
fi
echo "Voy a reproducir el mp3 : " $MP3
mpg321 $MP3 &> /dev/null
done
Como siempre en Informática, este script tan sencillo se puede complicar hasta lo inimaginable.
Por ejemplo, este script necesita para funcionar que los nombres de los archivos mp3 no
contengan espacios en blanco, ya que el conjunto separa sus valores por este carácter. Así, si
tuviéramos una canción con nombre La Gasolina, veríamos que en el menú nos aparecen dos
opciones 1) La y 2) Gasolina, por lo que el script como es obvio no funcionará. ¿Se os ocurre
alguna manera de solucionarlo?
Podemos pasar parámetros tanto a los scripts como a las funciones. Los parámetros en bash se
indican como un símbolo dólar ($) seguido de un número o carácter. Los principales parámetros
que se pueden usar son:
Parámetros
$1 Devuelve el 1º parámetro pasado al script o función al ser llamado.
$2 Devuelve el 2º parámetro.
$3 Devuelve el 3º parámetro. (Podemos usar hasta $9).
$* Devuelve todos los parámetros separados por espacio.
$# Devuelve el número de parámetros que se han pasado.
$0 Devuelve el parámetro 0, es decir, el nombre del script o de la función.
script01.sh juan 12 45
$0 $1 $2 $3
$* = juan 12 45
$# = 3
#!/bin/bash
# parámetros.sh - script sobre parámetros.
echo “El primer parámetro que se ha pasado es “ $1
echo “El tercer parámetro que se ha pasado es “ $3
echo “El conjunto de todos los parámetros : “ $*
echo “Me has pasado un total de “ $# “ parámetros”
echo “El parámetro 0 es : “ $0
¿Existe un parámetro especial, el $? que nos devuelve el valor del resultado de la última orden.
Es decir, después de ejecutar cualquier orden o comando del sistema (o casi cualquier orden
mejor dicho) podemos comprobar el valor de $? que tendrá un 0 si todo ha ido bien, y otro
valor cualquiera en caso de que haya fallado. Comprobarlo es muy simple:
cd /juegos/faluyah
echo $?
¿Comprobareis como $? vale 1, es decir, indica que la última orden no funcionó correctamente.
cd /etc/network
echo $?
Comprobareis como vale 0, es decir, indica que la última orden funciono sin problemas.
Este parámetro puede sernos muy útil realizando scripts, ya que nos permite una forma rápida
y cómoda de ver si todo está funcionando bien o no.
Como ejemplo, realizad un script con nombre borrar.sh. Dicho script aceptará como parámetro
el nombre de un fichero. El script debe eliminar ese fichero, pero antes debe guardar una copia
de seguridad del mismo en el directorio papelera que debemos crear en nuestro home de
usuario. Una vez comprobado que funciona, pasadle como parámetro el nombre de un fichero
que el usuario no tenga permisos para borrar (recordad que además debe estar en un directorio
en el que el usuario no tenga el permiso de escritura). ¿Como es obvio, el script nos dará un
error al intentar borrar dicho fichero, pues precisamente después de ese rm es donde podemos
colocar un if preguntando por $?, de modo que interceptemos el error y avisemos al usuario de
que dicho fichero no ha podido ser borrado.