Home IT

Location | Personnel | Teaching | Research | News | Intranet   

 

Home / Teaching / Telecommunication Engineering / Computer Architecture Laboratory / Laboratory 1

anteriorsiguiente

 

Laboratorio de Arquitectura de Ordenadores

Práctica 1:

Shell Script

Contenidos

   1. Introducción

   2. Uso de variables

   3. Manejo de cadenas

   4. Control de flujo (I): if/else

   5. Control de flujo (II): case y select

   6. Bucles (I): for

   7. Bucles (II): while

   8. Gestión de E/S y uso de comandos

   9. Cómo aprender más

   10. Descripción del ejercicio

   11. Ayuda para el ejercicio

 

1. Introducción

 

Este primer laboratorio está diseñado para que el alumnos adquiera una comprensión general de la programación en shell script. Esto incluye el manejo de variables y el uso de otras estructuras como decisiones y bucles. El tutorial presenta una cierta cantidad de ejemplos fáciles de seguir para ilustrar cada aspecto de la programación. Se trata de proporcionar un enfoque conciso y práctico que complemente las clases teóricas, en lugar de repetir los contenidos de éstas. Se han incluido una buena cantidad de hipervínculos para que el alumno tenga la posibilidad de aumentar sus conocimientos. Tras completar este tutorial, el alumno estará familiariazado con los principios básicos de la programación en Shell Script, tendrá una visión clara de su potencial y, en consecuencia, será capaz de afrontar el ejercicio propuesto al final.

 

 

2. Uso de variables

 

Como ya sabe, las variables en los scripts tienen un nombre que se establece al inicializarlas (var_nombre=valor, siempre sin espacios entremedias), y se referencian utilizando el carácter especial  '$', preferiblemente entre llaves (${var_nombre}). Esto es de utilidad para el intérprete del script, que así sabe que la variable ya tiene un valor. En esencia, 'var_nombre' representa el nombre de la variable, mientras que $var_nombre o ${var_nombre} representa su valor. Es muy importante recordar este último punto, puesto que omitir alguno de estos caracteres especiales puede llevar a errores bastante difíciles de detectar, dados los mensajes de error poco útiles que suele dar el intérprete.. Veamos un ejemplo básico de creación y manejo de una variable, en concreto la impresión de su valor por pantalla:

#!/bin/bash

#Script que muestra un mensaje 'Hello world!'
msg='Hello world'
echo ${msg}

exit 0

Ejemplo 1

Aunque este es probablemente el ejemplo más simple, merece la pena comentar ciertas características comunes a todos los scripts:

  1. La línea inicial '#!/bin/bash' es obligatoria e identifica al archivo como un script
  2. Como siempre en programación, los comentarios son muy importantes. Una línea de comentarios debe ser precedida del carácter '#'. Los contenidos de esta línea no serán interpretados.
  3. Un error típico es intercalar espacios en blanco en la inicialización:  " msg ='Hello world' " o " msg= 'Hello world' " causarán errores.
  4. Aunque las llaves en el comando 'echo' son opcionales en este caso, es una buena práctica incluirlas siempre, puesto que son obligatorias cuando se han de realizar más operaciones sobre la variable, como veremos posteriormente.
  5. También es recomendable acabar el script con un comando 'exit' que devuelve un código de error codificando el resultado de la ejecución. En este caso el código es 0, es decir, sin errores.

Naturalmente, una variable puede tomar cualquier valor, incluyendo valores numéricos. No obstante, todas son tratadas como cadenas a no ser que se especifique lo contrario, como veremos en los próximos apartados.

Puede ver más ejemplos aquí.

 li

 

 

3. Manejo de cadenas

 

Una de las virtudes de los shell scripts de Linux radica en la potencia del tratamiento de cadenas que ofrece. Como ya hemos comentado, las variables se consideran cadenas a no ser que se especifique de otra manera, de modo que todas se pueden tratar tal y como se describe en este apartado. Puede consultar las transparencias de las clases teóricas para revisar los operadores que permiten comprobar si una variable está definida, la extracción de una subcadena y la extracción de patrones de varias formas. Este ejemplo incluye los operadores más útiles. ¿Puede determinar cual será la salida sin ejecutar el script?

#!/bin/bash

msg='to be or not to be'
msg2='two beers'
echo $msg

