Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Programacion en Shell
Programacion en Shell
Este tutorial es una introducción al uso del lenguaje del intérprete de órdenes de UNIX como lenguaje
de programación sencillo para automatizar tareas y hacer pequeños programas. No es en absoluto una
guía completa de programación, pero presenta todas las características importantes y da una idea de la
potencia del lenguaje
1. Introducción
2. ¿Qué es un guión (script)?
● 2.1 Comentarios
● 2.2 Redirecciones
● 2.3 El truco del #!
3. Variables
● 3.1 Referenciar variables
● 3.2 ¿Cómo funciona el analizador del intérprete?
● 3.3 Dos órdenes útiles: echo y read
● 3.4 Parámetros
● 3.5 Valores devueltos. Orden exit
● 3.6 Asignación de caracteres especiales
4. Comillas
● 4.1 Tres tipos de comillas
● 4.2 Separadores de parámetros
5. Condiciones
● 5.1 La orden test
7. Miscelánea
● 7.1 Variables útiles de entorno
● 7.2 Ejecución en el mismo inérprete (shell)
● 7.3 Referirse a variables
● 7.4 La orden eval
8. Colofón
Página siguiente Página anterior Índice general
1. Introducción
Lo primero es lo primero: no soy un experto (ni mucho menos) en la programación en shell, pero hace un
tiempo me interese por ella e intenté aprender lo que pude de un libro y unos apuntes. El libro es ``UNIX
shell programming'', de Stephen G. Kochan y Patrick H. Wood, de Hayden books UNIX system library
(que a su vez pertenece a Prentice-Hall), ISBN 0-672-48448-X. Es necesario que consigas ese libro (que
por otro lado es absolutamente genial) u otro si realmente necesitas saber programar bien en shell. Esto
más que nada es una introducción a la programación, para hacer algunos programas sencillos y para
hacerte una idea de cómo es programar en shell.
La programación en shell se basa en el uso de las herramientas del sistema, y el UNIX es un sistema
operativo (UNIX y sus clones) que cuenta con bastantes herramientas de proceso y filtrado de textos y de
control de procesos, entre otras. Por ello, permite automatizar procesos repetitivos, que hechos a mano
serían engorrosos y lentos.
Para poder utilizar este tutorial hay que tener acceso a un intérprete compatible con el sh, como el propio
sh, el Korn Shell o el bash de Linux. También hay que tener una cierta soltura con sistemas UNIX
(saber lo que son los permisos de los ficheros, conocer las órdenes básicas, etc.), y tener cierta idea de las
cosas que ofrece el intérprete (sustitución de caracteres especiales por nombres de ficheros, redirecciones
de las entradas y salidas, etc.)
Para poder utilizar los conocimientos que pretende facilitar este tutorial, hay que conocer el mayor
número posible de herramientas del sistema, como pueden ser el grep, el wc, sort, tr, sed, cut y
awk (el awk es casi un lenguaje de programación aparte, aunque puede servir perfectamente como
complemento para la programación en shell. Hay un tutorial de awk muy bueno en algún sitio de INet, a
donde tengo un enlace en la página de UNIX).
Si estás acostumbrado o al menos conoces el MS-DOS, puedes pensar que la programación en shell es
algo parecido a los ficheros BAT del primero, aunque la diferencia entre ellos es más o menos igual de
grande que la existente entre los sistemas UNIX y el MS-DOS (por versatilidad, por potencia, por diseño
...).
También sería bastante bueno, en realidad más para manejar las herramientas del sistema que para la
programación en shell en sí, que tuvieras conocimiento de las expresiones regulares (hay una pequeña
lista en mi página de trucos de UNIX).
Y tú te preguntarás: ¿Por qué programar en shell y no en un lenguaje de alto nivel? Pues depende de para
qué lo utilices. Lo mejor es saber las dos cosas y aplicarlas cuando sean más adecuadas. La programación
en shell es muy útil y cómoda para crear programas fácilmente modificables, pequeños, que resuelvan
tareas repetitivas, como por ejemplo comprobar ciertas condiciones cada cierto tiempo (ejecutándolos en
segundo plano, claro), o para ejecutar órdenes muy largas con frecuencia (para no tener que escribir una
orden larga o varias órdenes seguidas todo el tiempo)... Además, si el programa no requiere mucha
potencia pero si comodidad a la hora de manejar ristras y procesar y filtrar texto, es mucho más fácil
programar en shell, que, por ejemplo, en C.
2.1 Comentarios
Como el resto de los lenguajes de programación (al menos todos los que he visto yo), hay un carácter o
unos caracteres especiales que denotan comentario. Ese texto es ignorado por el intérprete al leer el
guión, y sólo sirven para que una persona, cuando edite el guión, pueda entender cómo funciona. Este
carácter especial es la almohadilla `#', y denota que desde ahí hasta el final de la línea, el resto de los
caracteres son un comentario del programador (como el `//' de C++).
2.2 Redirecciones
Al igual que si fuera un ejecutable cualquiera, un guión permite redireccionamientos de la entrada y de
las salidas. No ocurre lo mismo con los ficheros BAT de MS-DOS, por razones obvias (el MS-DOS está
mal hecho). Las redirecciones de la entrada, salida estándar, errores estándar, y todas las salidas se
realizan con los caracteres <, >, 2> y &> (depende del intérprete, así son las del bash), como estoy
suponiendo que ya sabes. Algo que puede que no sepas es que existe un fichero-dispositivo en UNIX,
estándar a todos los clones (en Linux está), que es el /dev/null. Cualquier cosa que es escriba en ese
dispositivo virtual va a parar a un agujero negro, es decir, se ignora completamente. ¿Y para qué sirve
esto?, puede que te preguntes. Pues si quieres comprobar si un programa devuelve un error, pero no
quieres que la salida estándar aparezca en pantalla (porque al usuario no le interesa, por ejemplo),
entonces redireccionas la salida total (con &>) a /dev/null.
#!/bin/bash
En general, debe empezar con los caracteres #!, seguidos de la ruta completa al intérprete que desees
utilizar para ese guión. Si no utilizas características específicas de bash, deberías poner #!/bin/sh.
¿Y cómo sabes qué intérprete quieres utilizar? Por lo general, bastará con /bin/sh. Si utilizas
características específicas del intérprete Korn o del bash, entonces deberías utilizar /bin/ksh o
/bin/bash respectivamente. Si el intérprete fuera del lenguaje Perl, por ejemplo (nada que ver con
este tutorial), querrías que se ejecutara el programa perl y que recibiera como parámetro el fichero en el
que has escrito esa línea mágica. Entonces, para hallar la ruta completa al programa perl, escribes
which perl, y el sistema te responderá algo como /usr/bin/perl o /usr/local/bin/perl.
Tú pones la línea que te devuelva y listo.
3. Variables
Como en los lenguajes de programación corrientes, en la programación en shell existen las variables, que
son nombres simbólicos para zonas de memoria que almacenan datos que nos interesan. Pero al contrario
que los lenguajes de alto nivel normales, las variables de los guiones no tienen tipo, o quizás sería más
apropiado decir que tienen un tipo único y permanente: a todos los efectos se tratan como ristras de
caracteres.
Para poder hacer sumas y restas hay una orden especial, la orden expr. Esta orden tiene que recibir cada
número u operando como un parámetro independiente, así que devolverá un error si intentas hacer algo
como expr 5+7. No vale la pena extenderse aquí, aunque si quieres más información sobre esta
instrucción o cualquier otra característica de la programación en shell, ya sabes adonde acudir.
y=hola
x=y
Nos encontraremos con la desagradable sorpresa de que el valor de x es el carácter y, y no los caracteres
hola, como quizás pretendiéramos. Para hacer la asignación correctamente, tendríamos que haber
escrito:
y=hola
x=$y
Como en todas o al menos la mayoría de las cosas en UNIX, los nombres son case sensitive, es decir, que
no es lo mismo y que Y (¡aviso para los programadores acostumbrados al MS-DOS!).
demasiadas palabras
3.4 Parámetros
Los parámetros son variables normales, que tienen los nombres $1, $2 ... $9. Aunque se pueden dar más
de nueve parámetros a un guión para el intérprete de órdenes, sólo se puede acceder de forma directa a
los nueve primeros. La orden shift permite desplazar los parámetros de sitio, de tal forma que sean
accesibles los que estén más allá del noveno, con el inconveniente de no poder acceder a los primeros. En
realidad, en el Korn shell (y en bash) se puede acceder al resto de los parámetros directamente con la
construcción ${número}. El funcionamiento de shift es el siguiente:
Supongamos que tenemos como parámetros $1=-o, $2=foo y bar, por llamar al guión (suponiendo
que el nombre del guión es compila) así:
shift 2
echo $1
Y suponiendo la llamada anterior, el resultado por pantalla sería bar.
Las variables $#, $*, $0 nos permiten saber el número de parámetros pasados al guión, la ristra entera
de todos los parámetros pasados, y el nombre del programa que se ha llamado. Esto último puede parecer
estúpido, pero piensa que en UNIX pueden hacerse enlaces a ficheros. Si utilizas Linux, cuando llamas al
vi en realidad estás ejecutando el fichero ejecutable vim. Fíjate cuando hagas un ls -l en tu
directorio /usr/bin. Tendrá que salir un fichero parecido a este:
Esto nos sirve para dos cosas: primero, para saber por qué preguntar cuando chequeemos el valor
devuelto por un programa. Y segundo, para devolver los valores establecidos por convenio cuando
salgamos de nuestros programas (ya sea hechos con lenguajes de alto nivel o guiones).
La forma de devolver valores al salir del programa es darlos como parámetro a la orden exit. Si lo
hacemos sin parámetros, se devolverá un cero.
cd /
asterisco=*.txt
cd /home/zoso/cambio
ls $asterisco
Si ejecutamos el siguiente código, la salida de éste será la lista de ficheros que concuerdan con la
expresión regular almacenada en la variable asterisco, pero los que se encuentren en el directorio
/home/zoso/cambio, no los que se encuentren en el directorio raíz.
4. Comillas
El uso de las comillas en la programación en shell es un tema bastante importante. En los lenguajes de
alto nivel normalmente las comillas sólo se utilizan para las ristras, y no necesitan un aprendizaje
especial para usarlas: simplemente saber (en el lenguaje C, por ejemplo) que las comillas simples son
caracteres y que las comillas dobles requieren reservar memoria, porque son ristras, o sea, punteros a
caracteres.
x=100
y='500$x'
echo $y # Esto enseñará `500$x' en pantalla
y="500$x"
echo $y # Esto enseñará `500100' en pantalla
Las que quedan son las comillas invertidas (`). Estas se utilizan con órdenes, y se sustituyen por la salida
del programa que está entre las comillas invertidas. Por ejemplo, si queremos almacenar en una variable
el número de usuarios que hay actualmente en el sistema, podemos hacer
usuarios=`who | wc -l`
Por si queda alguna duda, who dice los usuario que hay en el sistema, poniendo uno en cada línea, y wc
-l cuenta las líneas que tiene el texto que le entre por la entrada estándar, sacando el número resultante a
la salida.
x='10 y 5 espacios'
echo $x # Saldrá en pantalla '10 y 5 espacios'
echo `$x' # Saldrá en pantalla `$x'
echo "$x" # Saldrá en pantalla '10 y 5 espacios'
¿Queda claro? Pues eso.
5. Condiciones
Manejar condiciones es importante en todos los lenguajes de programación: sirven para controlar el flujo del
programa (el título del próximo capítulo), y eso lo es prácticamente todo.
En UNIX, al contrario de lo que podría parecer, el valor entero que representa al valor verdadero booleano es el 0,
y el valor entero que representa al falso es el 1. Esto es así por el convenio antes citado de los valores devueltos por
los programas en UNIX.
Operador Significado
-f Si fichero es un fichero ordinario
-d Si fichero es un directorio
-r Si el proceso tiene permiso de lectura sobre fichero
-w Si el proceso tiene permiso de escritura sobre fichero
-x Si el proceso tiene permiso de ejecución sobre fichero
-s Si fichero tiene una longitud mayor que cero
Operador Significado
= (binario) Si operando1 es igual a operando2
!= (binario) Si operando1 es distinto de operando2
-z (unario) Si operando es una ristra nula
-n (unario) Si operando es una ristra no nula
Operador Significado
-eq operando1 y operando2 son iguales
-ne operando1 y operando2 son distintos
-gt operando1 es mayor estricto que operando2
-ge operando1 es mayor o igual que operando2
-lt operando1 es menor que operando2
-le operando1 es menor o igual que operando2
Expresiones complicadas
Algunas veces vas a necesitar evaluar expresiones complicadas en un guión, como condiciones dobles (con y o con
o), y puede que incluso condiciones triples (más lo dudo).
Este es un tema delicado en la programación de shell: la forma de hacerlo es con los operadores -a y -o (and y or
respectivamente), con lo que una expresión doble puede quedar tal que así (de horrible):
[ -w /home/zoso/.profile -a -n $MAIL ]
¿Feo, verdad? Esto comprueba si el proceso tiene permiso de escritura sobre el fichero /home/zoso/.profile
y si la variable $MAIL tiene algún contenido.
Pues todavía no ha llegado lo peor: Si quisiésemos realizar condiciones dobles, y poner paréntesis, hay un
problema añadido, y es que los paréntesis son caracteres especiales para el intérprete. Entonces, para que no los
reconozca como paréntesis, y que coja los paréntesis literales, como texto, hay que escribirlos como \( y \) (sí,
utilizando el símbolo \ como en C y en otros lenguajes de programación relacionados con el UNIX). Así, una
condición doble quedaría en el lenguaje del intérprete así que ilegible:
6.1 Estructura if
La estructura if tiene una sintaxis algo inusual, porque necesita la palabra then, pero en la línea siguiente a donde
está el if y la condición. Es un if bastante versátil, ya que permite cláusulas elif (else if) y else. La palabra para
indicar el fin de la estructura if es la palabra fi (if al revés).
La cláusula elif, por si no lo sabes, es parecida al else, aunque necesita una condición después. Para comprender
mejor el funcionamiento, veamos la siguiente equivalencia:
if [ condicion ] if [ condicion ]
then then
... ...
else elif [ condicion2 ]
if [ condicion2 ] then
then ...
... fi
fi
fi
Algunas personas, para hacer la sintaxis más clara, o al menos más parecida a otros lenguajes, como Pascal, escriben
el if de la siguiente forma:
if [ condicion ]; then
fi
Para decidir qué ejecutar, la estructura if permite, además de una condición, el nombre de un programa. Lo que se
hará entonces es ejecutar el programa con los parámetros dados, y dar como verdadero (y ejecutar lo que haya entre el
if y el fi o el elif o el else) que el programa devuelva un cero.
En estos casos es muy útil la instrucción nula (:), ya que si lo que queremos es ejecutar un código si el programa va
mal, entonces la única forma de hacerlo es la siguiente:
if orden
then
:
# No hacemos nada
else
codigo
...
fi
case valor
in
expreg1)
...
ultimaorden1;;
expreg2)
...
ultimaorden2;;
...
expregn)
...
ultimaordenn;;
esac
Por ejemplo, si queremos comprobar una respuesta de sí o no (de forma un poco relajada), podemos hacer:
case $resp; in
s*)
echo "Has contestado sí (o algo parecido)"
n*)
echo "Has contestado no (o algo parecido)"
*)
echo "Has contestado alguna otra cosa"
esac
while orden
do
...
done
En orden podemos poner una orden normal y corriente (el bucle se ejecutaría mientras la orden devolviera un cero), o
podríamos también poner una condición, con la orden test (o mejor, con la sintaxis alternativa []), que por otra
parte no deja de ser una orden como otra cualquiera.
Si quisiésemos estar seguros de que la contestación a la pregunta del ejemplo anterior era s o n, podríamos haber
hecho lo siguiente:
for variable
do
...
done
Si tuviéramos un programa que sólo aceptara ficheros u opciones, podríamos discriminar entre unos y otras así:
for par; do
case $par; in
-*)
echo "Opción '$par' (empieza por '-')"
*)
echo "Fichero '$par' (cualquier otra cosa)"
esac
done
until orden
do
...
done
7. Miscelánea
Todo lo que se necesita para empezar a investigar y para despertar tu interés (si a estas alturas la programación
en shell no ha despertado tu interés, dudo que lo haga) ya se ha visto. Lo único que queda son decir algunas
cosas más, y decir que si crees que le falta algo de potencia, que te mires algún libro. Hay algunas cosas que yo
no he querido poner en este documento, como por ejemplo atrapar las señales del sistema, una orden para
manejar los parámetros estilo UNIX (es decir, con - al principio, y pudiendo agrupar -l -F en -lF), y otras
cosas.
Variable Significado
$PS1 Aspecto del apuntador (prompt) principal
$PS2 Aspecto del apuntador (prompt) secundario
$PWD Directorio actual
$HOME Directorio home del usuario que ejecuta el proceso
$USER Nombre de login del usuario que ejecuta el proceso
$$ PID del proceso
$PATH Ruta de búsqueda del usuario actual
El apuntador, como probablemente sabes, es lo que sale a la izquierda de la pantalla cuando escribes cosas. En
una terminal UNIX, probablemente sea `$' en Linux seguramente es algo más complicado.
A lo mejor lo que no sabes es qué diablos es el apuntador secundario. Cuando escribes comillas (de cualquier
tipo) en la línea de órdenes, pero pulsas Enter antes de terminar de escribir lo que tenías entre comillas (es decir,
si abres comillas pero no las cierras), el intérprete enseñará el apuntador secundario en la siguiente línea, y
seguirá poniendo apuntadores secundarios en las líneas subsiguientes hasta que cierres las comillas.
El PID de un proceso es su process id. Es un número que sirve para identificar al proceso (cuando haces un kill
y similares). Este número es evidentemente único para cada proceso, y lo podemos utilizar para crear ficheros en
el directorio /tmp únicos para esa interpretación del guión. Piensa que si tuviéramos a dos usuarios diferentes
ejecutando el mismo guión a la vez, uno de ellos machacaría algún hipotético fichero temporal que pudiera tener
el primero, y se formaría un pollo que no veas.
Algo a tener en cuenta cuando estemos programando apoyándonos en variables de entorno, es que estas
variables no se pueden modificar. Podemos asignarles valores y cambiarán, pero sólo en nuestro programa. Hay
que tener en cuenta que $PWD, que es la variable que controla el directorio actual, no tiene nada que ver con el
núcleo del sistema operativo, es un problema exclusivo del intérprete, que es al fin y al cabo la interfaz para que
el usuario se comunique con el sistema operativo. Así, si escribimos un guión que cambie el directorio actual,
aunque sea mediante la orden cd, al salir del guión del intérprete ni se habrá enterado.
${foo}bar
Porque si pusiéramos simplemente
$foobar
El intérprete creería que nos referimos a una variable no declarada llamada foobar, con lo que esa expresión
sería una ristra vacía.
read nombre
eval $nombre=10
La primera vez que el intérprete lo mirara, la expresión de eval quedaría como `[contenido de la variable
$variable]=10'. La segunda vez, ejecutaría la orden, como quedara, con lo que conseguiríamos asignar 10 a una
variable cuyo nombre ha sido elegido por el usuario.
8. Colofón
Bueno, espero que haya servido para algo todo esto (he perdido demasiado tiempo escribiendo y
escribiendo para que al final no sirva de nada), y todo ese rollo, escríbeme a a2092@correo.dis.ulpgc.es
si quieres preguntar algo, etc, etc. y sobre todo, si hay algún error de cualquier tipo (gramatical,
sintáctico, me expreso como el culo, algún error que hace que cuando ejecutes tu primer guión guiándote
por este documento tu ordenador explote...).