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 =========================

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.

Con esta lección 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. Bueno mejor no lo haga. Presumir no es bueno y ellos podrían presumir de su habilidad manejando el ratón.

El lenguaje shell-script es muy versatil pero ineficiente. Es mucho más eficiente los programas escritos en lenguaje C que es el lenguaje en el que se escribió el kernel de Linux y de otros muchos SO. El valor del lenguaje shell-script es que permite hacer cosas complicadas con poco esfuerzo. Algunas partes del SO están escritas en shell-script. Por ejemplo muchos comandos que se van ejecutando en secuencia mientras el sistema arranca son programas shell-script.

Después de hacer login en el sistema obtenedremos una sesión con el interprete de comandos asociado a nuestro usuario. (Esta información figura en /etc/passwd junto con la clave y otras cosas). 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 tuvieramos que teclear varias cosas podriamos guardar estas ordenes en un fichero y redirigir la entrada estandar 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: çCOM $ banner `date '+%D %T'` çFIN 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. Para ello 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. çCOM $ # 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 çAMA banner 08/02/00 20:26:30 çBLA çFIN !! 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 usaramos 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 deseabamos. Todo esto lo comentamos a modo de repaso. Entonces como tendríamos que haber generado el comando ? (Aquí es donde el listillo dice que usando un editor de texto). Recuerda que mencionamos la posibilidad de escapar un solo caracter con el caracter '\' ? Intentemos de nuevo. çCOM $ # Generamos el fichero con el comando $ echo "banner \`date '+%D %T'\`" > fichero_ordenes $ # Comprobamos el contenido del fichero $ cat fichero_ordenes çAMA banner `date '+%D %T'` çBLA çFIN Ahora si. Vamos a usar la shell de la misma forma que haríamos con un programa normal redirigiendo la entrada estandar desde este fichero. Por lo tanto el fichero tiene tratamiento de datos y solo se requiere permiso de lectura para ejecutarlo de esta forma. çCOM $ # Ejecutamos $ bash < fichero_ordenes $ # Tambien podemos ejecutarlo de otra forma $ cat fichero_ordenes | bash çFIN Para efecutarlo sin redirigir entrada salida podemos pasar como argumento el nombre del fichero. Nuevamente solo hará falta permiso de lectura. çCOM $ # Ejecutar pasando el nómbre del fichero como argumento $ sh fichero_ordenes $FIN Hasta este momento siempre hemos arrancado una sub-shell que leia las ordenes del fichero, las ejecutaba y después terminaba y moría cediendo el control a la shell original. Existe una forma de decirle a la shell actual que lea y ejecute una serie de ordenes 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. çCOM $ # Ejecutar ordenes de un fichero desde la shell actual $ . fichero_ordenes çFIN En este caso no se aprecia ninguna diferencia pero 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. çCOM $echo echo \$PPID \$$ > fichero_ordenes $ bash fichero_ordenes çAMA 213 2396 çBLA $ . fichero_ordenes çAMA 1 213 çBLA çFIN Evidentemente cuando pruebe este ejemplo obtendrá un pid distinto de 213 pero lo interesante es ver como ambas ejecuciones establecen claramente que la primera fué 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. # fichero_ordenes #/bin/bash $PATH /usr/local/bin /home/usuario/bin .bash_profile Paso de parámetros: ------------------- Imaginemos que usted tiene dificultad para pasar de ptas a euros, y quiere hacer un programita para pasar de una moneda a otra comodamente. Teclee el comando siguiente:

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