#Eliminar un prefijo que es la coincidencia más corta con el patrón
#Las llaves son obligatorias
a=${msg#to be}
echo $a

#Eliminar un sufijo que es la coincidencia más corta con el patrón
b=${a%to be}
echo $b

#Concatenar ambas variables (es decir, cadenas)
c=$msg2$b
echo $c
d=$c$msg2
echo $d

#Imprimir un cierto número de caracteres de una cadena desde una cierta posición
echo ${d:17:3}
echo $msg2 ${msg:6}

exit 0

Ejemplo 2

Preste atención al uso de comillas simples ('') para delimitar la asignación de cadenas con más de una palabra. Esto es necesario para hacer saber al intérprete que no estamos utilizando una serie de comandos, sino que estamos inicializando una variable con varias palabras. Podríamos haber utilizado comillas dobles, que son menos estrictas, lo que puede llegar a ser útil para incluir ciertos caracteres especiales. Haga click aqui para obtener más información sobre éste y otros asuntos relacionados con el entrecomillado Para obtener una guía completa de todas las posibilidad existentes para manejar cadenas, con abundancia de ejemplos, haga click  aqui.

¿Y qué pasa cuando quiere utilizar una variable como un número en vez de como una cadena? En ese caso es necesario indicárselo al intérprete, de forma que pueda realizar las operaciones numéricas deseadas. El siguiente ejemplo muestra el funcionamiento básico. Como antes, es un buen ejercicio intentar predecir el resultado de la ejecución del script.

#!/bin/bash

a=2
b=3

#Tratar la variables como siempre
echo a=$a b=$b
str=$a+$b
echo $str

#Especificar una operación numérica
num1=$((a+b))
echo $num1

exit 0

Ejemplo 3

Se utiliza $(('expresion')) para especificar que 'expresion' debe ser interpretada como una expresión aritmética, no como una cadena o el nombre de una variable.Otra posibilidad es utilizar el comando 'expr', aunque se suele preferir la primera construcción. Es importante recordar que solo se pueden realizar operaciones sobre números enteros. Números no enteros (por ejemplo, '2.2') son considerados siempre como cadenas. Puede encontrar más información y ejemplos sobre el manejo de expresiones aritméticas aquí.

 

 

4. Control de flujo (I): if/else

 

Otra de las razones de la potencia de la programación en shell script es la capacidad de control de flujo que ofrece. La herramienta más importante es la construcción if/else, que se puede usar de forma similar a como se hace en otros lenguajes de programación que ya conoce. Su estructura es la siguiente:

 

if    condicion1
then
codigo1
[elif condicion2
then
codigo2]
....
[else
codigoN]
fi

Estructura 1

Una condición se especifica entre corchetes, y puede incluir comparaciones de cadenas, operadores de archivo (por ejemplo, para saber si un archivo existe), operaciones lógicas (and/or lógico) y operadores aritméticos (igual que, mayor que, etc. para cadenas que se interpretan como números). Para una lista completa de todos los operadores posibles, revise las transparencias de la clase teórica, o haga click aquí para una lista más detallada, con un gran número de ejemplos.El siguiente ejemplo muestra una utilización básico pero muy común de esta construcción, pidiendo al usuario que realice una elección y ejecutando un código u otro en función de esa elección:

 

#!/bin/bash

echo 'Introduzca su peso en kilogramos:'
#Leer una línea de entrada
read peso
echo 'Quiere saber su peso en libras? (s/n)'
read respuesta

if [ $respuesta == 's' ]
then
    #Cuidado, solo operaciones con numeros enteros
    #luego 'numLibras=numKgs/0.45' no es valido
    peso2=$(($peso*100))
    peso2=$(($peso2/45))
    #Con 'echo' no hacen falta comillas
    echo Su peso es de $peso2 libras
else
    echo Como quiera
fi

exit 0

Ejemplo 4

El siguiente ejemplo utiliza operadores de archivo para comprobar si un archivo existe y si el usuario tiene permiso de escritura en él. El nombre del archivo se pasa como el primer parámetro al ejecutar el script desde la línea de comandos, tal y como veremos en el apartado 8. Cabe destacar como las dos condiciones se asocian mediante el operador and (%%): el codigo se ejecutará solo su ambas condiciones se satisfacen.

 

#!/bin/bash

archivo=$1

if [ $archivo ] && [ -f $archivo ] && [ -w $archivo ]
then
    echo El archivo $archivo existe y puede escribir en él
else
    echo No puede escribir en $archivo
fi

exit 0

Ejemplo 5

Estos ejemplos muestran un uso básico de la estructura if/then/else. Puede encontrar ejemplos más complejos aquí, incluyendo el uso de otros operadores y el de condiciones if/then/else anidados.

 

5. Control de Flujo(II): Sentencias case y select

 

Aunque la construcción if/else es lo suficientemente potemte como para cubrir todas las necesidades en cuanto a control de flujo, en algunos casos su uso exclusivo produce scripts demasiado complejos, con demasiadas condiciones y/o if/else anidados que hacen difícil de leer el código. Hay dos construcciones que permiten salvar tiempo y espacio cuando esto sucede: 'case' y 'select'. La primera es útil para elegir un cierto código a ejecutar de entre varias posibilidades en función del valor de una variable, sin tener que recurrir a una construcción if/else con varias cláusulas 'elif'. La segunda presenta al usuario un menú por pantalla para que elija entre las opciones disponibles, de forma que ahorra un buen número de líneas en el script.

La construcción 'case' tiene la siguiente estructura, donde 'expresion' es una variable o una expresión compuesta de varias variables que puede tomar valores entre patron1 y patronN:

 

case expresion in
   patron1 )
       codigo1 ;;
   patron2 )
       codigo2 ;;
