Subsecciones


Programación Shell-Script

Bash (y otros shells) permiten programar scripts:
Script o programa shell:
fichero de texto conteniendo comandos externos e internos, que se ejecutan línea por línea
Para saber más: Más detalles en:
www.ac.usc.es/docencia/ASRI/Tema_3html/node34.html

Ejecución de un script

Los scripts deben empezar por el número mágico #! seguido del programa a usar para interpretar el script: Las forma usuales de ejecutar un script es:

Paso de parámetros

Es posible pasar parámetros a un scripts: los parámetros se recogen en las variables $1 a $9
Variable Uso
$0 el nombre del script
$1 a $9 parámetros del 1 al 9
${10}, ${11},... parámetros por encima del 10
$# número de parámetros
$*, $@ todos los parámetros
Ejemplo:
$ cat parms1.sh
#!/bin/bash
VAL=$((${1:-0} + ${2:-0} + ${3:-0}))
echo $VAL
$ bash parms1.sh 2 3 5
10
$ bash parms1.sh 2 3
5

El comando shift desplaza los parámetros hacia la izquierda el número de posiciones indicado:

$ cat parms2.sh
#!/bin/bash
echo $#
echo $*
echo "$1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11}"
shift 9
echo $1 $2 $3
echo $#
echo $*
$ bash parms2.sh a b c d e f g h i j k l
12
a b c d e f g h i j k l
a b c d e f g h i j k
j k l
3
j k l

Entrada/salida

Es posible leer desde la entrada estándar o desde fichero usando read y redirecciones:
#!/bin/bash
echo -n "Introduce algo: "
read x
echo "Has escrito $x"
echo -n "Escribe 2 palabras: "
read x y
echo "Primera palabra $x; Segunda palabra $y"
Si queremos leer o escribir a un fichero utilizamos redirecciones:
echo $X > fichero
read X < fichero
Este último caso lee la primera línea de fichero y la guarda en la variable X Ejemplo de lectura de fichero
#!/bin/bash
# Usa $IFS para dividir la línea que se está leyendo
# por defecto, la separación es "espacio"
echo "Lista de todos los usuarios:"
OIFS=$IFS # Salva el valor de IFS
IFS=: # /etc/passwd usa ":"para separar los campos
cat /etc/passwd |
while read name passwd uid gid fullname ignore
do
    echo "$name ($fullname)"
done
IFS=$OIFS # Recupera el $IFS original

Redirecciones

Las redirecciones y pipes pueden usarse en otras estructuras de control

Ejemplo: lee las 2 primeras líneas de un fichero

if true
then
    read x
    read y
fi < fichero1
Ejemplo: lee líneas de teclado y guardalas en un fichero temporal convirtiendo minúsculas en mayúsculas
#/bin/bash
read buf
while [ "$buf" ]
do
    echo $buf
    read buf
done | tr 'a-z' 'A-Z' > tmp.$$

Tests

Los comandos que se ejecutan en un shell tienen un código de salida, que se almacena en la variable $? Ejemplo:
$ ls /bin/ls
/bin/ls
$ echo $?
0
$ ls /bin/ll
ls: /bin/ll: Non hai tal ficheiro ou directorio
$ echo $?
1
Podemos chequear la salida de dos comandos mediante los operadores && (AND) y || (OR) Ejemplo con &&:
$ ls /bin/ls && ls /bin/ll
/bin/ls
ls: /bin/ll: Non hai tal ficheiro ou directorio
$ echo $?
1
$ ls /bin/ll && ls /bin/ls
ls: /bin/ll: Non hai tal ficheiro ou directorio
$ echo $?
1
Ejemplo con ||:
$ ls /bin/ls || ls /bin/ll
/bin/ls
$ echo $?
0
$ ls /bin/ll || ls /bin/ls
ls: /bin/ll: Non hai tal ficheiro ou directorio
/bin/ls
$ echo $?
0

Estructura if...then...else

Podemos usar el estado de salida de uno o varios comandos para tomar decisiones:
if comando1
then
    ejecuta otros comandos
elif comando2
then
    ejecuta otros comandos
else
    ejecuta otros comandos
fi
Ejemplo:
$ cat if.sh
#!/bin/bash
if (ls /bin/ls && ls /bin/ll) >/dev/null 2>&1
then
    echo "Encontrados ls y ll"
