Esta página puede ser redistribuida libremente bajo los términos de la licencia GPL. Vease ( GPL texto original ) o si lo prefiere (Traducción española no oficial de la GPL) Al margen de las obligaciones legales que se derivan del uso de esta licencia rogamos sea respetada la referencia a su lugar de publicación original www.ciberdroide.com. y a su autor original Antonio Castro Snurmacher (Madrid 01/01/2000).

Ausencia de Garantía

Esta ausencia de garantía se hace extensa a cualquier tipo de uso de este material y muy especialmente a las prácticas, ejercicios, y de ejemplos que encuentre en estas páginas. Deberá trabajar siempre salvo indicación contraria con un SO Linux y con un usario distinto de 'root' sin privilegios especiales. Como directorio de trabajo se procurará usar el directorio '/tmp' o algún otro que no contenga información valiosa. Tampoco se considera buena idea practicar en una máquina que contenga información valiosa.

Todo esto son recomendaciones de prudencia. En cualquier caso si algo sale mal toda la responsabilidad será únicamente suya. En ningún caso podrá reclamar a nadie por daños y perjuicios derivados del uso de este material. Para más información lea el contenido de la licencia GPL y abstengase de hacer prácticas si no está dispuesto a asumir toda la responsabilidad.

PROGRAMACION SHELL-SCRIPT (Primera parte)

Que es un shell-script
Ya vimos hace algunas lecciones una introducción al interprete de comandos de Linux (shell). Vamos a ampliar nuestros conocimientos sobre la shell y si bien antes consideramos a la shell como un interprete de comandos en esta lección veremos que es mucho más que eso. La shell de Linux que vamos a usar es la Bash que como ya dijimos es un superconjunto de la Bourne-Shell. Sin lugar a dudas esta lección le permitirá dar un salto enorme en el aprovechamiento de su SO Linux.

Con esta lección y las siguientes nos acercamos al momento en que usted podrá presumir ante algunos de sus amigos de haber realizado en su flamante SO cosas que ellos no saben hacer con Güindos, aunque ellos podrían presumir de su habilidad cerrando las ventanas del General Failiure y del Doctor Guasón.

El lenguaje shell-script es muy versátil aunque hay que advertir que es bastante ineficiente. Son mucho más eficientes los programas escritos en lenguaje C. Este es el lenguaje en el que se escribió el kernel de Linux y otros muchos SO. El Bash no es tan eficiente como el C. El valor del lenguaje shell-script es que permite hacer cosas complicadas con muy poco esfuerzo en perfecta combinación con una serie de comandos también muy potentes disponibles en Linux . Verdad que suena interesante ? Algunas partes del SO que no pertenecen al kernel están escritas en shell-script. Por ejemplo muchos comandos que se van ejecutando en secuencia mientras el sistema arranca son programas realizados en shell-script así que la ineficiencia no es ningún obstáculo para ciertas tareas. Por el contrario para un administrador tener ciertos programas del sistema en shell-script le permite retocarlos a su gusto con gran facilidad.

Después de hacer login en el sistema obtendremos una sesión con el interprete de comandos asociado a nuestro usuario. (Esta asociación figura en el fichero '/etc/passwd' junto quizás con la clave encriptada y otras cosas). Decimos quizás porque la clave no siempre es guardada en este fichero. La razón es que pese a estar encriptada /etc/passwd es un fichero que cualquiera puede leer y resulta más seguro otro sistema llamado shadow. Que /etc/passwd sea legible nos permite consultar entre otras cosas cual es nuestra shell en el inicio de una sesión. Desde esta sesión podemos ir introduciendo ordenes desde el teclado pero nuestro interprete de comandos no deja de ser a su vez un programa normal como cualquier otro, así que si tuviéramos que teclear varias cosas podríamos guardar estas ordenes en un fichero y redirigir la entrada estándar de la shell para que tome la entrada desde el fichero en lugar de hacerlo desde el teclado.

Vamos a ver un ejemplo para mostrar fecha y hora en letras grandes. Teclee el comando siguiente:

$ banner `date '+%D %T'`