.....
   patronN )
       codigoN ;;
esac

Estructura 2

El siguiente ejemplo muestra una calculadora a la que se le pasan los datos y la operación a realizar por línea de comandos (puede acudir al material de la clase teórica o al apartado 8 para revisar este tipo de paso de parámetros) La calculadora se basa en una construcción case que realiza una u otra operación numérica con los dos primeros parámetros en función del valor del tercero. Cabe destacar el uso del patrón 'por defecto' (*), que representa cualquier posibilidad no contemplada anteriormente.

 

#!/bin/bash

#Revisar el numero de parametros
#La llamada debe ser como aqui:
#  bash calculator.sh 1 + 2
if [ "$#" != 3 ]
then
   echo Introduzca una operacion
   exit 1
fi

#Mostrar el resultado en funcion de la operacion seleccionada
case "$2" in
   '+')
      echo $(($1+$3)) ;;
   '-')
      echo $(($1-$3)) ;;
   '/')
      echo $(($1/$3)) ;;
   *)
      echo "Operacion invalida";;
esac

exit 0

Ejemplo 6

La construcción 'select' resulta muy útil cuando el usuario ha de elegir un elemento de una lista, puesto que presenta automáticamente un menú ya formateado que lee la selección del usuario en la variable especificada, como se muestra en el cuadro. Es necesario especificar el caracter que delimita los elementos de la lista. Además, hay que indicar una pregunta con la que se pedirá al usuario que realice una elección, como se muestra en el ejemplo más abajo. El uso típico de esta construcción incluye en su interior un select que efectúa una o utra acción en función de la elección del usuario.En resumen, 'select' asigna un valor a una variable de acuerdo a la elección del usuario, mientras que 'case' realiza una acción elegida entre varias posibles dependiendo del valor de una variabls. Es evidente que ambas construcciones son complementarias en multitud de situaciones.

 

select variable [in lista]
do
 
codigo
done

Estructura 3

El ejemplo presentado a continuación constituye un uso típico de la construcción select. Permite al usuario elegir entre cuatro elementos, mostrando un menú con formato, y realiza la acción correspondiente al elemento elegido mediante una construcción case. Estas acciones consisten simplemente en la llamada a un comando, pero puede imaginar lo útil que esto resulta para realizar tareas mas complejas, ya que la estructura del código será siempre la misma.

 

#!/bin/bash

#Lista con las posibles opciones ofrecidas
lista="Directorio actual,Fecha y hora,Ambos,Salir"
#Pregunta para el usuario
PS3="¿Que informacion quiere conocer?"
#Especificar el caracter que separa los elementos en la listaIFS=,
#Linea en blanco
echo

select task in $comm_list
do
   case $task in
      "Directorio actual" ) pwd ;;
      "Fecha y hora" ) date ;;
      "Ambos" ) pwd;date ;;
      "Salir" ) exit 0 ;;
   esac
   echo
done

exit 0

Ejemplo 7

Puede encontrar más ejemplos sobre las construcciones case y select aquí

 

6. Bucles (I): for

 

Los bucles son otra de las herramientas imprescindibles en programación estructurada. Como ya sabe, su objetivo es repetir un cierto código un número de veces que depende de una condición. El bucle más básico es la construcción 'for', en el que el número de iteraciones es conocido antes de ejecutar el bucle. Su estructura se muestra a continuación. El código se repite tantas veces como elementos haya en la lista (que tendrá el mismo formato que en para la construcción select), y la variable tomará el siguiente valor de la lista cada vez, comenzando por el primero.

 

for variable in [lista]
do
 codigo
done

Estructura 4