else
    echo "Falta uno de los ficheros"
fi
$ bash if.sh
Falta uno de los ficheros

Comando test

Notar que if sólo chequea el código de salida de un comando, no puede usarse para comparar valores: para eso se usa el comando test
El comando test permite:
test puede usarse de dos formas:
test expresión
o bien
[ expresión ]3
Si la expresión es correcta test devuelve un código de salida 0, si es falsa, devuelve 1:

Expresiones

Existen expresiones para chequear strings, números o ficheros

Chequeo de strings

Expresión Verdadero sí
string el string es no nulo ("")
-z string la longitud del string es 0
-n string la longitud del string no es 0
string1 = string2 los strings son iguales
string1 != string2 los strings son distintos

Chequeo de enteros

Expresión Verdadero sí
int1 -eq int2 los enteros son iguales
int1 -ne int2 los enteros son distintos
int1 -gt int2 int1 mayor que int2
int1 -ge int2 int1 mayor o igual que int2
int1 -lt int2 int1 menor que int2
int1 -le int2 int1 menor o igual que int2

Chequeo de ficheros

Expresión Verdadero sí
-e file file existe
-r file file existe y es legible
-w file file existe y se puede escribir
-x file file existe y es ejecutable
-f file file existe y es de tipo regular
-d file file existe y es un directorio
-c file file existe y es un dispositivo de caracteres
-b file file existe y es un dispositivo de bloques
-p file file existe y es un pipe
-S file file existe y es un socket
-L file file existe y es un enlace simbólico
-u file file existe y es setuid
-g file file existe y es setgid
-k file file existe y tiene activo el sticky bit
-s file file existe y tiene tamaño mayor que 0

Operadores lógicos con test

Expresión Propósito
! invierte el resultado de una expresión
-a operador AND
-o operador OR
( expr ) agrupación de expresiones; los paréntesis tienen un significado especial para el shell, por lo que hay que escaparlos
Ejemplos:
$ test -f /bin/ls -a -f /bin/ll ; echo $?
1
$ test -c /dev/null ; echo $?
0
$ [ -s /dev/null ] ; echo $?
1
$ [ ! -w /etc/passwd ] && echo "No puedo escribir"
No puedo escribir
$ [ $$ -gt 0 -a \( $$ -lt 5000 -o -w file \) ]

Comando de test extendido

A partir de la versión 2.02 de Bash se introduce el extended test command: [[ expr ]] Ejemplos:
$ [[ -f /bin/ls && -f /bin/ll ]] ; echo $?
1
$ [[ $$ -gt 0 && ($$ -lt 5000 || -w file) ]]

Control de flujo

Además del if bash permite otras estructuras de control de flujo: case, for, while y until

Estructura case

case valor in
    patrón_1)
        comandos si value = patrón_1
        comandos si value = patrón_1 ;;
    patrón_2)
        comandos si value = patrón_2 ;;
    *)
        comandos por defecto ;;
esac
Ejemplo:
#!/bin/bash
echo -n "Respuesta:" read RESPUESTA
case $RESPUESTA in
    S* | s*)
        RESPUESTA="SI";;
    N* | n*)
        RESPUESTA="NO ";;
    *)
        RESPUESTA="PUEDE";;
esac
echo $RESPUESTA

Lazos for

for var in lista
do
    comandos
done
Ejemplo: recorrer una lista
LISTA="10 9 8 7 6 5 4 3 2 1"
for var in $LISTA
do
    echo $var