pretende una descripcion exaustiva de la bourne-shell. Un buen metodo para aprender a programar shell-script es intentar comprender las shell-script que encontremos en el sistema con ayuda del manual online de unix, y ejecutando algunas partes desde la shell para ver lo que hacen. El lenguaje shell-script es un poco especial con relacion a otros lenguajes ya que los blancos tienen un significado que hay que respetar. set set sin parametros lista las variables. set -x Activa el modo traza. Similar al comando 'xtrace' set +x Desactiva el modo traza. set -v Similar a set -x. Y similar al comand verbose. set +v Similar a set +x. set -k Considera la asignacion aunque este precedida de un comando set +k Lo contrario de lo anterior. Es equivalente al comando 'keyword'. set -a Hace exportables todas las variables. Es equivalente al comando 'allexport'. set -e Termina si un comando retorna un codigo distinto de 0. Es equivalente al comando 'errexit' set -u Trata variables no definidas como un error. set -- No cambiar ninguna opcion. Util cuando $1 empieza por - o por +. unset Destruye una variable. Tiene el mismo efecto que = typeset Sin parametros muestra la lista de variables exportables. typeset -i Considera variable numerica (integer). funcion () Se pueden definir funciones que podran ser llamadas por su nombre permitiendo el paso de parametros de la misma forma que cuando se llama a una shell. Tambien puede devolver codigos de retorno. Las variables alte- radas dentro de una funcion tienen efecto visible fuera de la funcion ya que una funcion no se ejecuta invocando a una sub-shell. Una funcion se puede definir en un shell-scrip o en la propia linea de comandos. Si se hace esto ultimo sera como si aniadieramos un comando interno. Existe un comando que permite acceder a para- metros superiros al $9. Es el comand shift que actua desplazando los parametros. Ejemplo: # Funcion que muestra dos parametros de entrada modifica una variable y # devuelve un codigo de retorno. funcion() { echo '<'$1'>' echo '('$2')' var=1234 return 33 } ## main ## funcion 1 2 echo $? echo $var # Visualizar los parametros del 6 al 14 function() { shift 5 echo $1 $2 $3 $4 $5 $6 $7 $8 $9 } ## main ## funcion 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (lista) Ejecuta una lista de ordenes en una subshell. Ejemplo: $PS1> ( var1=11; var2=22; echo $var1 ) $PS1> echo $var1 $PS1> echo $var2 test Test es un comando externo que devolvera un codigo de retorno 0 o 1 dependiendo del resultado de la expresion que sigua a continuacion. Se usa muchisimo y tiene dos formatos. 'test ' o '[ ]'. 0 significara TRUE y distinto de 0 FALSE. -r TRUE si el fichero existe y es legible. -w TRUE si el fichero existe y se puede escribir. -x TRUE si el fichero existe y es ejecutable. -f TRUE si el fichero existe y es detipo regular (normal). -d TRUE si existe y es un directorio. -c TRUE si existe y es un dispositivo especial de caracteres. -b TRUE si existe y es un dispositivo especial de bloques. -p TRUE si existe y es un pipe (fifo). -u TRUE si existe y tiene el bit set-user-ID. -s TRUE si existe y tiene tamanio mayor que 0. -z TRUE si es una cadena vacia. -n TRUE si es una cadena no vacia. = TRUE si ambas cadenas son identicas. OJO hay dejar espacios a un lado y otro del signo igual para no confundir la orden con una asignacion. != TRUE si son distintas. TRUE si no es cadena nula. (Una variable que no existe devuelve cadena nula. -eq TRUE si ambos numero son iguales. (Otros operadores algebraicos son -ne -gt -ge -lt -le) ! Operador logico NOT -a Operador logico AND -o Operador logico OR ( ) Parentesis para agrupar expresiones. Comandos true y false Son comandos que retornan el valor logico true o false respectivamente. Operadores && y || Son operadores AND y OR a nivel de shell y poseen circuito de evalua- cion corto. Ejemplos: # Como un OR queda evaluado cuando se encuentra el primer resultado TRUE # o se completa toda la serie de operaciones, solo se evaluara el primer # o comando, que de volvero TRUE. $PS1> echo "hola" || echo "adios" # El resultado sera 0 $PS1> echo $? # Como un AND no queda hasta que se evalua el primer resultado FALSE # o se completa la serie de operaciones, se evaluaran los dos comandos # y devolvera igualmente TRUE. $PS1> echo "hola" && echo "adios" # El resultado sera 0 $PS1> echo $? # Solo se visualizara 1 2 y 3 $PS1> echo 1 && echo 2 && echo 3 && false && echo 4 && echo 5 # Devolvera false $PS1> echo $? Sentencias condicionales case Sirve para bifurcar entre varias posibilidades en funcion de la evaluacion de un valor. if Sentencia condicional simple. Ejemplos: # Ejecutar la funcion PASO01 o la funcion PASO02 dependiendo del valor # de variable paso (1, 2 respectivamente). En caso de valor distinto de # 1 y de 2 mandar un mensaje de 'Paso inexistente'. case $paso in 1) PASO01 ;; 2) PASO02 ;; *) echo "Paso inexistente" ;; esac # Si exite un fichero para lectura en la variable CMPRT01 imprimirlo. if test -r $CMPRT01 then lp $CMPRT01 fi # Si La variable 'modo' contiene 'lp' imprimir el fichero en caso # contrario sacarlo por pantalla. if [ "$modo" = "lp" ] then lp $FICH else cat $FICH fi Bucles: for Sirve para hacer un bucle con un numero conocido de iteraciones while Sirve para hacer un bucle con un numero desconocido de itera- ciones break Provoca la salida de un bucle por el final. Si viene seguido de un numero 'n' saldra de 'n' niveles. continue Provoca un salto al comienzo del bucle para continuar con la siguiente iteracion. Si viene seguida de un numero 'n' saldra de 'n' niveles. Ejemplos: # Para ver distintos modos de manejar listas vamos a generar todos los # nombres posibles formados por combinacion de las partes siguientes: # Como parte 1 los nombres de ficheros de directorio actual que # empiezan por 'SCAN'. # Como parte 2 los nombres contenidos en el fichero 'lista'. # Como parte 3 los identificadores 'cxx0 cxx2 bxx5' # Como parte 4 la relacion de elementos contenido en lista ${tab[*]}. # Como parte 5 la lista de parametros $1 $2 $3 .. etc, # Como parte 6 los ficheros '*.doc' que contienen la palabra 'CODIGO'. for i in `ls SCAN*` do for j in `cat lista` do for k in cxx0 cxx2 bxx5 do for l in ${tab[*]} do for m in $* do for n in `grep -l "CODIGO" *.doc` do echo $i.$j.$k.$l.$m.$n done done done done done done # Desarrollar una funcion que admita un parametro de entrada. Si el # parametro de entrada contiene una cadena que ya esta almacenada en # la tabla 'tabnom' retornar sin mas, pero si no esta aniadir esa # palabra a la tabla. gesttab(){ # Si numero de parametros distindo de 1 salir con error. if [ $# -ne 1 ] then echo "Numero de parametros invalido en gesttab()" return 2 fi typeset -i j=1 for var in ${tab[*]} do if [ $var = $1 ] then ind=$j return 1 fi let j=j+1 done ind=$j tab[$ind]=$1 return 0 } # Hacer un bucle de captura por consola (terminara cuando se pulse # enter ) y mostrar el resultado de cada captura entre parentesis. LINEA="x" # Inicializar a un valor cualquiera while test $LINEA do read LINEA echo '('$LINEA')' done # El siguiente ejemplo funciona pero es una mala idea ya que supone # Varias ejecuciones de un comando cuando se podia haber hecho con # uno solo. ( Habria sido mejor hacer chmod +x * ) for i in $* do chmod +x $i done # Deseamos conocer las diferencias de el fichero patron con los # fich1, fich2, y fich3. for i in fich1 fich2 fich3; do diff patron $i; done # Analizar el siguiente programa: # Comprobar los comandos y las opciones desconocidas en el # man. Ejecutar las partes dudosas desde la shell para poder # comprenderlas. Explicar finalmente para que sirve y # como funciona. for i in `echo $PATH | tr ":" " "` do for j in `ls $i 2> /dev/null | grep -i $1 2> /dev/null` do echo `ls -liad $i/$j 2> /dev/null` done done PROGRAMACION SHELL-SCRIPT ========================= En este apartado se van a comentar mas cosas sobre la shell pero no se pretende una descripcion exaustiva de la bourne-shell. Un buen metodo para aprender a programar shell-script es intentar comprender las shell-script que encontremos en el sistema con ayuda del manual online de unix, y ejecutando algunas partes desde la shell para ver lo que hacen. El lenguaje shell-script es un poco especial con relacion a otros lenguajes ya que los blancos tienen un significado que hay que respetar. set set sin parametros lista las variables. set -x Activa el modo traza. Similar al comando 'xtrace' set +x Desactiva el modo traza. set -v Similar a set -x. Y similar al comand verbose. set +v Similar a set +x. set -k Considera la asignacion aunque este precedida de un comando set +k Lo contrario de lo anterior. Es equivalente al comando 'keyword'. set -a Hace exportables todas las variables. Es equivalente al comando 'allexport'. set -e Termina si un comando retorna un codigo distinto de 0. Es equivalente al comando 'errexit' set -u Trata variables no definidas como un error. set -- No cambiar ninguna opcion. Util cuando $1 empieza por - o por +. unset Destruye una variable. Tiene el mismo efecto que = typeset Sin parametros muestra la lista de variables exportables. typeset -i Considera variable numerica (integer). funcion () Se pueden definir funciones que podran ser llamadas por su nombre permitiendo el paso de parametros de la misma forma que cuando se llama a una shell. Tambien puede devolver codigos de retorno. Las variables alte- radas dentro de una funcion tienen efecto visible fuera de la funcion ya que una funcion no se ejecuta invocando a una sub-shell. Una funcion se puede definir en un shell-scrip o en la propia linea de comandos. Si se hace esto ultimo sera como si aniadieramos un comando interno. Existe un comando que permite acceder a para- metros superiros al $9. Es el comand shift que actua desplazando los parametros. Ejemplo: # Funcion que muestra dos parametros de entrada modifica una variable y # devuelve un codigo de retorno. funcion() { echo '<'$1'>' echo '('$2')' var=1234 return 33 } ## main ## funcion 1 2 echo $? echo $var # Visualizar los parametros del 6 al 14 function() { shift 5 echo $1 $2 $3 $4 $5 $6 $7 $8 $9 } ## main ## funcion 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (lista) Ejecuta una lista de ordenes en una subshell. Ejemplo: $PS1> ( var1=11; var2=22; echo $var1 ) $PS1> echo $var1 $PS1> echo $var2 test Test es un comando externo que devolvera un codigo de retorno 0 o 1 dependiendo del resultado de la expresion que sigua a continuacion. Se usa muchisimo y tiene dos formatos. 'test ' o '[ ]'. 0 significara TRUE y distinto de 0 FALSE. -r TRUE si el fichero existe y es legible. -w TRUE si el fichero existe y se puede escribir. -x TRUE si el fichero existe y es ejecutable. -f TRUE si el fichero existe y es detipo regular (normal). -d TRUE si existe y es un directorio. -c TRUE si existe y es un dispositivo especial de caracteres. -b TRUE si existe y es un dispositivo especial de bloques. -p TRUE si existe y es un pipe (fifo). -u TRUE si existe y tiene el bit set-user-ID. -s TRUE si existe y tiene tamanio mayor que 0. -z TRUE si es una cadena vacia. -n TRUE si es una cadena no vacia. = TRUE si ambas cadenas son identicas. OJO hay dejar espacios a un lado y otro del signo igual para no confundir la orden con una asignacion. != TRUE si son distintas. TRUE si no es cadena nula. (Una variable que no existe devuelve cadena nula. -eq TRUE si ambos numero son iguales. (Otros operadores algebraicos son -ne -gt -ge -lt -le) ! Operador logico NOT -a Operador logico AND -o Operador logico OR ( ) Parentesis para agrupar expresiones. Comandos true y false Son comandos que retornan el valor logico true o false respectivamente. Operadores && y || Son operadores AND y OR a nivel de shell y poseen circuito de evalua- cion corto. Ejemplos: # Como un OR queda evaluado cuando se encuentra el primer resultado TRUE # o se completa toda la serie de operaciones, solo se evaluara el primer # o comando, que de volvero TRUE. $PS1> echo "hola" || echo "adios" # El resultado sera 0 $PS1> echo $? # Como un AND no queda hasta que se evalua el primer resultado FALSE # o se completa la serie de operaciones, se evaluaran los dos comandos # y devolvera igualmente TRUE. $PS1> echo "hola" && echo "adios" # El resultado sera 0 $PS1> echo $? # Solo se visualizara 1 2 y 3 $PS1> echo 1 && echo 2 && echo 3 && false && echo 4 && echo 5 # Devolvera false $PS1> echo $? Sentencias condicionales case Sirve para bifurcar entre varias posibilidades en funcion de la evaluacion de un valor. if Sentencia condicional simple. Ejemplos: # Ejecutar la funcion PASO01 o la funcion PASO02 dependiendo del valor # de variable paso (1, 2 respectivamente). En caso de valor distinto de # 1 y de 2 mandar un mensaje de 'Paso inexistente'. case $paso in 1) PASO01 ;; 2) PASO02 ;; *) echo "Paso inexistente" ;; esac # Si exite un fichero para lectura en la variable CMPRT01 imprimirlo. if test -r $CMPRT01 then lp $CMPRT01 fi # Si La variable 'modo' contiene 'lp' imprimir el fichero en caso # contrario sacarlo por pantalla. if [ "$modo" = "lp" ] then lp $FICH else cat $FICH fi Bucles: for Sirve para hacer un bucle con un numero conocido de iteraciones while Sirve para hacer un bucle con un numero desconocido de itera- ciones break Provoca la salida de un bucle por el final. Si viene seguido de un numero 'n' saldra de 'n' niveles. continue Provoca un salto al comienzo del bucle para continuar con la siguiente iteracion. Si viene seguida de un numero 'n' saldra de 'n' niveles. Ejemplos: # Para ver distintos modos de manejar listas vamos a generar todos los # nombres posibles formados por combinacion de las partes siguientes: # Como parte 1 los nombres de ficheros de directorio actual que # empiezan por 'SCAN'. # Como parte 2 los nombres contenidos en el fichero 'lista'. # Como parte 3 los identificadores 'cxx0 cxx2 bxx5' # Como parte 4 la relacion de elementos contenido en lista ${tab[*]}. # Como parte 5 la lista de parametros $1 $2 $3 .. etc, # Como parte 6 los ficheros '*.doc' que contienen la palabra 'CODIGO'. for i in `ls SCAN*` do for j in `cat lista` do for k in cxx0 cxx2 bxx5 do for l in ${tab[*]} do for m in $* do for n in `grep -l "CODIGO" *.doc` do echo $i.$j.$k.$l.$m.$n done done done done done done # Desarrollar una funcion que admita un parametro de entrada. Si el # parametro de entrada contiene una cadena que ya esta almacenada en # la tabla 'tabnom' retornar sin mas, pero si no esta aniadir esa # palabra a la tabla. gesttab(){ # Si numero de parametros distindo de 1 salir con error. if [ $# -ne 1 ] then echo "Numero de parametros invalido en gesttab()" return 2 fi typeset -i j=1 for var in ${tab[*]} do if [ $var = $1 ] then ind=$j return 1 fi let j=j+1 done ind=$j tab[$ind]=$1 return 0 } # Hacer un bucle de captura por consola (terminara cuando se pulse # enter ) y mostrar el resultado de cada captura entre parentesis. LINEA="x" # Inicializar a un valor cualquiera while test $LINEA do read LINEA echo '('$LINEA')' done # El siguiente ejemplo funciona pero es una mala idea ya que supone # Varias ejecuciones de un comando cuando se podia haber hecho con # uno solo. ( Habria sido mejor hacer chmod +x * ) for i in $* do chmod +x $i done # Deseamos conocer las diferencias de el fichero patron con los # fich1, fich2, y fich3. for i in fich1 fich2 fich3; do diff patron $i; done # Analizar el siguiente programa: # Comprobar los comandos y las opciones desconocidas en el # man. Ejecutar las partes dudosas desde la shell para poder # comprenderlas. Explicar finalmente para que sirve y # como funciona. for i in `echo $PATH | tr ":" " "` do for j in `ls $i 2> /dev/null | grep -i $1 2> /dev/null` do echo `ls -liad $i/$j 2> /dev/null` done done

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

.
....
...

Volver a la página anterior
Volver a la página principal de la tienda