Ahora vamos a practicar repasando un poco las nociones que ya vimos en el primer capítulo dedicado al shell script porque son conceptos que conviene asentar. Para ello vamos a guardar este comando en un fichero para poder ejecutarlo cuando nos apetezca. Previamente nos situamos en el directorio '/tmp' como es costumbre y nos aseguramos de no estar usando el usuario root ni ningún otro usuario con privilegios especiales.

$ # Nos situamos en /tmp
$ cd /tmp
$ # Generamos el fichero con el comando
$ echo "banner `date '+%D %T'`" > fichero_ordenes
$ # Comprobamos el contenido del fichero
$ cat fichero_ordenes

banner 08/02/00 20:26:30

!! Vaya !! Esto no es lo que queríamos hacer. Este comando siempre mostraría la misma fecha y hora y seguramente no pretendíamos eso. Dado que hemos usado las dobles comillas se ha producido la expansión de la línea de ordenes concretamente el operador grave. Si en lugar de las dobles comillas usáramos las comillas simples no se habría producido ese error pero existen ya comillas simples dentro del propio comando y la shell tomaría el cierre de la comilla simple en un lugar que no es el que nosotros deseábamos. Todo esto lo comentamos a modo de repaso. Entonces como tendríamos que haber generado este comando ? Aquí es donde el listillo dice que usando un editor de texto. Bueno al fin de cuentas ya sabemos usar el 'vi' no ? Bueno pues vamos primero a hacerlo sin editor. Recuerda que mencionamos la posibilidad de escapar un solo carácter con el carácter '\' ? Intentemos de nuevo.

$ # Generamos el fichero con el comando
$ echo "banner \`date '+%D %T'\`" > fichero_ordenes
$ # Comprobamos el contenido del fichero
$ cat fichero_ordenes

banner `date '+%D %T'`

Ahora si. Vamos a usar la shell de la misma forma que haríamos con un programa normal redirigiendo la entrada estándar desde este fichero. Por lo tanto el fichero tiene tratamiento de datos y solo se requiere permiso de lectura para ejecutarlo de esta forma.

$ # Ejecutamos
$ bash < fichero_ordenes
$ # También podemos ejecutarlo de otra forma
$ cat fichero_ordenes | bash

Para ejecutarlo sin redirigir entrada salida podemos pasar como argumento el nombre del fichero. Nuevamente solo hará falta permiso de lectura.

$ # Ejecutar pasando el nombre del fichero como argumento
$ sh fichero_ordenes
$FIN

Ejecución de un shell-script directamente como un comando
Para ejecutarlo directamente como un comando necesitamos darle permiso de ejecución.

$ # Ejecutar pasando el nombre del fichero como argumento
$ chmod +x fichero_ordenes
$ ./fichero_ordenes
$FIN

Lo del './' antes del ejecutable resultará necesario si el directorio donde se encuentra el ejecutable no figura en la variable '$PATH' En realidad ocurre algo muy similar al caso anterior porque también se arranca una sub-shell que toma como entrada el fichero.

Esto ocurre cuando la shell detecta que el ejecutable está en formato de texto. Si se tratara de código binario lo ejecutaría sin arrancar una sub-shell. Para averiguar que tipo de fichero se trata, la shell mirará los primeros cráteres que contiene. Los ficheros ejecutables suelen tener un par de bytes iniciales que servirán para identificar el tipo de fichero. En caso contrario se asume que es un shell-srcip.

A estos dos caracteres iniciales juntos forman lo que se denomina número mágico. Es solo una cuestión de nomenclatura. El SO lo ve como un número entero de dos bytes y nosotros desde un editor lo vemos como dos caracteres.

Existe un programa llamado 'file' que utiliza esta información así como la presencia de ciertos patrones para identificar el tipo de un fichero.

Se usa el término script para ficheros que son legibles y ejecutables. Es decir el propio fuente del programa es ejecutable. Fuente de un programa es lo que escribe el programador. El lenguaje C no es un lenguaje tipo script ya que el fuente no es ejecutable. Hace falta un compilador que traduce el código fuente a código propio de la máquina. Los Script no se ejecutan directamente en la máquina sino dentro de un programa que va interpretando las instrucciones. Bueno en general esto es más o menos así.