El sencillo ejemplo que se muestra a continuación muestra los cuadrado de los diez primeros números. Recuerde que es necesario especificar un delimitador al utilizar la lista, es decir, hay que dar un valo a la variable interna IFS. Puede encontrar más información sobre variables internas  aquí.

 

#!/bin/bash

#Lista con los diez primeros numeros
nums="1:2:3:4:5:6:7:8:9:10"
#Delimitador
IFS=:

#Mostrar los numeros al cuadrado
for n in $nums
do
   echo $((n*n))
done

exit 0

Ejemplo 8

Este tipo de bucles ofrecen un gran número de posibilidades, puede consultar más ejemplos aquí.

 

7. Bucles (II): while

 

Aunque los bucles for son muy útiles y se usan con mucha frecuencia, en ciertas ocasiones no son la mejor opción, puesto que el número de iteraciones no se conoce antes de la ejecución del bucle, sino que depende de otra condición. En ese caso, es necesario utilizar un bucle 'while'. Cualquier bucle for puede ser sustituido por un bucle while equivalente. La estructura de éste se muestra a continuación. El código es ejecutado indefinidamente hasta que la condición entre corchetes. El formato y la interpretación de estas condiciones es igual que en el caso de las construcciones if/else.

 

while [condicion]
do
 codigo
done

Estructura 5

El siguiente ejemplo imprime todos los números de la secuencia Fibonacci menores que 100. Esta secuencia se caracteriza porque un elemento de ella es la suma de los dos elementos anteriores, comenzando por 1 (1,1,2,3,5,8,13,...). Observe que este script se podría haber realizado también con un bucle for.

#!/bin/bash

#Displays those numbers of the Fibonacci sequence lower than 100
a=1
b=1
c=1

#Iterate while c<100
while [ "$c" -lt 100 ]
do
   echo $c
   a=$b
   b=$c
   c=$(($a+$b))
done

exit 0

Ejemplo 9

Para consultar más ejemplo de uso de este bucle (incluyendo aquellos casos en los que no se puede encontrar un bucle for equivalente), y aprender sobre el bucle 'until' como posible alternativa en ciertos casos, haga click aquí.

 

 

8. Gestión de E/S y uso de comandos

 

Este apartado presenta conceptos y herramientas que van más allá del funcionamiento básico pero son bastante útiles. Comenzaremos con los parámetros posicionales, es decir, parámetros pasados al script por línea de comandos. Si se llama al script mediante "./scriptname param1 param2", el dato param1 será almacenado en la variable $1, y el dato param2 será almacenado en la variable $2. La variable $* almacenará la lista completa de parámetros (incluyendo el nombre del script), y $# almacena el número de elementos en esa lista. Ya hemos realizado un uso básico pero típico de este mecanismo en algunos de los ejemplo anteriores, pero puede encontrar ejemplo más sofisticados aquí.

 

Otra posibilidad muy útil que ofrece la shell es la redirección de la entrada o la salida desde la pantalla a un archivo. El caso de uso típico es la redirección de la salida de un comando a un archivo. Por ejemplo,  'ls > arch' creará un nuevo archivo llamado 'arch' con la salida del comando 'ls'. Si se usa el operador '>>' en lugar de '>', la salida será incluida al final del archivo en el caso de que éste ya existiese. Para la entrada por teclado, los operadores equivalentes son '<' y '<<'. Otra posibilidad es pasar a un comando como entrada la salida de otro, utilizando una tubería (pipe, '|'), el ejemplo típico es 'ls | less'.

Finalmente, es habitual llamar a un buen número de comandos desde un script. Para asignar la salida de un comando a una variable, solo es necesario hacer var = $(comando)

El siguiente ejemplo ilustra la redirección de entrada y salida (E/S) y el manejo de comandos descrito. Su funcionalidad consiste en extraer el día y la hora manteniendo un registro, consistente en un archivo que contiene todas las fechas en las que se ejecutó el script y otro archivo que sólo contiene la última hora a la que se ejecutó el script.

 

#!/bin/bash

#Almacenar la salida del comando 'date'
dia=$(date)
#Extraer la fecha
nuevo_dia=${dia:0:10}
#Extraer la hora
hora=${dia:11:8}

#Mostrar el dia
echo Hoy es: $nuevo_dia

#Incluir la nueva fecha al final del archivo 'fechas'    #o crearlo si no existe
echo $nuevo_dia >> fechas

#Crear un archivo 'ultimahora ' con la hora,
#borrando cualquier archivo anterior con el mismo nombre
echo $hora > ultimahora

exit 0

Ejemplo 10

Puede encontrar más ejemplos aquí.

 

9. Cómo aprender más

 

