25/6/2020 Programación Shell-Script
Siguiente: Manejo de ficheros de Subir: Programación de scripts de Anterior: Programación de scripts de
Índice de Materias
Subsecciones
Ejecución de un script
Paso de parámetros
Entrada/salida
Redirecciones
Tests
Estructura if...then...else
Comando test
Expresiones
Chequeo de strings
Chequeo de enteros
Chequeo de ficheros
Operadores lógicos con test
Comando de test extendido
Control de flujo
Estructura case
Lazos for
Bucle while
Bucle until
break y continue
Funciones
Paso de parámetros
return
Otros comandos
wait
trap
exit
Referencias indirectas
Optimización de scripts
Depuración
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
El programa puede contener, además de comandos
1. variables
2. constructores lógicos (if...then, AND, OR, etc.) y lazos (while, for, etc.)
3. funciones
4. comentarios
Para saber más:
[Link]/[Link]/ASR/Tema_2html/[Link] Ú 1/15
25/6/2020 Programación Shell-Script
Advanced Bash-Scripting Guide, Mendel Cooper, Última revisión Mayo 2005,
[Link]/[Link]
The Deep, Dark Secrets of Bash, Ben Okopnik, Linux Gazette,
[Link]/articles/Shell_Scripting-[Link]
Introduction to Shell Scripting, Ben Okopnik, [Link]/[Link]
Más detalles en:
[Link]/docencia/ASRI/Tema_3html/[Link]
Ejecución de un script
Los scripts deben empezar por el número mágico #! seguido del programa a usar para interpretar
el script:
#!/bin/bash script de bash
#!/bin/sh script de shell
#!/usr/bin/perl script de perl
Las forma usuales de ejecutar un script es:
darle permiso de ejecución al fichero y ejecutarlo como un comando:
$ chmod +x helloworld
./helloworld
ejecutar una shell poniendo como argumento el nombre del script (sólo necesita permiso de lectura)
$ bash helloworld
ejecutarlo en la shell actual
$ . helloworld
o bien:
$ source helloworld
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 [Link]
#!/bin/bash
VAL=$((${1:-0} + ${2:-0} + ${3:-0}))
echo $VAL
$ bash [Link] 2 3 5
10
$ bash [Link] 2 3
5
[Link]/[Link]/ASR/Tema_2html/[Link] 2/15
25/6/2020 Programación Shell-Script
El comando shift desplaza los parámetros hacia la izquierda el número de posiciones indicado:
$ cat [Link]
#!/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 [Link] 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
Si queremos leer un fichero línea a línea podemos usar while:
#!/bin/bash
# FILE: linelist
# Usar: linelist filein fileout
# Lee el fichero pasado en filein y
# lo salva en fileout con las lineas numeradas
count=0
while read BUFFER
do
count=$((++count))
echo "$count $BUFFER"» $2
done < $1
el fichero de entrada se va leyendo línea a línea y almacenando en BUFFER
count cuenta las líneas que se van leyendo
El uso de lazos para leer ficheros es bastante ineficiente
deberían evitarse (por ejemplo, usar cat fichero)
Ejemplo de lectura de fichero
[Link]/[Link]/ASR/Tema_2html/[Link] 3/15
25/6/2020 Programación Shell-Script
#!/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
El fichero /etc/passwd se lee línea a línea
para cada línea, sus campos se almacenan en las variables que siguen a read
la separación entre campos la determina la variable $IFS (por defecto, espacio en blanco)
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 $?
si $? es 0 el comando terminó bien
si $? es > 0 el comando terminó mal
Ejemplo:
$ ls /bin/ls
/bin/ls
$ echo $?
0
$ ls /bin/ll
[Link]/[Link]/ASR/Tema_2html/[Link] 4/15
25/6/2020 Programación Shell-Script
ls: /bin/ll: Non hai tal ficheiro ou directorio
$ echo $?
1
Podemos chequear la salida de dos comandos mediante los operadores && (AND) y || (OR)
estos operadores actúan en cortocircuito:
comando1 && comando2
comando2 sólo se ejecuta si comando1 acaba bien
comando1 || comando2
comando2 sólo se ejecuta si comando1 falla
comandos true y false: devuelven 0 y 1, respectivamente
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
debe respetarse la colocación de los then, else y fi
también puede escribirse if comando1 ; then
el elif y el else son opcionales, no así el fi
[Link]/[Link]/ASR/Tema_2html/[Link] 5/15
25/6/2020 Programación Shell-Script
Ejemplo:
$ cat [Link]
#!/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 [Link]
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:
chequear la longitud de un string
comparar dos strings o dos números
chequear el tipo de un fichero
chequear los permisos de un fichero
combinar condiciones juntas
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:
este código puede usarse para tomar decisiones:
if [ "$1" = "hola" ]
then
echo "Hola a ti también"
else
echo "No te digo hola"
fi
if [ $2 ]
then
echo "El segundo parámetro es $2"
else
echo "No hay segundo parámetro"
fi
en el segundo if la expresión es correcta si $2 tiene algún valor; falsa si la variable no está definida o
contiene null ("")
Expresiones
Existen expresiones para chequear strings, números o ficheros
Chequeo de strings
[Link]/[Link]/ASR/Tema_2html/[Link] 6/15
25/6/2020 Programación Shell-Script
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
[Link]/[Link]/ASR/Tema_2html/[Link] 7/15
25/6/2020 Programación Shell-Script
$ [ ! -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 ]]
permite realizar comparaciones de un modo similar al de lenguajes estándar:
permite usar los operadores && y || para unir expresiones
no necesita escapar los paréntesis
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
si valor no coincide con ningún patrón se ejecutan los comandos después del *)
esta entrada es opcional
patrón puede incluir comodines y usar el símbolo | como operador OR
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
[Link]/[Link]/ASR/Tema_2html/[Link] 8/15
25/6/2020 Programación Shell-Script
for var in lista
do
comandos
done
var toma los valores de la lista
puede usarse globbing para recorrer los ficheros
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
se ejecuta mientras que el código de salida de comando sea cierto
Ejemplo:
while [ $1 ]
do
echo $1
shift
done
Bucle until
until comando
do
comandos
done
[Link]/[Link]/ASR/Tema_2html/[Link] 9/15
25/6/2020 Programación Shell-Script
se ejecuta hasta que el código de salida de comando sea hace cierto
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)
break permite especificar el número de lazos de los que queremos salir (break n)
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:
[Link]/[Link]/ASR/Tema_2html/[Link] 10/15
25/6/2020 Programación Shell-Script
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 [Link]
#!/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 [Link]
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:
podemos ponerlo de forma explícita usando return
#!/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"
[Link]/[Link]/ASR/Tema_2html/[Link] 11/15
25/6/2020 Programación Shell-Script
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 $!
$! devuelve el PID del último proceso lanzado
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
permite hacer que el programa termine limpiamente (p.e. borrando ficheros temporales, etc.) aún en el
evento de un error
$ cat [Link]
#!/bin/bash
cachado() {
echo "Me has matado!!!"
kill -15 $$
}
trap "cachado" 2 3
while true; do
true
done
$ bash [Link]
(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)
[Link]/[Link]/ASR/Tema_2html/[Link] 12/15
25/6/2020 Programación Shell-Script
3 quit
9 kill (no puede ser parada ni ignorada)
15 terminate; señal por defecto generada por kill
exit
Finaliza el script
se le puede dar un argumento numérico que toma como estado de salida, p.e. exit 0 si el script acaba
bien y exit 1 en caso contrario
si no se usa exit, el estado de salida del script es el del último comando ejecutado
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 [Link]
#!/bin/bash
dniPepe=23456789
dniPaco=98765431
echo -n "Nombre: "; read nombre
eval echo "DNI = \$dni${nombre}"
$ bash [Link]
Nombre: Pepe
DNI = 23456789
Optimización de scripts
El shell no es especialmente eficiente a la hora de ejecutar trabajos pesados
Ejemplo: script que cuenta las líneas de un fichero:
$ cat [Link]
#!/bin/bash
count=0
while read line
[Link]/[Link]/ASR/Tema_2html/[Link] 13/15
25/6/2020 Programación Shell-Script
do
count=$(expr $count + 1)
done < $1
echo "El fichero $1 tiene $count líneas"
si medimos el tiempo que tarda
$ time bash [Link] [Link]
El fichero [Link] tiene 36855 líneas
real 0m59.757s
user 0m17.868s
sys 0m41.462s
Podemos mejorarlo si usamos aritmética de shell en vez de el comando expr
$ cat [Link]
#!/bin/bash
count=0
while read line
do
count=$(($count+1))
done < $1
echo "El fichero $1 tiene $count líneas"
el tiempo ahora
$ time bash [Link] [Link]
El fichero [Link] tiene 36855 líneas
real 0m1.014s
user 0m0.887s
sys 0m0.108s
Y todavía mejor:
$ cat [Link]
#!/bin/bash
count=$(wc -l $1 | cut -d " " -f 1)
echo "El fichero $1 tiene $count líneas"
$
$ time bash [Link] [Link]
El fichero [Link] tiene 36855 líneas
real 0m0.096s
user 0m0.005s
sys 0m0.009s
Conclusiones
Intenta reducir el número de procesos creados al ejecutar el script, por ejemplo, usando las
funciones aritméticas del shell
Siempre que sea posible, intenta usar comandos del shell (wc, tr, grep, sed, etc.) en vez de lazos
Depuración
Para depurar un script de shell podemos usar la opción -x o -o xtrace de bash:
muestra en la salida estándar trazas de cada comando y sus argumentos, después de que el comando se
haya expandido pero antes de que se sea ejecutado
$ bash -x [Link] [Link]
++ wc -l [Link]
++ cut -d ' ' -f 1
+ count=36855
+ echo 'El fichero [Link] tiene 36855 líneas'
El fichero [Link] tiene 36855 líneas
Es posible depurar sólo parte de un script:
[Link]/[Link]/ASR/Tema_2html/[Link] 14/15
25/6/2020 Programación Shell-Script
poner set -x o set -xv al inicio del trozo a depurar
set +x o set +xv para cancelar
$ cat [Link]
#!/bin/bash
set -x
count=$(wc -l $1 | cut -d " "-f 1)
set +x
echo "El fichero $1 tiene $count líneas"
$
$ bash [Link] [Link]
++ wc -l [Link]
++ cut -d ' '-f 1
+ count=36855
+ set +x
El fichero [Link] tiene 36855 líneas
Siguiente: Manejo de ficheros de Subir: Programación de scripts de Anterior: Programación de scripts de
Índice de Materias
Administración de Sistemas e Redes <[Link][at][Link]>
Tomás Fernández Pena <[Link][at][Link]>
Última actualización: 30-09-15 17:44 por tomas
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 [Link]/[Link]/ASR.
[Link]/[Link]/ASR/Tema_2html/[Link] 15/15