Hay muchos tipos de scripts. La shell tiene su propio lenguaje. Existe un lenguaje distinto llamado 'perl' que también se ejecuta como un script. 'perl' es un lenguaje inspirado en el uso combinado del interprete de comandos y algunos comandos clásicos de los SO tipo Unix, y además incorpora cosas de otros lenguajes como el 'C' y otras cosas totalmente originales. 'perl' no es fácil de aprender pero es un lenguaje de script muy potente. Un script de 'perl' se puede ejecutar mediante el comando 'perl prueba.pl'. El fichero 'prueba.pl' deberá estar en lenguaje 'perl'. Si ejecutáramos directamente './prueba.pl' desde bash, el interprete de comandos intentará ejecutarlo como si fuera un script de bash dando errores de sintaxis. Para evitar esto se puede poner totalmente al principio del fichero los caracteres '#!' Estos caracteres serán vistos como un número mágico que avisa que a continuación viene el programa a usar con ese script. Para el script de perl debería haber empezado con '#!/usr/bin/perl' para poderlo ejecutar directamente sin problema. Para un script de bash no es necesario poner nada pero podríamos haber puesto '#!/bin/bash', o '#!/bin/sh'. De esta forma también se puede indicar cual es la shell que deseamos usar ya que podríamos desear usar otras shells como '#!/usr/bin/csh' para la C-shell o '#!/usr/bin/ksh' para la Korn shell. A nosotros con el bash nos basta y sobra.

Ejecución con la shell-actual
Hasta este momento siempre hemos arrancado una sub-shell que leía las ordenes del fichero, las ejecutaba y después terminaba y moría cediendo el control nuevamente a la shell original que arrancó la sub-shell.

Existe una forma de decirle a la shell actual que lea y ejecute una serie de ordenes por si misma sin arrancar una sub-shell. Para ello hay que anteponer un punto y un blanco al nombre del comando. Nuevamente solo hará falta permiso de lectura.

$ # Ejecutar ordenes de un fichero desde la shell actual
$ . ./fichero_ordenes

En este caso no se ha ejecutado mediante una sub-shell pero no se aprecia ninguna diferencia. Esto no siempre es así. Veamos un ejemplo en el que se aprecie la diferencia. $$ tomará el valor del pid de la shell en ejecución y $PPID tomará el valor del pid del proceso padre.

$echo echo \$PPID \$$ > fichero_ordenes
$ bash fichero_ordenes

213 2396

$ . ./fichero_ordenes

1 213

Evidentemente cuando pruebe este ejemplo obtendrá un pid distinto de 213 pero lo interesante es ver como ambas ejecuciones establecen claramente que la primera fue efectuada por un proceso hijo de la shell que se ejecuta en segundo lugar usando el punto, seguido del espacio y del 'fichero_ordenes'.

Los procesos hijos heredan el entorno (variables y otras cosas) desde el proceso padre por lo cual un padre si puede comunicarse con su hijo de esa forma. Por el contrario un proceso hijo (sub-shell en nuestro caso) jamás puede comunicarse de esa forma con su padre. Resulta imposible que un proceso hijo comunique valores de variables del entorno a su padre. Si deseamos modificar variables del entorno ejecutando un shell-script no podremos hacerlo nunca si la ejecutamos desde dentro de una sub-shell. Por el contrario deberemos usar algo del tipo '. ./fichero_ordenes' como acabamos de ver.

Existe un fichero en el directorio inicial de cada usuario que consiste en una serie de ordenes iniciales para shell. En el caso de la Bash este fichero es '~/.bash_profile'. Podemos ejecutarlo si queremos pero este tipo de ficheros incluyen ordenes para inicializar variables entre otras cosas, así que lo adecuado sería ejecutarlo desde la shell actual. Haga lo siguiente:

$ cd
$ . ./.bash_profile

Observe que hay tres puntos en el último comando con tres significados distintos. El primero es para ejecutar con la shell-actual, el segundo es para indicar el directorio actual, y el último forma parte del nombre y se usa para que no sea tan visible dado que un punto al principio del nombre de un fichero no será expandido por la shell cuando usemos '*'.

Vamos a comprobar la acción del número mágico. Pase a un directorio para practicar sus ejercicios, y edite un nuevo fichero que debe llamarse 'aviso'.

echo "Error este shell-script debió arrancarse con . /comando" 