Para mejorar sus conocimientos sobre programación en shell script, este tutorial  es muy apropiado. Tenga en cuenta que esta asignatura solo cubre una pequeña parte de esos contenidos, pero es muy útil utilizarlo como referencia, además de como fuente de ejemplos. Si prefiere un libro en papel, éste está en la biblioteca y proporciona una gran cantidad de ejemplos explicados detalladamente:

  • Learning the bash shell. Newham, Cameron (L/D 004.451.9 UNIX NEW)

Cuando esté programando, este resumen la resultará muy útil.

 

 10. Descripción del ejercicio

 

Este ejercicio consiste en el desarrollo de un script (proc.sh) que permita obtener  la representación en binario de números representado en los sistemas Decimal, Octal y Hexadecimal. Es decir, se tendrán que contemplar las siguientes conversiones:

 

1.      Decimal a Binario

2.      Octal a Binario.

3.      Hexadecimal a Binario

  • El script podrá funcionar en dos modos: menú y líneas de comando.

      El modo comando admite dos parámetros, el primero indicará el sistema de representación (D, O, H) en que se encuentra el número a convertir
      y el segundo el  número a convertir:

          > ./proc.sh  sist_representación numero

            En modo menú realizará las mismas operaciones, pero el sistema de representación y el número serán introducidas por el usuario interactivamente.

  • Será necesario comprobar que el número introducido se encuentra en el sistema de representación elegido, es decir, si es un número decimal, octal o hexadecimal válido.

      Para un mejor desarrollo del script utilice funciones, para realizar cada una de las conversiones.

 

 


 11. Ayuda para el ejercicio

 

  1. No se permite la utilización del comando ‘bc’, se deberán desarrollar las funciones oportunas que realicen las conversiones.
  2. Revise el formato de salida sugerido, y verifique la corrección de su script realizando AL MENOS los ejemplos de ejecución incluidos. No obstante, cuanto más completas sean sus comprobaciones más seguro podrá estar de la correción de su solución.
  3. Aquí tiene una posible punto de partida para su script. Naturalmente, puede utilizar otra estructura si lo considera apropiado:

 

if [ menu mode ]
then
....
select task ....
....
case $task in
'Decimal')
....
'Octal')
....
'Hexadecimal')
....

'Exit')
....
....

else #Command-line mode
case ....
"D")
....
"O")
....
"H")
....
*) ....
....

 

Punto de partida

 


 Notas

 

  • No trabaje más de lo necesario: utilice los comandos que Linux ofrece tanto como pueda.
  • Puede encontrar un gran número de alternativas en los ejemplos más sofisticados que se encuentran en el tutorial ya mencionado.

 

 


 Ejemplos de ejecución / benchmark

 

La ejecución del script en ambos modos debe ser similar a ésta. Para comprobar su solución intente realizar al menos las operaciones mostradas aquí y compruebe los resultados:

 

$> ./proc.sh

                                                                     

1) Decimal

2) Octal

3) Hexadecimal

4) Salir

Elija una opción:1

¿Que número DECIMAL desea convertir?

123

El número 123 en decimal se representa como 1111011 en binario.

Elija una opción:1

¿Que número DECIMAL desea convertir?

2A

Digito A no válido en decimal

Elija una opción:2

¿Que número OCTAL desea convertir?

27

El número 27 en octal se representa como 010111 en binario.

Elija una opción:2

¿Que número OCTAL desea convertir?

F3

Digito F no válido en octal

Elija una opción:3

¿Que número HEXADECIMAL desea convertir?

a290

El número a290 en hexadecimal se representa como 1010001010010000 en binario.

Elija una opción:3

¿Que número HEXADECIMAL desea convertir?

12J

Digito J no válido en hexadecimal

Elija una opción:4

Fin del programa

$> ./proc.sh D 45

El número 45 en decimal se representa como 101101 en binario.

$>./proc.sh D a45

Digito a no válido en decimal

$>./proc.sh o 17

El número 17 en octal se representa como 001111 en binario.

$>./proc.sh O 19

Digito 9 no válido en octal

$>./proc.sh H A2

El número A2 en hexadecimal se representa como 10100010 en binario.

$>./proc.sh L A2

Sistema de representación L no válido

Salida por pantalla del script

Para corregir su script, se realizarán un cierto número de pruebas similares a las anteriores.

 


 Submission

 

Shell Script Submission page

 

Notas de práctica sobre Shell Script

 

© Ralf Seepold - last updated 23/02/2006

 

Location | Personnel | Teaching | Research | News | Intranet

 

inicio | mapa del web | contacta