Ampliación de Sistemas Operativos - 2000

Referencia para la Programación del Shell de UNIX

 

El objetivo de la práctica es desarrollar un programa intérprete de comandos (shell) similar al Csh del UNIX. En el caso que el shell se desarrolle en otro sistema operativo, también deberán compilarse los fuentes de los comandos principales del UNIX para que el shell pueda ejecutarlos (ls, more, cat, grep, mkdir, rmdir, rm, etc.). Los grupos de prácticas no podrán ser de mas de dos personas.

 

Las prácticas se presentarán interactivamente y se deberán preparar una serie de programas (scripts) que ejecutados con el comando interno source (ver punto 3) muestren todas las capacidades del shell (comandos internos, estructuras de control, etc.). Para que la práctica sea aprobada deberán programarse como mínimo todas las especificaciones descriptas en este documento y no se deben producir errores de programación durante la ejecución. Se valorará además la facilidad de uso del mismo como la estructura y modularización de las funciones y ficheros fuentes. Si el programa se desarrolla en Linux o Minix, deberá funcionar como shell del usuario (especificado en el fichero /etc/passwd).

 

La documentación a tener a mano durante la presentación será:

 

1. Máquina (grafo) de estados finitos del shell.

2. Comandos/capacidades adicionales programadas.

3. Listado del programa (estructurado y comentado).

4. Listado de los scripts de pruebas.

 

1. Líneas de Comando e inicialización.

Al arrancarse el programa este debe leer dos ficheros:

 

a)     El fichero con las inicializaciones de las variables de entorno y comandos de inicialización que se encuentra en $HOME/.inicio. En este fichero deberá inicializar como mínimo las variables de entorno PATH e HISTORIA)

b)    El fichero donde se ha grabado los últimos comandos ejecutados en la sesión anterior ($HOME/.historia).

 

Las líneas de comandos aceptarán “;”como separadores de comandos. Los pipes entre comandos estarán indicados por el caracter “|”. Las redirecciones de salida a ficheros se especifican con “> fichero” (nuevo fichero) y “>> fichero” (concatenar). La redirección de entrada estándard se especifica con “< fichero”. Ejemplos:

 

% com1 arg1 arg2; com1 | com2 | com3 > salida < entrada; com8

 

El shell almacenará los últimos N (siendo N definido por la variable de entorno HISTORIA, si la variable no existe será un mínimo de 20) comandos ejecutados que podrán reejecutarse con la siguiente sintaxis (opcionalmente con las flechas de arriba y abajo).

 

!!             ejecuta el último comando.

!2            ejecuta el segundo comando guardado en la historia.

!-n           ejecuta el último - n comando.

!XY         ejecuta el último de los comandos que comienzan con XY...

 

Los comandos serán almacenados en un fichero ($HOME/.historia) cuando se sale del shell y será leído cuando se arranca. Se utilizará una lista circular para almacenar los comandos ejecutados.

 

2. Procesos en Background.

Para enviar un proceso a background se indica con el carácter & al final del comando (después de las redirecciones, si las hubiera).

 

% com1 | com2 | com3 > salida < entrada &

 

3. Comandos internos

El shell tendra algunos comandos internos, lo que significa que en vez de hacer un <fork, exec> se ejecutará una función específica del shell. Los comando internos son:

cd                                                                                         (cambio de directorio de trabajo).

pwd                                                                                      (Imprimir directorio de trabajo).

printenv                                                                         (Imprimir variables de entorno).

print {$variable, constante}                     (Imprime variable o constante).

export variable                      (Exporta el valor de variable)

while expresión                                                      (Estructura de control).

for variable in lista                                       (Estructura de control).

if expresión                                                               (Estructura de control).

end                                                                                      (Fin de if, while, foreach).

@                                                                                            (Asignación de variables, ver siguiente apartado).

jobs                                                                                    (Listar Procesos en background).

fg pid                                                                              (Enviar proceso a foreground).

source fichero                                                            (Ejecuta el fichero)

!, !!, etc.                                                                (Reejecución de comandos).

history                                                                                  (Muestra los últimos comandos ejecutados).

 

4. Variables

El shell almacenará variables de entorno internas, que podrán ser exportadas con el comando export para que sean heredadas por los procesos hijos (putenv(), getenv()). Las variables podrán ser de tipo string o entera. El comando para asignar valores es @. Las funcionalidades mínimas son:

 

@ v={constante, $variable)      @ v=expresión      @ v++      @ v--

@ v+={constante, $variable)      @ v-={constante, $variable)

 

donde expresión es {constante, $variable} {+, -} {constante, $variable}

 

Para referirse al valor de una variable (en expresiones lógicas, argumentos de comandos, etc.) se antepondrá $ al nombre de la variable. Ejemplos:

% @ a=1

% @ a++

% print $a

2

% @ b=practica1

% print $b

practica1

% ls -l $b

-rw-r--r--    1 gallir   animacio   10465 Feb 22 13:07 practica1

% @ a=$a + $b

Error: Variable no numerica

%

Las variables siempre deben reemplazarse por su valor en las líneas de comando, es decir que un ls $a $b en realidad corresponde internamente a ls 2 practica1

 

5. Estructuras de Control.

Las estructuras mínimas de control serán las siguientes:

 

% while (condición)      % for var in (lista...)        % if (condición)

> comando                 > comando                       > comando

> …                     > …                           > …

> end                  > end                        > end

 

Cada estructura de control comienza con uno de los comandos internos indicados, almacenan las líneas de comandos hasta encontrarse con el end y luego ejecuta los comandos almacenados (haciendo los reemplazos de variables correspondientes).

Condición

                 {constante, $variable} {<, <=, =, >=, >} {constante, $variable}

Lista

                 cadena1, cadena2, ..., cadenaN.

 

While

Mientras se cumpla la condición, ejecutar los comandos hasta el end. Ej:

% @ a=1

% while ($a <= 100)

> print $a

> @ a++

> end

1

2

...

100

%

 

For

Para cada una de las cadenas de la lista, asignar la cadena a la variable var y ejecutar los comandos. Ej:

 

% for b in (practica1 practica2)

> print $b

> ls -l $b

> end

practica1

-rw-r--r--    1 gallir   animacio   10465 Feb 22 13:07 practica1

practica2

-rw-r--r--    1 gallir   animacio   11238 Feb 22 13:09 practica2

%

 

If

Si se cumple la condición, ejecutar los comandos (opcional: aceptar else). Ej:

% @ a=99

% if ($a < 100)

> echo "El valor de a es menor a 100"

> end

El valor de a es menor a 100

%

 

OPCIONAL (mejora la nota)

El shell debe interpretar comodines (expresiones regulares simples del tipo “*” y “?”), abrir el directorio correspondiente, verificar que ficheros cumplen las condiciones de los comodines y agregar su nombre a los argumentos que se enviarán al comando a ejecutar.

 

 

Ricardo Galli

2000-10-05