Ahora edite otro nuevo fichero que llamaremos 'comando'.

#!/bin/bash /tmp/aviso
echo "COMANDO"

Vamos a ejecutar nuestro shell-script de dos formas distintas y observamos que el resultado no es el mismo. En el segundo caso se ejecuta el script 'aviso'. Este último ejemplo no es completo ya que '/bin/bash /tmp/aviso' no actuará interpretando sino de una forma independiente del texto y mostrará siempre un mensaje de aviso.

$ # (1) Se ejecuta usando una sub-shell pero en este
$ # caso se pasa un argumento a la sub-shell y por ello
$ # no se ejecuta el código del fichero comando.
$ chmod +x aviso
$ ./comando

Error este shell-script debió arrancarse con . /comando

$ # (2) Ahora ejecutamos con la shell actual

$ . ./comando

COMANDO

La explicación es la siguiente. El número mágico indica con que shell se ejecutará el script. Nosotros hemos puesto en lugar una shell sin argumetos que ejecutaría ese texto una shell que recibe un argumento. Eso hace que se ejecute el comando pasado como argumento en lugar de tomar el texto del fichero que contiene el número mágico. Cuando ejecutamos con la shell actual la información siguiente al número mágico no será tenida en cuenta porque supondría arrancar una sub-shell.

Es un ejemplo rebuscado pero hay que intentar comprender lo que sucede en distintas situaciones porque el lenguaje shell-script tiene sus rarezas.

Ejecutar en una subshell usando paréntesis
Ya que estamos con el tema de la subshell vamos a comentar como funciona el uso de los paréntesis desde la linea de órdenes. No es necesario realizar un script para ejecutar algo en una subshell (lista) Ejecuta una lista de ordenes en una subshell.

$ var1=888
$ ( var1=111; echo $var1 ) 

111
$ echo $var1

888

Estamos avanzando muy deprisa, y por eso tenemos que practicar bastante para consolidar los conocimientos. No siempre resulta trivial averiguar como funcionan las cosas. Observe el siguiente ejemplo:

$ echo $$ ; ( echo $$ ; ( echo $$ ) ) 

218
218
218

Nuevamente el resultado puede ser distinto en su ordenador pero lo importante es que 'echo $$' parece obtener el mismo pid pese a ocurrir en distintas sub-shells. Cuando parece ocurrir algo distinto de lo que nosotros esperábamos resulta muy útil usar el modo traza con 'set -x'. Repetimos el ejemplo misterioso de antes.

$ set -x
$ echo $$ ; ( echo $$ ; ( echo $$ ) ) 

+ echo 218
218
+ echo 218
218
+ echo 218
218
$ set +x

Misterio aclarado. Realmente no ejecutamos ningún 'echo $$' sino que ya se ejecuta directamente 'echo 218' porque la shell lo primero que hizo antes de ejecutar las ordenes fué expandir cada '$$' presente en la línea de ordenes.

Ahora que ya sabemos que es un shell-script vamos a ir aprendiendo cosas pero no se pretende una descripción exhaustiva de la Bash. Puede consultar con man bash las dudas que tenga. Recuerde que el man no es una herramienta de estudio sino de consulta. Cuando consulte 'man bash' verá cantidad de información relativa a cosas que no hemos explicado y man no explica casi nada. Por ello muchas cosas de 'man bash' no las entederá aunque siempre queda el recurso de probar las cosas que no se comprenden para ver como funcionan.

Se irán proponiendo ejemplos que ilustren unas cuantos conceptos básicos. Una vez que haya practicado con estos ejemplos habremos conseguido dar los primeros pasos en la programación de shell-script que son los que más cuestan. Después un buen método para continuar aprendiendo a programar en shell-script es intentar comprender las shell-script que encontremos en el sistema con ayuda del manual online de unix.

El lenguaje shell-script es un poco especial con relación a otros lenguajes ya que los blancos y los saltos de línea tienen un significado que hay que respetar y la expansión de ordenes es algo que puede resultar desconcertante. Eso lo hace un poco antipático al principio. Es importante tener una buena base de conocimientos que en estos momentos ya tenemos, y continuar practicando.