done
Ejemplo: recorrer los ficheros *.bak de un directorio
dir="/var/tmp"
for file in $dir/*.bak
do
    rm -f $file
done
Sintaxis alternativa, similar a la de C
LIMIT=10
for ((a=1, b=LIMIT; a <= LIMIT; a++, b--))
do
    echo "$a-$b"
done

Bucle while

while comando
do
    comandos
done
Ejemplo:
while [ $1 ]
do
    echo $1
    shift
done

Bucle until

until comando
do
    comandos
done
Ejemplo:
until [ "$1" = ""]
do
    echo $1
    shift
done

break y continue

Permiten salir de un lazo (break) o saltar a la siguiente iteración (continue) Ejemplo con break:
# Imprime el contenido de los ficheros hasta que
# encuentra una línea en blanco
for file in $*
do
    while read buf
    do
        if [ -z "$buf"]
        then
            break 2
        fi
        echo $buf
    done < $file
done
Ejemplo con continue:
# Muestra un fichero pero no las líneas de más
# de 80 caracteres
while read buf
do
    cuenta=`echo $buf | wc -c`
    if [ $cuenta -gt 80 ]
    then
        continue
    fi
    echo $buf
done < $1

Funciones

Podemos definir funciones en un script de shell:
funcion() {
    comandos
}
y para llamarla:
funcion p1 p2 p3
Siempre tenemos que definir la función antes de llamarla:
#!/bin/bash
# Definición de funciones
funcion1() {
    comandos
}
funcion2() {
    comandos
}
# Programa principal
funcion1 p1 p2 p3

Paso de parámetros

La función referencia los parámetros pasados por posición, es decir, $1, $2, ..., y $* para la lista completa:
$ cat funcion1.sh
#!/bin/bash
funcion1()
{
    echo "Parámetros pasados a la función: $*"
    echo "Parámetro 1: $1"
    echo "Parámetro 2: $2"
}
# Programa principal
funcion1 "hola" "que tal estás" adios
$
$ bash funcion1.sh
Parámetros pasados a la función: hola que tal estás adios
Parámetro 1: hola
Parámetro 2: que tal estás

return

Después de llamar a una función, $? tiene el código se salida del último comando ejecutado:
#!/bin/bash
funcion2() {
    if [ -f /bin/ls -a -f /bin/ln ]; then
        return 0
    else
        return 1
    fi
}
# Programa principal
if funcion2; then
    echo "Los dos ficheros existen"
else
    echo "Falta uno de los ficheros - adiós"
    exit 1
fi

Otros comandos

wait

Permite esperar a que un proceso lanzado en background termine
sort $largefile > $newfile &
ejecuta comandos
wait
usa $newfile
Si lanzamos varios procesos en background podemos usar $!
sort $largefile1 > $newfile1 &
SortPID1=$!
sort $largefile2 > $newfile2 &
SortPID2=$!
ejecuta comandos
wait $SortPID1
usa $newfile1
wait $SortPID2
usa $newfile2

trap

Permite atrapar las señales del sistema operativo
$ cat trap.sh
#!/bin/bash
cachado() {
    echo "Me has matado!!!"
    kill -15 $$
}
trap "cachado" 2 3
while true; do
    true
done
$ bash trap.sh
(Ctrl-C)
Me has matado!!!
Terminado
Las señales más comunes para usar con trap son:
Señal Significado
0 salida del shell (por cualquier razón, incluido fin de fichero)
1 colgar
2 interrupción (Ctrl-C)
3 quit
9 kill (no puede ser parada ni ignorada)
15 terminate; señal por defecto generada por kill

exit

Finaliza el script

Referencias indirectas

Permiten definir variables cuyo contenido es el nombre de otra variable:
a=letra
letra=z
# Referencia directa
echo "a = $a"    # a = letra
# Referencia indirecta
eval a=\$$a
echo "Ahora a = $a"    # Ahora a = z
Las versiones de bash a partir de la 2 permiten una forma más simple para las referencias indirectas:
a=letra
letra=z
# Referencia directa
echo "a = $a"    # a = letra
# Referencia indirecta
echo "Ahora a = ${!a}"    # Ahora a = z
Otro ejemplo con eval
$ cat dni.sh
#!/bin/bash
dniPepe=23456789
dniPaco=98765431
echo -n "Nombre: "; read nombre
eval echo "DNI = \$dni${nombre}"
$ bash dni.sh
Nombre: Pepe
DNI = 23456789

Optimización de scripts

El shell no es especialmente eficiente a la hora de ejecutar trabajos pesados

Depuración

Para depurar un script de shell podemos usar la opción -x o -o xtrace de bash: Es posible depurar sólo parte de un script:

Administración de Sistemas e Redes <ASR.USC[at]gmail.com>
Tomás Fernández Pena <tf.pena[at]usc.es>
Última actualización: 30-09-15 17:44 por tomas

Creative Commons License
Curso de Administración de Sistemas y Redes por Tomás Fernández Pena se distribuye bajo la licencia Creative Commons Recoñecemento-Compartir baixo a mesma licenza. 3.0 España.
Trabajo original en persoal.citius.usc.es/tf.pena/ASR.