Paso de parámetros a un shell-script
Imaginemos que usted tiene dificultad para pasar de Ptas a Euros, y quiere hacer un programita para pasar de una moneda a otra cómodamente. Edite el comando siguiente, y luego ejecutelo en la forma 'pta2eu 35':

#pta2eu
echo $1 '/ 166.386' | bc -l

En este caso $1 actúa como una variable que tomará el valor del primer parámetro. $0 toma el valor del propio comando ejecutado y $2, $3 etc el valor de los parámetros segundo y tercero respectivamente. La variable $# tomará el valor del número de parámetros que usemos. La variable $* toma el valor de todos los parámetros concatenados. Vamos a poner un ejemplo que llamaremos 'pru_parametros'. Debe editarlo, darle permisos de ejecución con chmod +x por ejemplo y ejecutarlo pasando distintos parámetros. './pru_parametros', './pru_parametros hola', './pru_parametros pararm1 param2 param3'.

# pru_parametros
echo "Numero de parámetros = $#"
echo '$0=' $0
echo '$1=' $1
echo '$2=' $2
echo '$3=' $3
echo '$*=' $*

La ejecución de esta prueba quedaría como sigue.

$ ./pru_parametros

Numero de parámetros = 0
$0= ./pru_parametros
$1=
$2=
$3=
$*=
$ ./pru_parametros hola

Numero de parámetros = 1
$0= ./pru_parametros
$1= hola
$2=
$3=
$*= hola
$ ./pru_parametros  pararm1 param2 param3

Numero de parámetros = 3
$0= ./pru_parametros
$1= pararm1
$2= param2
$3= param3
$*= pararm1 param2 param3
$

No existe la posibilidad de usar $10 para acceder al parámetro décimo. En lugar de acceder de esta forma habría que usar un comando interno llamado 'shift'. El efecto de este comando es desplazar los argumentos una posición. De esta forma $1 se pierde y $2 pasará a ser $1, $3 pasará a ser $2, etc. Si existían más de 10 parámetros ahora el décimo que antes era inaccesible pasará a ser $9. $0 no varía. Se pueden desplazar varios parametros de un golpe pasando un número a la función 'shift' por ejemplo 'shift 5' desplazaría a 5 parámetros.

Vamos a editar un fichero de ejemplo que llamaremos 'pru_shift': Debe editarlo, darle permisos de ejecución con 'chmod +x pru_shift' y ejecutarlo pasando distintos parametros. './pru_shift pararm1 param2 param3'.Observe que es una prueba parecida a la anterior. Intente utilizar sus conocimientos de vi para no tener que teclear tanto. Recuerde que puede leer un fichero desde dentro del editor y que puede duplicar bloques de información de varias formas.

# pru_shift
echo "Numero de parámetros = $#"
echo '$0=' $0
echo '$1=' $1
echo '$2=' $2
echo '$3=' $3
echo '$*=' $*
shift 
echo "Numero de parámetros = $#"
echo '$0=' $0
echo '$1=' $1
echo '$2=' $2
echo '$3=' $3
echo '$*=' $*

La ejecución de esta prueba quedaría como sigue.

$ ./pru_shift  pararm1 param2 param3

Numero de parámetros = 3
$0= ./pru_shift
$1= pararm1
$2= param2
$3= param3
$*= pararm1 param2 param3
Numero de parámetros = 2
$0= ./pru_shift
$1= param2
$2= param3
$3=
$*= param2 param3

Test
Puede comprobar sus conocimientos respondiendo el siguiente test.
Para ello seleccione las opciones que se ajusten a la verdad y luego pulse el boton para ver el resultado de su test.

1 El lenguaje shell-script es muy eficiente en el aprovechamiento de la CPU.
2 Antes de ejecutar un programa shell-script hay que compilarlo.
3 Para ejecutar un programa shell-script hay que arrancar siempre una sub-shell.
4 '\$12' representa el parámetro número 12.
5 El número mágico indica la clase de fichero ejecutable.
6 El uso de comandos externos en shell-sript lo hace muy versatil.
7 El uso de comandos externos en shell-sript lo hace muy ineficiente.

Resultado del test

Si quiere hacernos llegar alguna duda, aclaración,
crítica, o contribución personal, utilice nuestro
formulario de contacto y nosotros le contestaremos
contacto