You are on page 1of 102

Guía

Guía de Practicas
Practicas
Sistemas
Sistemas Operativos I

Esta Guía de Prácticas es parte de la obra “Sistemas


Operativos I. Guía didáctica y de trabajo autónomo”
del mismo autor, 2007

José Antonio Gómez Hernández


A Encarni, mi mujer, y a mis hijos José Antonio y Manuel.

© José Antonio Gómez Hernández, Granada, 2007.


Depósito Legal: GR-2367/07
ISBN: 978-84-612-6553-4

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 2


GUIA DE PRACTICAS
Indice de la Guía de prácticas
¿Cómo utilizar esta Guía y realizar el trabajo de prácticas?.....................................................4
Planificación de prácticas.........................................................................................................5
Convenios utilizados en esta guía.............................................................................................6
Modulo I: El tcshell y órdenes de unix....................................................................................7
Parte I: El sistema operativo Linux
1. La estructura y las interfaces de Linux.......................................................9
2. Los interpretes de órdenes..........................................................................9
3. Una sesión de trabajo................................................................................10
4. El sistema de archivos...............................................................................11
5. El shell de entrada.....................................................................................14
6. Historia de órdenes....................................................................................16
7. Metacaracteres..........................................................................................18
8. Procesamiento de la línea de órdenes.......................................................28
9. Los alias....................................................................................................29
10. Ordenes para el control de trabajos...........................................................31
11. Ordenes para el control de recursos..........................................................37
12. Miscelánea de órdenes..............................................................................38
13. Procesamiento avanzado de archivos........................................................40
14. Expresiones regulares...............................................................................43
15. Operaciones sobre medios separables.......................................................44
16. Algunas herramientas para vigilar procesos.............................................45
Ejercicios..................................................................................................48
Parte II: La programación del tcshell..........................................................................51
1. Variables del shell.....................................................................................53
2. Programas del shell...................................................................................56
3. Variables empotradas que admiten valores...............................................58
4. Variables que actúan como conmutadores................................................63
5. Variables numéricas..................................................................................64
6. Ordenes de interacción..............................................................................67
7. Ordenes condicionales..............................................................................71
8. Otras órdenes............................................................................................74
9. Archivos de configuración del shell.........................................................76
Ejercicios..................................................................................................80
Modulo II: Programación con hebras – Visión general de las Pthreads................................83
1. ¿Qué son las hebras?.................................................................................85
2. ¿Qué son las Pthreads?..............................................................................86
3. Diseñando programas con hebras..............................................................87
4. La API de Pthreads....................................................................................88
5. Compilación de un programa con hebras..................................................98
6. Relación de funciones de la biblioteca......................................................99
Ejercicios................................................................................................101

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 3


¿ Cómo utilizar esta guía y realizar el trabajo de
prácticas?
El presente guión pretende introducir al lector, alumno de la asignatura de Sistemas
Operativos I, en el conocimiento del sistema operativo Linux. Se pretende dar una base sólida
para que el alumno maneje el sistema y siga avanzando en su estudio. Nuestra Guía abordará
en un primer bloque las órdenes básicas del sistema así como el conocimiento en detalle del
funcionamiento de un intérprete de órdenes, el tcshell (una versión mejorada del C Shell). En
un segundo bloque, estudiaremos la biblioteca de hebras de Linux y programaremos ejemplos
de sincronización y comunicación entre hebras mediante semáforos.
La metodología a seguir para la realización de las prácticas es similar a la de teoría.
Los pasos a seguir son:
1. Leer antes de cada sesión de práctica los apartados de la Guía que se indican el la
“Planificación de prácticas”. Anotar las dudas que surjan.
2. Esbozar una solución para los ejercicios de prácticas propuestos para cada semana
en la planificación, antes de asistir al laboratorio.
3. En el laboratorio, aclarar las dudas surgidas con el monitor de prácticas, e
implementar y probar las soluciones que tenéis planteadas a los ejercicios. Si
funcionan, enhorabuena lo habéis entendido. Si no funcionan correctamente,
intentad comprender qué está pasando para poder corregirlo.

Si bien la guía tiene los contenidos necesarios para la realización de las prácticas, no se
intenta eliminar la labor del monitor de prácticas, que puede y debe guiar al estudiante fuera y
durante las sesiones de prácticas. Tampoco se quiere que el alumno deba memorizar todas las
ordenes, opciones y detalles del sistema, se pretende que se comprendan cómo funcionan y la
forma de resolver los problemas que va a encontrar a la hora de trabajar en UNIX, ya sea
como usuario o como administrador de sistema.
Al ser una introducción, se ha sacrificado completitud y, en algunos casos, exactitud por
claridad. En este sentido, no se han cubierto todas las ordenes y opciones de las mismas, ni se
han analizado en profundidad todos los detalles de un sistema tan complejo como UNIX,
además, para eso están los manuales del sistema. Aún así, el guión contiene suficiente
información para hacerlo complejo, por lo que aconsejamos al lector lo aborde, como se suele
decir “sin prisa pero sin pausa”, no perdiendo de vista que no se trata de memorizar sino de
comprender. Para conocer la totalidad y los detalles de un sistema como UNIX, se requiere
más tiempo del que permite la asignatura, y un trabajo asiduo con él. Por ello, es importante
que cumplas la programación que se propone y no dejes las prácticas para “el último día”.
Otro aspecto menor a tener en cuenta hace referencia al funcionamiento de las órdenes
y ejemplos que aparecen a lo largo del guión. En él, suponemos un sistema UNIX
determinado. Como el lector podrá probar las órdenes en otros sistemas o versiones (Linux,
Solaris, HP-UNIX, System V Versión 4, etc.), conviene estar prevenido de que pueden existir
pequeñas diferencias entre sistemas, tanto en sintaxis como en funcionamiento.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 4


Planificación de prácticas

La siguiente tabla muestra la planificación semanal del trabajo de prácticas: qué hay
que estudiar cada semana y los ejercicios que debe resolver antes y durante la sesión de
prácticas. Como puedes observar, ser estiman 12 semanas lectivas para el cuatrimestre debido
a que se han eliminado las fiestas, semana para formar grupos, etc.

Sema Estudio y propuesta de ejercicios Ejercicios en laboratorio


na
1ª Parte I: Prueba los ejemplos que veas más
Estudia los Apartados 1 a 7 de la Guía difíciles o no hayas entendido bien.
de prácticas Ejercicios 1, 2, 3, 4, 5, 11
2ª Estudia los apartados 8 a 9 Resuelve los ejercicios 14, 7,
3ª Idem para los apartados 10 a 16 Resuelve los ejercicios 6, 8, 9, 10, 12, 13
4ª Parte II: Resuelve los ejercicios 1, 2, 3, 4, 6
Estudiar los apartados del 1 al 7
5ª Idem para el 8 y 9 Resuelve los ejercicios 7, 9, 10, 11, 12, 17
6ª Resuelve los ejercicios 5, 8, 13, 14, 15,
16, 18
7ª Examen del Módulo I
8ª - Estudia los apartados 1 a 5 del Módulo Prueba en prácticas la solución que has
II de la Guía de prácticas. propuesto para el Ejercicio 1.
- Probar los ejemplos, cuyos código
están en la página web de la asignatura.
- Aborda una solución para el Ejercicio
1 (Nota: debes implementar la solución
vista en teoría con la interfaz de hebras).
9ª Diseña una solución para el Ejercicio 2 Implementa la solución al Ejercicio 2
10ª Implementa la solución al Ejercicio 2
(continuación)
11ª Implementa la solución al Ejercicio 2
(continuación)
12ª Examen del Módulo II

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 5


Convenios utilizados en esta guía

Por conveniencia, utilizaremos varios convenios en este documento:


● La letra con esta apariencia indica texto que debemos escribir directamente en el
computador. Por tanto, los nombres de las órdenes están impresos con este formato.
● Se utiliza este tipo de letra para reflejar las salidas dadas por el computador.
● La itálica se usa para los argumentos y opciones de las órdenes: valores suministrados
por el usuario. Por ejemplo, si escribimos:
% cd directorio
indica que debes escribir cd como aparece y sustituir directorio por el nombre del
directorio que desees.
● Los caracteres no imprimibles se representarán utilizando ángulos. Por ejemplo, <ESC>, ó
<ESCAPE>. <CONTROL>-C representa la pulsación de la tecla “c” mientras se tiene pulsada
la tecla <CONTROL>.
En las prácticas interesa distinguir entre órdenes empotradas del shell, y órdenes Unix,
(independientes del shell). Por ello, utilizaremos el siguiente convenio de escritura:

Esto es la sintaxis de una orden del shell: Esto es una orden Unix, su sintaxis es
orden [–opciones] [argumentos] orden –opciones argumentos
junto con una breve explicación de lo que y una breve explicación de la misma.
hace.
Los elementos encerrados entre corchetes, [ ], en la sintaxis de las órdenes indican que
éstos son opcionales.
Cuando trabajamos en Linux, el intérprete de ordenes muestra un indicador (prompt) cuando
está preparado para recibir ordenes, por defecto en el guión es número_orden%, si bien en el
laboratorio será diferente. El siguiente ejemplo muestra la orden date:
3% date # Muestra la fecha y hora del sistema
Fri Sep 5 12:00:00 EST 1998
Podemos ver como usamos la negrita indicando lo que se debe teclear. La salida no lleva
dicho atributo. Además, vemos otro convenio: juntos a las ordenes pondremos, en muchos
casos, un comentario, precedido por el signo #, para indicar qué hace la orden. Este
comentario no debéis escribirlo.
En general, las ordenes Linux admiten opciones que modifican el comportamiento
normal de la orden o suministra alguna entrada a la orden. Estas se indican con letras (-i), o
letras seguidas de un valor (-i 50). Si una orden admite varios indicadores, estos pueden darse
juntos, separados, o una combinación de los dos. Por ejemplo, las siguientes listas de
indicadores hacen lo mismo:
% orden -l -s -t
% orden -lst
% orden -ls -t
Para finalizar, tened presente que Unix es sensible al tipo de letra, es decir, no es lo mismo
escribir una mayúscula que una minúscula. Por tanto, las ordenes y sus argumentos deberán
teclearse tal como aparecen en el texto.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 6


Módulo I - Parte 1:

EL SHELL INTERATIVO
ORDENES DE LINUX
Esta parte se dedica al manejo del shell de forma interactiva e introduce un conjunto
básico de órdenes para comenzar a trabajar con el sistema operativo.
• Estudiaremos como:

 Determinar cual es nuestro shell de entrada,


 Iniciar un shell desde el terminal,
 Cambiar de shell, temporal o permanentemente,
 Cómo reutilizar órdenes escritas con anterioridad
• Presentaremos los metacaracteres que nos ayudan a escribir muchas de las órdenes y
daremos algunos consejos para su uso. Veremos los metacaracteres: (a) sintácticos, (b) de
nombres de archivos, (c) de citación, (d) de entradas/salidas, (e) de expansión/sustitución, y
una miscelánea de metacaracteres.
• En relación con los alias, veremos cómo:
● Definir y eliminar alias, y ver los alias existentes,
● Crear nuevas ordenes, e
● Incluir argumentos en el alias.

● Veremos las siguientes órdenes:


Ordenes Linux Ordenes Linux Ordenes empotradas
cat ps %
chsh pwd alias
cp rmdir at
cut rm bg
date sort cd
file sty echo
find tail exec
grep tar exit
gzip tee fg
gunzip umask history
ls wmstat kill
man wc jobs
mv who logout
mkdir zcat repeat
more set
mtools sched
nice stop
nohup unalias
passwd wait
paste where

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 7


Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 8
1 El Sistema Operativo Linux

El objetivo de esta parte es conocer un conjunto básico de órdenes de Linux y manejar


un shell, el tcshell, interactivamente. Tened presente que los elementos que veamos ahora
también nos servirán en la Parte II para construir programa escritos en el lenguaje tcshell.

1 La estructura y las interfaces de Linux

Como muchos sistemas, Linux puede verse como una pirámide (Figura 1.1). En la base
tenemos el hardware, y sobre él, el sistema operativo. Su función es controlar el hardware y
suministrar la interfaz de llamadas al sistema a todos los programas. Esta interfaz permite a
los usuarios crear y gestionar procesos, archivos, y otros recursos. Como las llamadas al
sistema deben hacerse en ensamblador, el sistema dispone de una biblioteca estándar para
facilitar la labor del programador, y que puede invocarse desde un programa escrito en C.
Además el sistema suministra un gran número de programas estándares o utilidades. Entre
estos se incluyen intérpretes de órdenes, compiladores, editores, y utilidades de manipulación
de archivos (para copiar, mover, ...).

Figura 1.1.- Las capas del sistema Linux.

El objetivo de nuestras prácticas es aprender a: (a) manejar la interfaz de usuario mediante el


uso de órdenes y el shell, y (b) manejar la interfaz de la biblioteca de hebras. En Sistemas
Operativos II se utilizará la interfaz de llamadas al sistema.

2 Los intérpretes de órdenes

Un intérprete de órdenes, o shell en la terminología Unix, está construido como un


programa normal de usuario. Esto tiene la ventaja de que podemos cambiar de interprete de
ordenes según nuestras necesidades o preferencias. Existen diferentes shells: Bourne Again
Shell (bash), TC shell (tcsh), y Z shell. Estos shells no son exclusivos de Linux, se distribuyen

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 9


libremente y pueden compilarse en cualquier sistema Linux. Podemos ver los shell de los que
dispone nuestro sistema mirando en el archivo /etc/shells.
El shell es un programa que básicamente realiza las siguientes tareas:
for(;;) {
imprime indicador de órdenes;
lee la línea de ordenes;
analiza la línea de ordenes (arg0,arg1,...,>,<,|,&,...);
prepara entorno según lo que aparece en línea de ordenes;
crea un proceso para ejecutar orden;
if (estamos en el proceso hijo) {
ejecuta la orden dada en arg0;
else /* es el proceso padre */
if (línea ordenes no aparece el símbolo &)
espera hasta que finalice el hijo;
}
}

El shell, además de ejecutar las órdenes de LINUX, tiene sus propias órdenes y
variables, lo que lo convierte en un lenguaje de programación. La ventaja que presenta frente a
otros lenguajes es su alta productividad -una tarea escrita en el lenguaje del shell suele tener
menos código que si está escrita en un lenguaje como C.
Como recomendación general indicar que es conveniente usar el shell cuando
necesitemos hacer algo con muchos archivos, o hacer la misma tarea repetitivamente. No lo
deberíamos de usar cuando la tarea sea compleja, requiera gran eficiencia, necesite de un
entorno hardware diferente, o requiera diferentes herramientas software.
Respecto a las órdenes, en el tcshell podemos encontrar los siguientes tipos:
1. Alias – son abreviaciones para órdenes existentes que se definen dentro del shell.
2. Ordenes empotradas – rutinas implementadas internamente en el shell.
3. Programas ejecutables – programas que residen en disco.
La ordenación que hemos dado indica el orden en el que se ejecutan. Por ejemplo, si tenemos
un alias con el nombre de un programa ejecutable, primero se ejecutaría el alias.

3 Una sesión de trabajo

Cuando arrancamos el sistema, tras muchos mensajes de inicialización, aparece en la


consola el mensaje siguiente:
Red Hat Linux release 7.1 (Seawolf)
Kernel 2.4.2-2 on an i856
login: x0000000
password: ********
Cuando damos el login y password, el programa /bin/login verifica nuestra identidad: 1)
comprueba que nuestro login coincide con el primer campo de algunas de las líneas del
archivo /etc/passwd; 2) Si nuestro nombre esta allí, encripta y compara el password, o palabra
clave, que hemos dado con el que hay en el archivo; 3) una vez verificado, el programa login
establece el entorno de trabajo que se pasará al shell, es decir, se asignan las variables HOME,
SHELL, USER, y LOGNAME de los valores extraídos del archivo /etc/passwd (estas variables las
veremos en la Parte II). Después, se crea el shell de entrada o login shell, con lo que podemos
iniciar la sesión1 de trabajo.

1 Denominamos sesión a todo el trabajo realizado desde que entramos al sistema (login) hasta que salimos
(logout).

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 10


La palabra clave debe mantenerse en secreto para nuestra protección y cambiarse con
cierta frecuencia (al menos dos veces al año) y para su elección debemos seguir las reglas
básicas vistas en clase de teoría. Podemos cambiar la clave de acceso con la orden passwd. En
nuestro sistema, dada la configuración especial que tiene, debemos ejecutar la orden passwd en
el servidor, y no en nuestro PC, ya que si no el password cambiado se pierde cuando apaguemos
el PC. Por ejemplo, para cambiar la clave:
% passwd
Changing password for x0000000
(current)LINUX password:
New LINUX password:
Retype new LINUX password:
Passwd: all authentication token update successfully
Una vez en el sistema, disponemos de un manual en-línea para consultar la sintaxis y
opciones de sus órdenes, e incluso algunos ejemplos de como se utiliza. El manual en línea se
puede consultar con la orden man, cuya sintaxis es:

man [opciones] [sección] orden


Por ejemplo, si queremos ver que hace y cuales son las opciones de la orden ls, que lista el
contenido de un directorio, podemos ejecutar
% man ls
Sólo comentar la opción -k palabra_clave que imprime la sección del manual que contiene la
palabra clave. Esta es muy útil cuando sabemos lo que queremos hacer pero no conocemos la
orden que lo hace. La orden apropos palabra_clave realiza la misma función. P. Ej., si
queremos buscar programas shells, podemos solicitar
% man -k shell
...
mc (1) – visual shell for Linux-like systems
rsh (1) – remote shell
shar (1) – create shell archives
...

También, podemos visualizar los archivos de documentación con la orden info.

4 El sistema de archivos

Uno de los elementos del sistema con el que interaccionamos muy frecuentemente es el
sistema de archivos. En Linux, todos los archivos están almacenados en una jerarquía con
estructura de árbol, que consta de archivos y directorios, similar al Windows. En la parte más
alta del árbol esta el directorio raíz, representado con /, de él cuelgan todos los directorios y
archivos del sistema. Cuando el nombre de un archivo comienza por /, estamos indicando que
es un nombre absoluto (cuelga del directorio raíz). Si el nombre no comienza con /, hablamos
de un nombre relativo. Cuando encontramos el símbolo / entre el nombre de un archivo o
directorio, éste se utiliza simplemente como separador.
Los directorios de primer nivel, y posteriores, que aparecen en un sistema Linux
pueden variar de unas instalaciones, y versiones, a otras, pero casi siempre encontraremos
algunos directorios comunes a todos los sistemas. Por ejemplo, algunos de ellos son:
/bin Directorio con programas ejecutables del sistema

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 11


/boot Imagen del sistema
/dev Directorio de dispositivos
/etc Directorio administrativo y archivos de configuración
/home De él cuelgan los directorios de los usuarios
/lib Bibliotecas de programación estándares
/sbin Ordenes de administración del sistema
/tmp Directorio para archivos temporales
/usr Directorio de usuarios
/usr/bin Directorio de programas ejecutables de aplicación
/usr/X11 Archivos del sistema de ventanas X
/usr/lib Archivos de configuración de paquetes y bibliotecas
/usr/local/bin Ordenes añadidas localmente
/usr/src/linux Archivos fuentes del kernel de Linux
/var Desbordamiento para algunos archivos grandes
...
Al entrar al sistema, este nos asigna un directorio por defecto, nuestro directorio home. En
muchos casos el nombre de este directorio se hace coincidir con el nombre del usuario y suele
estar situado en el directorio /home. Por ejemplo, /home/x0000000. En sistemas como el
nuestro, con miles de usuarios, los directorios homes están ubicados en otros directorios.
La Tabla 1.1 recoge las órdenes más frecuentes relacionadas con archivos y directorios.
Tabla 1.1.- Ordenes LINUX relacionadas con archivos y directorios.

Ordenes Descripción
ls archivo_directorio Lista los contenidos de un directorio
cd directorio Orden empotrada que nos cambia de directorio de trabajo.
Las abreviaciones . y .. se pueden utilizar como referencia
de los directorios actual y padre, respectivamente. Es una
orden empotrada del shell.
pwd Imprime camino absoluto del directorio actual
mkdir directorio Crea un directorio
rmdir directorio Borra un directorio existente (si esta vacío)
cat archivo(s) Orden multipropósito: muestra el contenido de un archivo o
varios, concatena archivos, copia un archivo, crea un archivo
de texto, o muestra los caracteres invisibles de control.
cp archi1 archi2 Copia archivos
mv fuente destino Renombra archivos (la fuente pueden ser archivos o
directorios, al igual que el destino).
more archivo(s) Visualiza un archivo fraccionándolo una pantalla cada vez
(existen otros paginadores como page, pg, etc.)
file archivos(s) Determina el contenido de un archivo o archivos (¡muy útil si
lo usamos antes de visualizar, con cat, el contenido de un
archivo que no estamos seguros si ASCII!)
rm DirectorioArchivos Borra archivos y directorios

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 12


Vamos a comenzar con la orden ls para ver el contenido de un directorio.

La orden ls (list source) muestra los archivos contenidos en el directorio que se especifica
(si no se especifica nada es el directorio actual)
ls [-OPTION]... [FILE]..
Algunas de las opciones más corrientes:
-a lista los archivos del directorio actual, incluidos aquellos cuyo nombre comienza con un
punto, “.”.
-C lista en formato multicolumna.
-l formato largo (ver descripción a continuación).
-r lista en orden inverso.
-R lista subdirectorio recursivamente, además del directorio actual.
-t lista de acuerdo con la fecha de modificación de los archivos.
....

Por ejemplo, con la opción -l veremos los archivos del directorio en “formato largo”,
que da información detallada de los objetos que hay dentro del directorio. Para cada entrada
del directorio, se imprime una línea con la siguiente información:

El significado de la información lo iremos viendo a lo largo del guión. De momento,


comentar dos de los campos:
 Tipo de objeto – Un carácter indica el tipo de objeto que representa esa entrada: un -
(guión) indica que es un archivo plano o regular; una d que es un directorio; la c que es un
archivo de dispositivo orientado a carácter; la b, dispositivo orientado a bloques; y la p
que es un FIFO (estos los estudiaremos en SOII).
 Bits de protección – Parte de la protección Linux descansa en la protección de los archivos.
Cada archivo tiene asociados 12 bits de protección que indican qué operaciones podemos
realizar sobre él, y quién puede hacerlas. La orden ls muestra sólo 9 bits a través de una
cadena de la forma genérica rwxrwxrwx donde cada letra representa un bit (un permiso). El
significado de los bits se muestra en la Figura 1.2. La pertenencia a un grupo la establece el
administrador del sistema cuando lo crea (en nuestra sistemas, todos los alumnos pertenecen
al mismo grupo).

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 13


Figura 1.2.- Significado de los bits de protección.

Algunos ejemplos de las otras órdenes antes citadas:


• Copiar los archivos ../x3333/ejemplo ../x3333/mensaje del directorio x3333, que esta al
mismo nivel que en el que estamos situados, en el directorio midir.
% cp ../x3333/ejemplo ../x3333/mensaje midir
● Asigna el nuevo nombre megustamas a un archivo existente, denominado viejonombre:
% mv viejonombre megustamas
• Creamos un directorio para prácticas de sistemas operativos I y borramos un directorio
temporal que no nos sirve:
% mkdir SOI # crea el directorio SOI
% rmdir temporal # Borra temporal si esta vacío
● Ver el tipo de contenido de uno o varios archivos, para por ejemplo, visualizar el contenido
de aquellos que son texto:
% file practica1 programa.c a.out
practica1: ASCII text
programa.c: ISO-8859 C program text
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 ...
% cat practica1 programa.c
...
En ocasiones, podemos por error visualizar un archivo con cat que contiene caracteres no
imprimibles, por ejemplo, un archivo ejecutable. Esto nos suele dejar el terminal con caracteres
extraños. Pues bien, podemos restablecer el estado normal del terminal con la orden
% setterm –r

5 El shell de entrada

Suponiendo una instalación general, si deseamos trabajar con tcsh deberemos ver cual es
el shell de entrada del sistema y entonces, si no es el deseado, cambiarlo de forma temporal o
permanente.

• Determinación del shell de entrada y cambio del mismo

Cuando entramos al sistema, Linux inicia un shell por defecto, este es el shell de entrada
o login shell. Es el administrador del sistema el que establece este shell cuando crea un usuario
(ver el último campo del archivo /etc/passwd). Nota: cuando estamos en el entorno de ventanas
y lazamos un xterm, este shell no es el login shell. Hacemos esta distinción, pues en ocasiones
las propiedades de un login shell y uno que no lo sea, son diferentes.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 14


¿Cómo sabemos cual es el shell asignado por defecto? Cuando se inicia el shell, este
asigna a una variable el valor del shell actual. Esta variable se llama $SHELL. Podemos ver el
valor de la variable con la orden empotrada echo:
% echo $SHELL
/bin/tcsh
¡Ojo! En algunos sistemas al ejecutar esta orden, el shell nos podría decir que utilizamos
el /bin/csh, pero si ejecutamos ls -l /bin/csh veremos que este es un enlace al /bin/tcsh, por
lo que no debemos cambiarlo.
Podemos variar el shell por defecto con la orden chsh (change shell) pero su efecto sólo
tiene lugar cuando nos salimos del sistema y volvemos a entrar. De nuevo, en la instalación de
nuestra Escuela, para que el cambio perdure después de apagar la máquina, tendríamos que
cambiarlo en el servidor.
Se puede obtener un cambio temporal de shell invocando directamente a un programa
shell desde el indicador de ordenes del shell en el que estamos. P. ej. si estamos en el Bourne
Shell y queremos pasar al tcsh, solo tendremos que ejecutar
$ tcsh
Este cambio permanecerá mientras no finalicemos el shell actual con la orden exit.

● Finalizar la sesión de trabajo

Nuestra sesión de trabajo durará hasta que nos despidamos del sistema. La despedida
puede hacerse de varias formas: con las ordenes empotradas exit o logout (según los sistemas
podremos usar una u otra; en otros podremos usar las dos); o bien, pulsando <CONTROL>-D
(aunque a veces está desactivado). La sintaxis de estas órdenes citadas es:
• exit – orden empotrada que termina la ejecución del shell y, opcionalmente, puede
suministrar información al proceso que invocó el shell.
exit [expresion]
Esta orden finaliza al shell y devuelve opcionalmente el valor de la expresión al entorno
de invocación del shell.

La expresión de exit debe formarse para que genere un único valor entero. Si aparece
más de un valor, deben encerrarse entre paréntesis. Algunos ejemplos:
35% tcsh
36% exit 4
37% echo $status
4
38% tcsh
39% exit ( 4 + 2 ) # Devuelve resultado de evaluar expresión
40% echo $status
6
41% tcsh
41% exit valor # Valor no definido
exit: Expression syntax.
42% exit 4+2 # Valor no definido, expresión mal formada
exit: Badly formed number.
El uso principal de exit es retornar un valor al proceso padre. Si el padre del proceso que
finaliza es otro C Shell, el resultado se encontrará en la variable $status.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 15


• logout - es otra orden para terminar el shell.
logout
Termina el shell. No tiene argumentos. Un sinónimo de esta es la orden bye.

logout también comprueba que la sesión es la sesión de un “shell de entrada”. Si no lo fuera, no


permitiría su fin y emitiría el mensaje “No es un shell de ingreso.”, en cuyo caso, debemos de
usar exit. La orden logout no informa del estado al proceso padre, y ejecuta el archivo .logout
en el directorio home antes de acabar.

6 Historia de órdenes

Veamos cómo el shell mantiene una historia de las órdenes escritas previamente, y cómo
volver a reutilizarlas y modificarlas, para hacer más fácil y productivo el trabajo interactivo.

• Asignando la variable de historia

La variable $history establece el número de ordenes que el shell mantiene en su


historia antes de descartar la orden salvada más antigua. Posiblemente, tengamos asignado un
valor como parte de la configuración inicial. Podemos verlo con la orden set que muestra el
valor de las variables definidas en nuestra sesión.
% set
argv ()
cwd /usr/minombre
history 20
ignoreeof
...
La primera columna refleja el nombre de la variable y la segunda su valor. En este caso
la variable history tiene un valor de 20, adecuado en la mayoría de los casos.

• Viendo la historia

La orden history muestra una lista de las órdenes de la historia. Cada orden va
precedida de un número que indica la ocurrencia de la misma en la historia. En nuestro caso:
% history
1 echo $SHELL
2 chsh -s /bin/tcsh
3 tcsh
4 set
5 history

• Repitiendo ordenes con el editor de ordenes

Podemos utilizar un editor de órdenes para recuperar y ejecutar órdenes. Con las
flechas arriba y abajo del teclado podemos movernos hacia arriba y abajo en la historia de
órdenes, siempre que la variable history este definida. Cada orden va apareciendo en pantalla
según la recuperemos con el cursor situado al final. Podemos pulsar <RETURN> para
ejecutarla, o <CONTROL>-C para cancelarla. Podemos buscar una orden en la lista que
comience con cadena, para ello escribimos cadena y pulsamos <ESCAPE>-P. También,
podemos editar las órdenes para reciclarlas. Para la edición utilizamos las flechas izquierda y

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 16


derecha para movernos por la línea, y podemos borrar o insertar para modificar según nuestra
necesidad la orden recuperada. Si para el borrado o inserción no se reconocen las teclas
correspondientes, podemos editarlas al estilo del editor vi (consultar manual).

• Repitiendo ordenes con el signo bang (!)

Otra forma para reutilizar órdenes es usar el signo de admiración, denominado bang.
Las formas para recuperar una orden son:
% !numero-orden # Invoca una orden conocida su posición en la historia
% !! # Invoca la última orden ejecutada
% !-numero # Invoca orden situada el número lugares atrás en historia
% !cadena_texto # Invoca orden que comienza por cadena_texto
% !?cadena_texto[?] # Invoca orden que contiene cadena_texto

Supongamos que estamos en la orden 6, diferentes formas de invocar la orden 4 son:


7% !4 # Damos el valor absoluto de la posición en la historia
...
8% !-2 # Damos la posición relativa en la historia
...
9% !se # Ejecutamos la orden que comienza por “se”
...
10% !?et # Ejecutamos la orden que contiene la subcadena “et”
...
En la orden número 10, indicamos que queremos igualar la orden más reciente que
contiene el texto en cualquier parte de la cadena de orden. Podremos seguir añadiendo texto a
una línea si finalizamos con una “?”. Del ejemplo anterior, reutilizamos la escritura de la orden
set para declarar una variable
11% !?et? Variable=“texto de prueba”
set Variable=“texto de prueba”

• Modificación de los designadores palabras de la historia

Siguiendo a un designador de palabra (o a un especificador de evento si no hay palabra)


podemos añadir un modificador de palabra:
! evento[:palabra][:modificador]
Los modificadores cambian la forma en la que es tratado el evento actual. Algunos de los
modificadores más útiles aparecen en la Tabla 1.2.
Tabla 1.2.- Modificadores de las referencias de la historia.

Modificador Descripción
p Imprime el resultado de una orden sin ejecutarla
r Raíz del nombre de archivos
e Extensión del nombre de archivo (parte que sigue al punto)
h Cabecera del pathname (todo salvo el último componente)
t Final de pathname (último componente)

Vamos a ver algunos ejemplos, :p es útil cuando no estamos seguros si la referencia que
vamos a hacer es la que deseamos. Con él, veremos la orden sin ejecutarla, y una vez que
estemos seguros sólo tenemos que hacer !!
17% !cat *:p

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 17


cat secundario.c principal.c
18% !!
Para finalizar, veremos el uso de los modificadores de nombres de archivos, que
permiten extraer un trozo particular del nombre de un archivo:
:h (cabecera) :t (final) :r (raíz) :e (extensión)
Nombre archivo
/usr/include/stdio.h /usr/include stdio.h /usr/include/stdio h
intro.ms - intro.ms intro ms
readme - readme readme -

Son útiles cuando debemos de escribir un nombre similar a uno ya escrito:


27% more /usr/local/include/etm.h
28% more !$:h/nio.h # cabecera ultimo argumento evento anterior
more /usr/local/include/noi.h

7 Metacaracteres

Todos los shells poseen un grupo de caracteres que en diferentes contextos tienen
diferentes significados, los metacaracteres. Estos juegan un papel importante cuando el shell
esta analizando la línea de órdenes, antes de ejecutarla. Vamos a verlos agrupados según la
función que realizan.

7.1 Metacaracteres sintácticos


Sirven para combinar varias órdenes de LINUX y construir una única orden lógica.
Suministran una forma de ejecución condicional basada en el resultado de la orden anterior. La
Tabla 1.3 muestra estos caracteres y da una descripción de su función.
Los metacaracteres sintácticos permiten materializar la filosofía de trabajo de LINUX de
caja de herramientas: dadas las ordenes que necesitamos para realizar un trabajo, los
metacaracteres sintácticos permiten componerlas (“pegarlas”) para resolverlo. Esta filosofía es
en gran medida la responsable de que las órdenes de LINUX realicen funciones muy concretas,
y sean parcas en dar información al usuario.

• Uniendo órdenes con ;

El uso del punto y coma (;) permite escribir dos o más órdenes en la misma línea, que
serán ejecutadas secuencialmente, como si se hubiesen dado en líneas sucesivas. En programas
shell, se utiliza por razones estéticas (permite una asociación visual entre ordenes relacionadas).
En el indicador de órdenes, permite ejecutar varias ordenes sin tener que esperar a que se
complete una orden para introducir la siguiente.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 18


Tabla 1.3.- Metacaracteres sintácticos.

Metacaracter Descripción de la función


; Separador entre órdenes que se ejecutan secuencialmente.
| Separación entre órdenes que forman parte de un cauce (pipeline). La salida de la
orden a la izquierda del separador es la entrada de la orden a la derecha del
separador.
() Se usan para aislar ordenes separadas por ; ó |. Las ordenes dentro de los
paréntesis, ejecutadas en su propio shell, son tratadas como una única orden.
Incluir un cauce dentro de paréntesis, nos permite a su vez incluirlo en otros
cauces.
& Indicador de trabajo en segundo plano (background). Indica al shell que debe
ejecutar el trabajo en segundo plano.
|| Separador entre órdenes, donde la orden que sigue al || sólo se ejecuta si la orden
precedente falla.
&& Separador entre ordenes, en la que la orden que sigue al && se ejecuta sólo si la
orden precedente tiene éxito.

En el ejemplo siguiente, utilizamos la orden pwd que nos permite ver el camino absoluto
del directorio de trabajo actual, y la orden cd (para cambiar de directorio).
1% pwd # camino del directorio actual
/home/x3333333
2% cd sistopei ; ls # Cambiamos de directorio y hacemos ls.
miprograma1
miprograma2
3% pwd # estamos en el directorio especificado antes
/home/x3333333/sistopei

• Creando cauces con |

Antes de ver los cauces, debemos comentar algo sobre el entorno de ejecución de los
programas en LINUX. Parte del entorno de ejecución de un programa son los archivos que
utiliza. Cuando el sistema operativo crea un proceso, le abre tres archivos: la entrada estándar
(stdin), la salida estándar (stdout) y el error estándar2 (stderr), que se corresponden con los
descriptores de archivo (o handles) 0, 1 y 2, respectivamente (ver Figura 1.3). Por defecto,
estos archivos se corresponden con el teclado para la entrada estándar, y con la pantalla para la
salida y error estándares, pero como veremos en breve, estas asociaciones se pueden cambiar.

Figura 1.3.- Entrada, salida y error, estándares.


Un cauce conecta la salida estándar de la orden que aparece a la izquierda del símbolo |
con la entrada estándar de la orden que aparece a la derecha. El flujo de información entre
ambas órdenes se realiza a través del sistema operativo, como muestra la Figura 1.4.

2 Aunque se denomine error estándar, en él escriben mensajes de diagnóstico, no necesariamente


de error, como veremos más adelante.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 19


Para ver un ejemplo, usaremos la orden who que produce un informe de los usuarios que
están conectados actualmente al sistema. Cada una de las líneas que muestra hace referencia a
un usuario, y da su nombre de usuario, el terminal al que esta conectado, y la fecha y hora de
entrada al sistema.
4% who
maria tty2 May 10 13:35
luis tty6 May 10 9:00
¿Qué debemos hacer si queremos obtener esta misma información pero ordenada
alfabéticamente? La solución es sencilla, usar la orden sort que sirve para clasificar.
5% who | sort # La salida de who se utiliza como entrada de sort
luis tty6 May 10 9:00
maria tty2 May 10 13:35
¿Qué ha ocurrido? La salida de who se ha pasado como entrada a la orden sort.
Gráficamente lo podemos ver en la Figura 1.4:

Figura1.4.- Flujo de información a través de una cauce.


Esto nos sirve para comentar lo siguiente: las órdenes de Linux están construidas siguiendo el
siguiente convenio: cuando en una orden no se especifican archivos de entrada y/o salida, el
sistema toma por defecto la entrada o salida estándar, respectivamente. Por ejemplo, ¿qué
ocurre si ejecutamos sort sin argumentos? Sencillo, se ordena la entrada estándar y se envía a la
salida estándar.
6% sort
escribimos algunas lineas
esperando saber cual sera
el resultado de ellas
<Ctrl>-d # El caracter <CTRL>-D indica fin de archivo
el resultado de ellas
escribimos algunas lineas
esperando saber cual sera

• Combinado ordenes con ()


En ocasiones, necesitaremos aislar un cauce, o una secuencia de punto y coma, del resto
de una línea de órdenes. Para ilustrarlo, vamos a usar las ordenes date, que nos da la fecha y
hora del sistema, y la orden wc, que nos permite conocer cuantas líneas, palabras y caracteres,
tiene el archivo que se pasa como argumento. Por ejemplo, las siguientes órdenes tienen
resultados diferentes:
8% date; who | wc # ejecuta date; ejecuta who cuya salida pasa a wc
Wed Oct 11 10:12:04 WET 1995
1 5 31
8% (date; who) | wc # ejecuta date y who, sus salidas se pasan a wc
2 12 64

• Ejecutando ordenes en segundo plano con &

Linux permite dos formas de ejecución de órdenes:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 20


- Ejecución de órdenes en primer plano (foreground) – en este caso, el indicador de órdenes
no aparece hasta que ha finalizado la orden que mandamos ejecutar. Por ejemplo, las órdenes
que hemos ejecutado hasta ahora han sido de este tipo.
- Ejecución en segundo plano (background) – en este segundo caso, el indicador reaparece
inmediatamente, lo cual nos permite ejecutar otras ordenes aunque la que se lanzó en
segundo plano no haya terminado.
Para ejecutar una orden en segundo plano sólo debemos finalizar la línea de ordenes con &.
Supongamos que deseamos realizar la compilación de un programa escrito en C y que la misma
dura algunos minutos, si queremos seguir trabajando mientras el sistema realiza el trabajo
podemos ejecutarla en segundo plano:
9% gcc miprograma.c & # compilación lanzada de fondo
[1] 1305
10% ls # seguimos mientras otro proceso realiza la compilacion
Vemos como el shell responde al carácter & indicando el número del trabajo que hemos
lanzado, que aparece entre corchetes, seguido del identificador del proceso que ejecuta la orden.
El número de trabajo nos servirá para controlar los procesos en segundo plano, pero eso lo
veremos más tarde. Otros ejemplos:
10% who | wc & # lazamos de fondo un cauce
[2] 1356 1357
11% (cd midirectorio ; ls) & # lanzamos de fondo una secuencia de ordenes
[3] 1400
12% cd midirectorio & ls & # & sirve de separador; perdemos la secuencialidad
[1] 1402
[2] 1403 # las dos órdenes se ejecutan concurrentemente

• Ejecución condicional de ordenes con || y &&

Estos dos metacaracteres permiten la ejecución condicional de órdenes basada en el


estado de finalización de una de ellas. Separar dos ordenes con || ó &&, provoca que el shell
compruebe el estado de finalización de la primera y ejecute la segunda sólo si la primera falla o
tiene éxito, respectivamente.
Para ver un ejemplo, introduciremos la orden grep, que busca un patrón dentro de
un(os) archivo(s) de texto, y muestra en stdin cada línea que iguale el patrón. Su sintaxis:
grep [-options] patrón archivo(s)

donde algunas de las opciones son:


-n precede cada línea con un el número de línea
-v iguala las líneas que NO contienen el patrón.
Patrón puede estar formado por expresiones regulares, que nos permiten buscar patrones
más complejos, pero en nuestra introducción sólo veremos cadenas de texto. Existen las
ordenes fgrep más rápida y que sirve solo para buscar literales, y la orden egrep para
expresiones compuestas.
Ejemplos:
% grep mivariable programa.c #busca mivariable en el fuente de C
% grep “a nivel de” cap1.txt #busca patrón con espacios en blanco

Si queremos mostrar un mensaje que indique si está conectado un usuario, podemos ejecutar:
13% who | grep x3333 || echo "Usuario x3333 no conectado."
Usuario x3333 no conectado.
14% who | grep x5555 && echo “Usuario x5555 conectado.”
x5555 tty04 Sep 27 9:00

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 21


Usuario x5555 conectado.
No hay restricción que limite el número de órdenes que aparecen antes del ||, pero sólo se
evalúa el estado de la última. Por otra parte, el ejemplo permite ver otro uso de echo: muestra en
pantalla el literal que se le especifica.

7.2 Metacaracteres de nombre de archivos


Se utilizan para formar patrones de igualación en la sustitución de nombres de archivos
con el fin de poder referenciar de forma abreviada una serie de archivos cuyos nombres siguen
un patrón. La Tabla 1.4 muestra estos metacaracteres.
Tabla 1.4.- Metacaracteres de nombres de archivos.

Metacaracter Descripción de la función


? Iguala cualquier carácter simple.
* Iguala cualquier secuencia de cero o más caracteres.
[] Designan un carácter o rango de caracteres que, como una clase, son igualados
por un simple carácter. Para indicar un rango, mostramos el primer y último
carácter separados por un guión (-).
{} Abreviar conjuntos de palabras que comparten partes comunes.
~ Se usa para abreviar el camino absoluto (path) del directorio home.

• Igualando un carácter simple con ?

Por ejemplo, para escribir este guión de prácticas hemos separado cada sesión en un
archivo y los hemos nombrado Sesion.1, Sesion.2, Sesion.3, etc. Si quisiésemos obtener
información de los archivos con ls podríamos dar las órdenes:
13% ls -l Sesion.1 Sesion.2 Sesion.3
-rw-r--r-- 1 jose profe 2430 Sep 17 13:20 Sesion.1
-rw-r--r-- 1 jose profe 2540 Sep 18 10:10 Sesion.2
-rw-r--r-- 1 jose profe 5500 Sep 18 10:30 Sesion.3
14% ls -l Sesion.? # forma mas compacta; cada ? iguala un caracter
-rw-r--r-- 1 jose profe 2430 Sep 17 13:20 Sesion.1
-rw-r--r-- 1 jose profe 2540 Sep 18 10:10 Sesion.2
-rw-r--r-- 1 jose profe 5500 Sep 18 10:30 Sesion.3

• Igualando cero o más caracteres con *

El carácter especial * iguala cero o más caracteres. Por ejemplo, los archivos listados en
las órdenes 13 y 14, podemos verlos usando la cadena de igualación Sesion.*:
15% ls -l Sesion.* # forma mas compacta; * iguala uno o más caracteres
-rw-r--r-- 1 jose profe 2430 Sep 17 13:20 Sesion.1
-rw-r--r-- 1 jose profe 2540 Sep 18 10:10 Sesion.2
-rw-r--r-- 1 jose profe 2540 Sep 18 10:10 Sesion.3
-rw-r--r-- 1 jose profe 740 Sep 18 10:40 Sesion.apendice

Observad que en Linux no existe el concepto de nombre y extensión a la hora de nombrar


archivos. El punto es un carácter más permitido en el nombre de un archivo.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 22


• Igualando cero o más caracteres con []

Los corchetes definen una lista, o clase de caracteres, que se puede igualar con un sólo
carácter. Como veíamos en la Tabla 1.4, cada clase puede contener un rango. A continuación,
ilustramos algunas formas de caracterizar grupos de archivos:
[A-Z]* Iguala todos los archivos que comienzan con una letra mayúscula.
*[aeiou] Iguala cualquier archivo que finaliza con una vocal.
tema.*[13579] Iguala los temas que finalizan con un número impar.
tema.0[1-3] Iguala tema.01, tema.02, y tema.03.
[A-Za-z][0-9]* Iguala los archivos que comienzan con una letra (mayúscula o
minúscula), seguida de un dígito, y cero o más caracteres.
Un ejemplo:
% cat *.[ch] # visualiza contenido de archivos finalizados en .c y .h
-rw-r--r-- 1 jose profe 2540 Sep 18 10:10 hebras.c
-rw-r--r-- 1 jose profe 2540 Sep 18 10:10 pipes.c
-rw-r--r-- 1 jose profe 740 Sep 18 10:40 hebras.h

• Abreviando nombre de archivos con {}

El uso de llaves ({}) solas o combinadas con los anteriores caracteres especiales (?, *,
[ ]) permite formar expresiones de nombres de archivos más complejas. Las llaves contienen una
lista de uno o más caracteres separados por comas. Cada ítem de la lista se utiliza en turno para
expandir un nombre de archivo que iguala la expresión en la que están inmersas las llaves.
Por ejemplo, a{f,e,d}b se expande en afb, aeb, y adb, en este orden exactamente. Las
llaves se pueden utilizar más de una vez en una expresión. La expresión s{a,e,i,o,u}{n,t} se
expande en san, sen, sin, son, sun, sat, set, sit, sot y sut.

• Abreviación del directorio home con ~

Cuando utilizamos la tilde (~), el shell la sustituye por el camino absoluto de nuestro
directorio home. La tilde puede ir seguida del nombre de un usuario del sistema. En tal caso, el
shell la sustituye por el camino del directorio home de ese usuario.
16% pwd
/tmp
17% cd ~/sistopei
18% pwd
/d5/home/x3333333/sistopei
19% cd ~/jose
20% pwd
/d1/home/jose
Importante: El uso de la tilde en programas permite que éstos sean adaptables al entorno, ya
que elimina la necesidad de codificar en los programas los caminos a los directorios de usuarios,
o ejecutar una búsqueda elaborada para encontrar el directorio home de un usuario.

7.3 Metacaracteres de citación


Los tres caracteres de citación, Tabla 1.5, se usan para proteger el resto de
metacaracteres de la expansión o interpretación del tcsh. Denominamos expansión al
procedimiento por el cual el shell sustituye la ocurrencia de un carácter especial por una lista.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 23


Tabla 1.5.- Metacaracteres de citación.

Metacaracter Descripción de la función


\ Evita que el carácter que le sigue sea interpretado como un metacarácter por el
shell.
" Evita que la cadena de caracteres encerrada entre dobles comillas sea
interpretada como metacaracteres.
' Evita que la cadena de caracteres encerrada entre comillas simples sea
interpretada como ordenes o metacaracteres.

• Eludir metacaracteres con \


Cuando el shell procesa una orden, lo primero que hace es sustituir los metacaracteres
por sus respectivos valores para que en la ejecución aparezcan los valores reales, no los
metacaracteres. "Eludir" metacaracteres significa evitar su interpretación por el shell, de forma
que estos permanecen en la línea de ordenes para ser procesados en la ejecución.
Aunque hablaremos de variables del shell más adelante, de momento comentaremos que
la orden set nos permite definir variables del shell. La sintaxis es sencilla set
variable=valor. Los ejemplos siguientes permiten ver lo que debemos hacer para que dentro
del contenido de una variable aparezcan las comillas dobles:
21% set sincomas="Sin comas" # es necesario las “” porque hay espacio en blanco
22% echo $sincomas
Sin comas
23% set concomas=\"Comas\" #las “” forman parte de la cadena
24% echo $concomas
“Comas"
25% echo * # lista el contenido del directorio, igual a ls
prueba practica.c texto
26% echo \* #evitamos la expansion del *
*
En las órdenes 23 y 26, dado que las comillas y el *, respectivamente, están precedidas
por la barra invertida (\), éstas eluden la interpretación del shell y se consideran parte del valor
de la variable o de la cadena.
Otro ejemplo, supongamos que queremos crear unos archivos con nombres abc;1 y
Trabajo Para Casa. Si escribiésemos estos nombres como tal, el shell interpretaría el ; y el
espacio en blanco como separadores, respectivamente (haz la prueba para ver que dice). Para
evitarlo podemos escribir:
27% mv abc1 abc\;1
28% mv TrabajoParaCasa Trabajo\ Para\ Casa
• Protegiendo metacaracteres con "

La barra invertida sólo protege al carácter que la sigue inmediatamente, por lo que no es
cómodo para proteger cadenas largas. En estos casos, podemos proteger la cadena completa
usando las comillas dobles. Estas desactivan el significado especial de los caracteres entre ellas,
salvo los de !evento, $var y `cmd` (esta última, la veremos en breve) que representan la
sustitución de la historia, variables y ordenes, respectivamente. Ejemplo:
25% set usuario=Pepito
26% echo "**** Hola $usuario"
**** Hola Pepito ****
27% _

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 24


Observa cómo los metacaracteres * no se han expandido, pero la variable $usuario si ha
sido sustituida por su valor.

• Protegiendo con ' ordenes, variables y metacaracteres


Las comillas simples son parecidas a las comillas dobles, desactivan el significado
especial de algunos caracteres salvo la expansión de la historia de órdenes (!evento).
28% set texto="mi variable"
29% set dentro="valor de $texto"
30% echo $dentro
valor de mi variable
31% set dentro='valor de $texto'
32% echo $dentro
valor de $texto1
33% echo ‘!hola’ # no protege a !evento
hola: no se encuentra el comando
Tened presente que la acotación de cadenas es compleja, especialmente las cadenas que
a su vez contienen metacaracteres de citación. Recordad que los metacaracteres de citación no se
utilizan igual que en la escritura normal. Suele ser más fácil delimitar partes de una cadena que
la cadena completa.

7.4 Metacaracteres de entrada/salida o de redirección

Los metacaracteres de entradas/salidas son otra de las características distintivas de


LINUX. Con ellos, podemos redireccionar (tomar) la entrada de una orden desde un archivo en
lugar del teclado, redireccionar (llevar) la salida estándar a un archivo en lugar de a la pantalla, o
encauzar su entrada/salida a/desde otra orden. También podremos mezclar los flujos de
información de la salida y el error estándares para que salgan juntos por la salida estándar.
Tenemos un resumen en la Tabla 1.6.
Tabla 1.6.- Metacaracteres de entradas/salidas, o de redirección.

MetacaracterDescripción de la función
< nombre Redirecciona la entrada de una orden para leer del archivo nombre.
> nombre Redirecciona la salida de una orden para escribir en el archivo nombre. Si
nombre existe, lo sobreescribe.
>& nombre La salida de stderr se combina con stdout, y se escriben en nombre.
>> nombre La salida de la orden se añade al final del archivo nombre.
>>& nombre Añade la salida de stderr, combinada con stdout y las añade al final de nombre.
<< palabra Mecanismo documentación aquí, lo veremos en breve.
| Crea un cauce entre dos órdenes. La salida estándar de la orden a la izquierda
de símbolo se conecta a la entrada estándar de la orden de la derecha del signo.
|& Crea un cauce entre dos órdenes, con las salidas de stderr y stdout de la orden
de la izquierda combinadas y conectadas con la entrada de la orden de la
derecha.

• Redirección de la entrada estándar con <

Algunas órdenes LINUX toman su entrada de archivos cuyo nombre se pasa como
argumento, pero si éstos no se especifican, como vimos anteriormente, la lectura se lleva a cabo

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 25


de stdin. Otras órdenes sólo leen de la entrada estándar, por lo que si queremos que lean de
archivo debemos utilizar la redirección <.
Por ejemplo, la orden mail (programa básico de correo electrónico) lee su entrada de
stdin y no acepta un nombre de archivo como argumento (espera que escribamos el mensaje por
teclado). Sin embargo, podemos escribir el mensaje en un archivo y pasárselo a la orden:
36% cat >mimensaje
Hola Juan: este es un mensaje de prueba
<CTRL>-d
37% mail x333333 < mimensaje
38% _
Como podemos observar, la orden cat >mimensaje indica al shell que el contenido de
la entrada estándar debe ser incluido en el archivo mimensaje.
• Redirección de salida con >, y >&

Las salidas de las órdenes van a stdout que suele estar asociada a la pantalla. Por otro
lado, muchas órdenes escriben mensajes de error, o simplemente información adicional, en
stderr. La redirección de la salida estándar permite enviar las salidas a un archivo, pero la salida
de stderr sigue produciéndose por pantalla. Para que no salga nada por pantalla, podemos
redireccionar además stderr utilizando >&. Lo que el tcshell no permite es redireccionar solo
stderr. Los ejemplos muestran los diferentes metacaracteres de redirección.
40% ls > temporal # El resultado de ls se almacena en temporal
41% cat temporal
archiv1
practicas
proyecto
42% cat temporil # Suponemos que temporil no existe
cat: temporil: No existe el fichero o el directorio
43% cat temporil > errores # nos muestra como la información de error sale por stderr
cat: temporil: No existe el fichero o el directorio
44% cat temporil >& errores
45% cat errores
cat: temporil: No existe el fichero o el directorio
46%_
Observamos como en la orden 43, sigue saliendo por pantalla el mensaje aunque
tenemos redirigida la salida estándar a un archivo. Esto se debe a que, como dijimos antes,
algunas órdenes generan información por el error estándar, no sólo por la salida estándar. Por
ello, debemos utilizar la redirección >& para no seguir viéndolo por pantalla.

• Redirección de entrada dentro de programas con <<

El metacarácter << nos permite redireccionar líneas desde un programa a la entrada de


una orden dentro del programa. Este mecanismo se de denomina documentación aquí. Esta
redirección permite escribir mensajes multilínea desde un programa (los programas los veremos
en la Parte II) sin necesidad de usar múltiples órdenes echo.
Para utilizarlo, escribimos <<DELIMITADOR, donde DELIMITADOR es un conjunto único
de caracteres que marcarán el fin de las líneas de entrada para la documentación aquí. Cada una
de las líneas de entrada que se vaya introduciendo se examina para igualar el delimitador sin
realizar ninguna sustitución, o expansión de metacaracteres. Una vez introducido el delimitador,
se ejecuta la orden. Esto nos va a permitir ajustar dinámicamente los mensajes u ordenes.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 26


El siguiente ejemplo muestra como usar este mecanismo para generar un mensaje de
bienvenida para un usuario adaptándolo a su entorno. Para ello, hacemos uso de las variables
$user y $PWD que nos dan el login de usuario y el directorio de trabajo actual, respectivamente.
46% cat << FIN_BIENVENIDA
Hola $user
Su directorio actual es $PWD
No olvide mirar las noticias
FIN_BIENVENIDA #escribimos el delimitador establecido
Hola x3333333
Su directorio actual es /d5/home/x3333333
No olvide mirar las noticias
47% _
Como vemos, cat esta aceptando información hasta que encuentra la palabra clave que
le hemos indicado tras el <<, en nuestro ejemplo, FIN_BIENVENIDA; cuando la encuentra
entiende que la entrada de información por stdin ha finalizado.

• Algo más sobre cauces y redirecciones

Al usar juntos los mecanismos de cauces y redireccionamiento, encontramos que el


único lugar donde la redirección de archivos se acomoda dentro de un cauce es al final de la
última orden del cauce. En este caso, podemos usar la redirección de la salida para capturar la
salida final en un archivo. Si intentásemos redireccionar la salida de una orden al principio, o
en medio, de un encauzamiento, todos los datos irán al archivo y nada fluirá a través del resto
del cauce. Para permitir introducir redireccionamiento en cualquier orden de un cauce, existe
la orden tee que a partir de un flujo de entrada desdobla éste en dos: uno de ellos puede ir
redireccionado a un archivo y el otro al cauce (como muestra la Figura 1.5). Con la
redirección de stdin pasa algo similar, sólo podemos usarla en la primera orden del cauce. Si la
usamos en medio, se ignora la información que fluye por el cauce. La sintaxis es:
tee [-ai] [archivos]
tee lee de stdin y escribe en stdout y en el(los) archivo(s) especificado(s). La opción -a
provoca que se añada al final del archivo, ya que por defecto se sobreescribe.
Por ejemplo, si queremos ordenar un archivo e imprimirlo, pero además queremos que el
resultado de ordenarlo se guarde en otro archivo, ejecutaremos

% sort mi_archi | tee archi_orden | pr -d

Figura 1.5.- Desdoblamiento del flujo realizado por tee.

• Archivos de dispositivos y redirecciones

El directorio /dev contiene los archivos de dispositivos y seudo-dispositivos


(dispositivos lógicos que no se corresponden con un dispositivo hardware). Uno de estos
seudodispositivos es /dev/null que actúa como un sumidero de información (todo lo que se le
envía es descartado por el sistema), y por tanto podemos enviarle la información que no

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 27


necesitemos. Por ejemplo, la orden time ejecuta otra orden pasada como argumento y nos da
los tiempos de ejecución de la misma (modo kernel, usuario y tiempo de reloj).
% time ls
COORDINA.PS arch-survey.ps complete.wav woods.ps
0.000u 0.000s 0:00.00 0.0% 0+0k 0+0io 84pf+0w
Como vemos, time muestra el resultado de ejecutar la orden ls y el tiempo de
ejecución. Si no estamos interesados en el resultado de ls, podemos descartarlo enviándolo a /
dev/null. Nota: como se puede observar los tiempos de ejecución siguen viéndose en pantalla
aunque hemos redireccionado la salida estándar.
% time ls > /dev/null
0.010u 0.000s 0:00.00 0.0% 0+0k 0+0io 84pf+0w
Otro archivo de dispositivo que es útil es /dev/tty. /dev/tty es un sinónimo de nuestro
terminal de control, de forma que independientemente de cómo se encuentren redireccionadas
las entradas, salidas y error estándar, la lectura o escritura de /dev/tty se hace siempre en el
terminal. Por ejemplo, la siguiente orden redirecciona la salida de cat al terminal:
% cat miarchivo >/dev/tty

7.5 Metacaracteres de expansión/sustitución


Los metacaracteres de expansión/sustitución ya los hemos visto en los ejemplo
anteriores, por lo que sólo los citamos en la Tabla 1.7.

Tabla 1.7.- Metacaracteres de expansión/sustitución.

Metacaracter Descripción
$ Indica la sustitución de variables. Una palabra precedida de $ se interpreta
como variable, y $palabra se sustituye por su valor.
! Indicador de sustitución de historia (historia órdenes en csh).
? Modificador de sustitución de historia de órdenes.
: Precede a los modificadores de sustitución en la historia de órdenes.

8 Procesamiento de la línea de órdenes

Para finalizar por donde comenzamos, indicar, una vez visto los metacaracteres, que los pasos
dados por el shell para procesar una línea de órdenes una vez analizada son los siguientes:
1. Sustitución de la historia (si está activa).
2. La línea de órdenes se analiza y trocea en palabras.
3. La historia se actualiza.
4. Se procesan las comillas.
5. Se realiza la sustitución de alias.
6. Se realizan las redirecciones, background, y encauzamientos.
7. Se realiza la sustitución de variables.
8. Se realiza la sustitución de órdenes.
9. Se lleva a cabo la sustitución de nombres de archivos, denominada globbing.
10. Se ejecuta el programa.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 28


9 Los alias

9.1 Definición y eliminación de alias


El mecanismo de alias permite adaptar el comportamiento de las ordenes LINUX cuando
se ejecutan. Es similar a la capacidad de definición de macros en otros lenguajes.
alias [nombre_alias [definición_orden]]
Asocia un nombre_alias con la definición_orden.
Ej. % alias dir ls -l # asigna a ls -l el nombre de dir

Cuando veamos que ya no nos es útil la definición de un alias, podemos borrarla con:
unalias nombre_alias
donde nombre_alias es el nombre del alias que se va a borrar.
Con los alias, seremos capaces de:
• Listar la definición de los alias existentes. En nuestro entorno de prácticas, el
sistema define algunos alias de uso común. Se pueden parecer bastante a:
1% alias # lista los alias existentes y su definición
l. ls –d . [a-zA-z]* --color=tty
ll ls –l --color=tty
ls ls –color=tty
mc setenv MC `/usr/bin/mc –P !*`; cd $MC; unsetenv MC
which alias |/usr/bin/which –tty-only –read-alias –show-dot
2% alias ll # muestra la definición del alias ll
ll ls –l --color=tty

Podemos ver los alias definidos para una orden con la orden empotrada where:
where alias
Por ejemplo;
% where ls
ls es un alias para ls –color=tty

• Renombrar o redefinir una orden existente. Esto permite ahorrar escritura de


órdenes o que nuestro sistema se parezca a otro sistema operativo.
3% alias rename mv # renombra mv por el nombre rename
4% alias ls 'ls -l' # abrevia la escritura de ls -l, ahorraremos en escrituras posteriores
Cuando redefinimos una orden con un alias, podemos seguir utilizando la orden en su forma
original anteponiendo al nombre un \, o bien invocándola con su camino completo. Por
ejemplo, definido el alias anterior, podemos invocar ls en su forma normal con:
% \ls
% /bin/ls

• Crear una nueva orden, lo que permite abreviar la escritura de las órdenes que usamos
frecuentemente:
5% alias hp 'history|more' # hp muestra historia paginada
6% alias ht 'history|tail' # ht muestra el final del archivo de historia

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 29


La orden tail muestra las últimas líneas de un archivo (por defecto 10), o conjuntos de
archivos. También, permite ver el crecimiento de un archivo. Así, la orden
% tail -f miarchivo
permite ver las 10 últimas líneas, y después, cada segundo, mostrará cada nueva línea
añadida al archivo. En este último caso, detenemos tail con <CTRL>-C.

¡Precauciones!
(1) A veces, los alias pueden entrar en conflicto con programas del shell, especialmente si se
usan para la redefinición de órdenes. Un programa shell podría no funcionar correctamente
por que espera el comportamiento por defecto de una orden.
(3) Defina siempre los alias multipalabra entre comillas simples.
(4) Al crear un alias, debemos tener la precaución de no llamarlos alias, ni crear alias que se
referencien a ellos mismos para no crear un bucle en las definiciones de alias. Ahora bien, sí
podemos crear alias que referencien a otros alias

9.2 Incluyendo argumentos en los alias


Para pasar parámetros desde la línea de órdenes que usa un alias a las órdenes que hay
dentro de él, sustituiremos los argumentos dentro del alias utilizando caracteres de la historia de
órdenes para indicar al shell que inserte en un punto de la definición del alias los parámetros de
la línea de órdenes. La Tabla 1.8 muestra algunas combinaciones útiles en la definición de alias.
Un ejemplo,
9% alias cd 'cd \!^; pwd' # cd usa el 1º arg de la línea de órdenes
Recuerda que la barra invertida (\) se pone para proteger al carácter !, es decir, permite
que se interprete ! cuando se ejecuta el alias, no cuando se define.
Tabla 1.8.- Metacaracteres para designar sucesos utilizados en los alias.

Metacaracter Efecto dentro de un alias


!^ Inserta el primer argumento de la línea de ordenes.
!* Inserta todos los argumentos de la línea de ordenes.
!:x Inserta el x-ésimo argumento.
!:x-y Inserta un rango de palabras; desde la x a la y.
!:x* Como el x-$ ($ es el último argumento) de arriba.
!:x- Como x* salvo que se omite la palabra $.
Para ilustrar como pasar varios parámetros a un alias, utilizaremos una potente orden que
nos permite buscar archivos que satisfacen un criterio en toda o parte de la jerarquía de
directorios, la orden find. Su sintaxis difiere de lo que estamos acostumbrados, por lo que es
un firme candidato para crear un alias.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 30


La orden find nos permite localizar archivos. Su sintaxis es
find path-name-list expression
donde path-name-list es un path o lista de path donde se buscan recursivamente los
archivos que cumplen la expresion. Algunas formas de expresion válidas son:
-name patron Verdad si patron iguala el nombre del archivo
-perm [-]onum Verdad si se igualan los permisos
-exec orden Verdad si la orden ejecutada devuelve cero
-ok orden Como la anterior, pero consulta antes de ejecutar orden
-print Siempre verdad, imprime el path de los archivos
...
Ejemplos:
• Deseamos buscar y listar los archivos cuyo nombre comienza por ‘st’ y que cuelgan del
directorio /usr/bin:
% find /usr/sbin -name 'st*' -print
/usr/sbin/strfile
/usr/sbin/stunnel
• Borrar todos los archivos en el directorio home cuyo nombre sea a.out ó * .o
que no han sido accedidos durante una semana:
% find $HOME \( -name a.out -o -name '*.o' \) -atime +7 -exec rm {} \;
Observar que debe ponerse la \ para proteger los paréntesis; además, hemos dado una
nueva expresión -atime que es verdad si la fecha de último acceso es de más de 7 dias.

Si utilizamos con frecuencia la orden find para buscar y listar los archivos que aparecen en un
directorio, podemos definir un alias de la forma:
10% alias findusr "find /usr/sbin -name '\!^' -print"
11% findusr st*
/usr/sbin/strfile
/usr/sbin/stunnel
Ganamos flexibilidad si podemos pasar como argumento el directorio a partir del cual deseamos
buscar. Cuando ejecutemos el alias, le daremos dos argumentos, el primero será el punto de
inicio de la búsqueda y el segundo el criterio de búsqueda.
12% alias findname "find \!^ -name '\!:2' -print"
13% findname /usr/sbin st*
/usr/sbin/strfile
/usr/sbin/stunnel

¡Precaución ! En los dos alias anteriores, la línea entera va entre dobles comillas y los
metacaracteres entre comillas simples: encerramos entre comillas el alias completo para que el
shell conozca todas las palabras que son parte de la definición del alias; a la vez, acotamos los
metacaracteres para el parámetro de la opción –name (esto asegura que los metacaracteres no
serán interpretados por el shell en la definición, sino al ejecutar orden find al utilizar el alias).

10 Ordenes para el control de trabajos

Se denomina trabajo, o job, a cada orden que mandamos a ejecutar al shell. El shell
mantiene la pista de todos los trabajos que no han finalizado y suministra mecanismos para
manipularlos. En esta sección, vamos a ver los mecanismos que suministra el shell para
controlar la ejecución de nuestros trabajos.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 31


• jobs
Un trabajo puede estar en uno de los tres estados siguientes: primer plano, segundo
plano, o suspendido (suspended o stopped, según el sistema.). La orden jobs muestra los
trabajos actualmente asociados con la sesión del TC Shell. Su sintaxis es:
jobs [ -l ]

Lista los trabajos activos bajo el control del usuario. Con la opción -l se listan, además
de la información normal, los identificadores de los procesos.

Sean las siguientes órdenes:


46% gcc -o prueba prueba.c &
47% sort misdatos > misdat.ord &
48% vi texto
..
<CTRL>-Z # suspendemos la ejecución de vi
La orden jobs permite ver el estado de los trabajos en segundo plano y los suspendidos. Un
ejemplo de la información que muestra y su significado es:
50% jobs
[1] Running gcc -o prueba prueba.c
[2] - Running sort misdatos > misdat.ord
[3] + Suspended vi texto
Línea de órdenes asociada con el trabajo
Estado del trabajo
Indicador del trabajo: actual (+) y anterior (-)
Número de trabajo
Si un trabajo contiene un cauce, las órdenes del cauce que han acabado se muestran en
una línea diferente de aquellas que están aún ejecutándose. En el tcsh, si la variable $listjobs
esta activa, se ejecutará automáticamente la orden jobs cuando se suspenda un trabajo. Si
deseamos que se ejecute jobs -l, debemos declarar la variable como set listjobs = long.
Las siguientes órdenes que vamos a ver nos permiten cambiar el estado de un trabajo. La
mayor parte de ellas admiten como argumento un identificador de trabajo. La Tabla 1.9 muestra
las diferentes formas que puede tomar un especificador de trabajos.
Tabla 1.9. Especificadores de trabajos.

Especificador Trabajo al que se refiere el especificador


% Trabajo actual (%+ y %% son sinónimos de %)
%- Trabajo previo
%n Trabajo número n
%cadena Trabajo cuya línea de ordenes comienza con cadena
%?cadena Trabajo cuya línea de ordenes contiene cadena
Normalmente, es más fácil referirse a un trabajo por su nombre que por su identificador.
Ahora bien, %cadena o %?cadena son referencias ambiguas si igualan más de un trabajo en la
lista de trabajos. Cuando se produce esta situación, el shell suministra un aviso. Por ejemplo, la
siguiente orden es ambigua si tenemos dos trabajos que comienzan por vi:
51% fg %vi
vi: Ambiguous.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 32


• fg
La orden fg se usa para traer trabajos de fondo, o suspendidos, a primer plano:
fg [ % [job ] ]

Sin argumentos, o con %, fg trae el trabajo actual a primer plano. Con un identificador
de trabajo, trae a primer plano el trabajo especificado.

Supongamos que hay dos trabajos en segundo plano, como muestra el ejemplo:
52% jobs
[1] + Stopped (user) xterm
[2] - Running xclock
Si deseamos poner en primer plano xterm, dado que esta suspendido, podemos ejecutar
53% fg %1
xterm
Ahora, el shell no responde hasta que finalicemos el proceso xterm o lo devolvamos a segundo
plano. Este tipo de transferencia, nos permite lanzar de fondo programas que necesiten mucho
procesamiento inicial y retomarlos cuando necesitemos interaccionar con él.

• bg
Contraria a la orden fg, bg envía a segundo plano un programa:
bg [ %job ]

Ejecuta el trabajo actual, o especificado, en background.


Esto se ilustra en el ejemplo.
54% jobs
[1] + Stopped (user) xterm
[2] - Running xclock
55% bg %1
[1] xterm &

• La orden %
La orden % se utiliza para cambiar el estado de un trabajo.
%[ job ] [ & ]

Esta lleva a primer plano al trabajo actual, si no tiene argumento, o al especificado por
job. Si esta presente el &, envía el trabajo a segundo plano.

La orden % es en realidad una abreviación de fg y bg. En los dos ejemplos anteriores, podríamos
haber sustituido fg y bg por el %:
56% jobs
[1] + Stopped (user) xterm
[2] - Running xclock
57% %
xterm
o bien
58% % &
[1] xterm &

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 33


• notify
La orden notify se utiliza para identificar los procesos para los cuales el usuario espera
ver los cambios de estado asíncronamente.
notify [ %job ]

Establece la notificación para el trabajo especificado; si no se dan argumentos, se toma


el trabajo más recientemente señalado.
Por defecto, el shell no notifica ningún cambio en el estado de un proceso hasta que necesita
imprimir el indicador de órdenes, lo que implica que si se produce un cambio, la orden actual no
se ve interrumpida. Podemos cambiar esta conducta con la variable $notify (al activarla se
notifican los cambios de estado cuando se producen). Ahora bien, la orden notify permite
tomar selectivamente órdenes que necesitan atención inmediata, como muestra el ejemplo:
59% make &
[3] 15543
60% jobs
[1] + Running xclock
[2] - Running xbiff
[3] Running make
61% notify %3
62% cat miarchivolargo | more
.........
--More--(32%)
[2] Done make
Ahora, la tarea actual, cat|more, se ha interrumpido para indicar la finalización de la orden.

• kill
La orden kill envia una señal a un(os) proceso(s). Tiene dos formas:
kill [ -sig ] { pid | %job } ...
kill -l
La acción por defecto de kill es enviar la señal de finalizar al proceso(s) indicado(s)
por PID o número de job. Las señales, -sig, se indican por números o nombres.
Con la opción -l, lista los nombres de la señales que pueden ser enviadas.
Las señales son en LINUX el medio básico para notificar a los procesos la ocurrencia de
eventos. Por ejemplo, las pulsaciones <CTRL>-C, <CTRL>-Z, etc. lo que hacen es provocar que el
manejador de teclado genere determinadas señales que son enviadas al proceso en ejecución.
La orden kill envía por defecto la señal SIGTERM. A un proceso que recibe una señal, se
le esta indicando que debe terminar su ejecución, independientemente del estado en que se
encuentre. Esta señal, como otras muchas es “ignorable” o es “atrapable”, es decir, puede que el
proceso haya dicho que no quiere recibir la señal (ignorarla) o que haga otra cosa que sea no
terminar (atraparla), y que por tanto no termine cuando la recibe (esto lo veremos en SOII). En
este contexto, es posible que alguna vez nos ocurra que ejecutemos kill para eliminar un
proceso, pero tras la ejecución de esta orden, el proceso que queremos eliminar siga “vivito y
coleando”. En este caso, lo que se debe de hacer es enviar al proceso una señal que no es ni
ignorable ni atrapable, la señal SIGKILL, con número 9:
% kill -9 pid_proceso # también se puede hacer % kill -KILL pid

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 34


Por ejemplo, podemos eliminar una ventana de reloj de pantalla con kill.

63% jobs
[1] + Running xclock
[2] - Running xbiff
[3] Running make
64% kill %1
65% _
[2] Terminated xclock

• stop
La orden stop se utiliza para detener trabajos en su ejecución.
stop [ %job ]

Detiene el trabajo actual o especificado.

Realmente, esta orden es una abreviación de la orden kill -STOP %. Es decir, envía la señal
SIGSTOP al proceso. Por ejemplo
65% jobs
[1] + Running xclock
[2] - Running xbiff
[3] Running make
66% stop %xc
[1] + Stopped (signal) xclock
El trabajo permanecerá "congelado" hasta que se reanude, moviéndolo a segundo plano, o a
primer plano, o enviándole la señal de reanudación (-CONT). Esta orden se puede utilizar para
mejorar la respuesta del sistema en aquellos casos en que ésta no sea buena debido a hay
muchos trabajos en segundo plano. En este caso, podemos parar algunos o todos, e ir
reanudándolos poco a poco.
La Tabla 1.10 resume las órdenes vistas y las transiciones entre estados.
Tabla 1.10.- Ordenes de control de trabajos y las transiciones que producen.
Estado Estado Inicial
final Primer plano Segundo plano Suspender
Primer plano -- fg %j fg %j
Segundo plano CTRL-Z + bg %j -- bg %j
Suspendido CTRL-Z stop %j --
Finalizar CTRL-C kill %j kill %j

• wait
La orden wait permite esperar la finalización de todos los procesos en segundo plano.
wait

El shell espera hasta la finalización de los procesos en segundo plano, o hasta que la
orden sea interrumpida por una señal.
73% jobs
[1] + Running xclock
[2] - Running xbiff

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 35


[3] Running xterm
74% set notify
75% wait
[1] Terminated xclock
[3] Terminated xbiff
[2] Done xterm
Si alguno de los procesos consume mucho tiempo o si se produce algún problema tal como la
necesidad de enviar información al terminal o leer del teclado, la orden wait puede terminarse
con <CTRL>-C. Esta orden no se detiene con <CTRL>-Z.

10.1 Control de la escritura de procesos de fondo


Vamos a ver algunos usos del control de trabajos. La escritura de una orden de fondo en
pantalla puede ser molesta en ocasiones, por ejemplo, si estamos editando un archivo o
componiendo un mensaje. Si no queremos que aparezca en pantalla la salida producida por un
trabajo de fondo, o tenemos varios trabajos de fondo y no queremos ver sus mensajes mezclados
entre sí, podemos forzar la suspensión de los trabajos de fondo que intentan escribir en la
pantalla (de forma similar a lo que hace el sistema automáticamente con un trabajo de fondo que
intenta leer del terminal) con la orden stty. Cuando estemos preparados para ver la salida de la
orden de fondo que esta suspendida esperando escribir sólo tenemos que traerla a primer plano.
Ejemplo:
76% stty tostop #desactivamos la escritura de procesos de fondo
77% date &
[1] 23123
78% stty -tostop #permitimos la escritura
[1] + Suspended (tty output) date
79% fg #traemos date a primer plano, y escribe
date
Mon Nov 24 24 9:00:00 EST 1997

10.2 Poner en segundo plano trabajos interactivos

Las órdenes interactivas esperan normalmente leer y escribir en el terminal. Si bien hay
órdenes interactivas que no son útiles en segundo plano, como vi o more, otras pueden ponerse
en segundo plano para realizar operaciones que no requieren nuestra atención. Por ejemplo, si
estamos realizando un FTP (File Transfer Protocol) anónimo para traernos un archivo remoto, la
secuencia de órdenes se parecerá a la siguiente:
80% ftp alguna.maquina
User: anonymous
Passwdord: # tecleamos nuestra direccion de e-mail
ftp> cd pub
ftp> binary # transferencia en modo binario
ftp> get software.tar.Z # traernos archivo software.tar.Z
ftp> quit # finalizamos el programa ftp

Si la operación get es lenta, y queremos realizar otros trabajos mientras, ejecutamos ftp
interactivamente hasta el get. Entonces, paramos el trabajo y lo llevamos a segundo plano:
80% ftp maquina.remota
User: anonymous
Passwdord: # Tecleamos nuestra direccion de e-mail
ftp> cd pub
ftp> bin

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 36


ftp> get software.tar.Z
<CTRL>-Z
Suspended
81% bg # Lo ponemos de fondo para seguir trabajando
[1] ftp alguna.maquina &
...... # Seguimos trabajando en otras cosas
[1] + Suspended (tty input) ftp maquina.remota
88% fg # ftp intenta escribir en tty, lo traemos primer plano
89% quit # finalizamos ftp

11 Ordenes para el control de recursos

• nice
La orden nice permite cambiar la prioridad de los procesos. Un usuario normal sólo
puede incrementar el valor numérico de la prioridad, lo que produce efectivamente una
disminución de la misma3. Solo el root puede aumentar la prioridad de un proceso. Podemos ver
la prioridad de un proceso con ps.
nice [ +n | -n ] [ orden ]

Cambia la prioridad de la orden en un valor especificado por n (por defecto es 4). Si


no damos una orden, cambiamos la prioridad de todas las órdenes dadas desde el shell.
Por tanto, si no somos el root no podremos volver a la prioridad anterior.

Podemos ver el tiempo de ejecución de dos procesos ejecutándose con prioridades diferentes.
94% ls & nice +20 ls &
[1] 5478
[2] 5479
95%_
[2] + Exit 1 ls
Elapsed time 0:15, CPU time 31%, Maximum memory 0
[1] + Exit 1 ls
Elapsed time 0:17, CPU time 29%, Maximum memory 0
Podemos ejecutar diferentes órdenes y ver la diferencia. Normalmente, cuanto mayor es la carga
del sistema peor es el tiempo de la orden ejecutada con nice.
• nohup
La orden nohup se utiliza para ignorar la señal SIGHUP, que se envía normalmente a los
procesos en ejecución cuando el shell de entrada indica que va a finalizar (logout), y cuya acción
por defecto para los procesos que reciben ésta señal es finalizar.
Las órdenes que podemos ejecutar con nohup no tienen porque ser simples. Ahora bien,
no se permiten cauces, listas de órdenes, o listas de órdenes parametrizadas. Para los cauces y las
listas de ordenes se pueden ejecutar con esta orden si se ejecuta un TC Shell y se pasa la lista
como argumento, p. ej.

95% nohup tcsh -c "ls | grep miarchivo | wc"

3 El convenio para especificar la prioridad en Unix difiere según los sistemas. En sistemas más antiguos, el
criterio es que una prioridad mayor viene representada por un número menor. En los sistemas más modernos,
un número mayor representa una prioridad mayor.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 37


12 Miscelánea de órdenes

• repeat
La orden repeat se usa para ejecutar una orden de forma repetida. Su sintaxis es:
repeat contador orden
La orden especificada en orden se ejecuta tantas veces como indica contador. La
orden debe ser simple, no puede ser un grupo entre paréntesis.

100% repeat 2 echo No hay espacio en el dispositivo


No hay espacio en el dispositivo
No hay espacio en el dispositivo

• umask
Como indicábamos, la protección en Unix descansa en el sistema de archivos, es decir, los
permisos de los archivos determinan quién, y qué, se puede hacer sobre un archivo. Por ello, es
necesario cuidar escrupulosamente los permisos de archivos y directorios. En este sentido y con
la idea de que es más seguro “limitar permisos y luego ampliarlos, que darlos y luego
limitarlos”, surge la máscara de permisos. Cada usuario tiene por defecto una máscara de
permisos, que indica en todo momento qué permisos NO QUEREMOS que tengan los archivos
que vamos a crear, así, al crear un archivo/directorio, el sistema elimina de la cadena de
permisos (por defecto sería: rwxrwxrwx si es un directorio o archivo ejecutable, y rw-rw-rw-
para archivo regular) los permisos que tengamos en la máscara. La orden umask nos permite
ver y modificar la máscara de permisos según nuestras necesidades.
umask [ valor ]

Sin argumentos, umask muestra la máscara de creación de archivos. Con un argumento,


que debe ser un número en octal, se establece una nueva máscara. Por defecto, es 0.

Para el usuario, los permisos de protección vienen representados por una cadena de
caracteres de la forma rwxrwxrwx. Sin embargo, el sistema utiliza un número en octal de tres
cifras para representarlos, donde cada cifra representa un grupo de tres letras. La
correspondencia entre números y permisos a parece en la Tabla 1.11.
Tabla 1.11. Permisos de archivos en octal.

Usuario Grupo Resto


Carácter r w x r w x r w x
Número octal 400 200 100 40 20 10 4 2 1

Por ejemplo, si queremos generar los permisos para un archivo de forma que sea de
lectura, escritura y ejecución para el propietario, de lectura y ejecución para el grupo, y de
ejecución para el resto, es decir, la cadena rwxr-x--x, el número octal se genera como: 400 +
200 + 100 + 40 + - + 10 + 4 + - + 1 = 755.
El valor de umask se resta de estos permisos de creación reales para determinar el
permiso efectivo de creación. Por ejemplo, cuando creamos un archivo de texto por defecto
vemos que se le asigna la cadena de protección rw-r--r--. Esto se debe a la máscara que se
suele poner tener por defecto en la mayoría de los sistemas:
101% umask
22

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 38


De forma que a los permisos 666 (rw-rw-rw-) del archivo regular le restamos 22, por los que
queda 644, o sea, rw-r--r--.
Como veremos en la Parte II, la orden chmod tiene dos formas de expresar los permisos,
la forma simbólica (cadena rwxrwxrwx), y la forma octal (número octal construido como
acabamos de ver). Así, podemos asignar permisos rw-r--r-- a miprograma.c de la forma:
102% chmod 644 miprograma.c

• Planificación de eventos con sched

Podemos planificar la ocurrencia de determinados eventos (órdenes) en diferentes instantes de


tiempo. La orden sched tiene el formato
sched [-n]
sched [+]hh:mm orden
Sin opciones, muestra la lista de eventos planificados (según el formato definido en la
variable sched). Con la opción –n, borra el evento de la lista.
La segunda forma añade orden a la lista de eventos a planificar.

Por ejemplo, para que el shell nos notifique a una determinada hora un mensaje:
% sched 10:00 echo ‘En 10 minutos debes ir a la reunión’
El tiempo se puede dar en 12 horas, por ejemplo, 5pm (a las cinco de la tarde); o relativo
al tiempo actual, +1:30 (dentro de hora y media). El mecanismo de esta orden es similar, no
igual, a la orden Linux at. Su principal desventaja es que pudiera no planificarse un evento en
el tiempo exacto que deseamos (en el instante de notificación, el shell puede estar ejecutando
una orden). Su principal ventaja es que como se ejecuta en el shell tiene acceso a todas las
variables. Esto nos suministra un mecanismo para cambiar el entorno de trabajo basándonos
en la hora del día.
El formato con el que debe mostrarse la lista de tareas planificadas por la orden
empotrada sched. Viene definido por la variable $sched. Si no se indica nada, se utiliza por
defecto ‘%h\t%T\t%R\n’. La secuencia de formatos definidos en esta variable es la misma
que la que se describira en la variable $prompt de la Parte II.

• Planificando eventos con at

La orden at se utiliza para programar la ejecución de órdenes en un instante de tiempo


posterior. Las órdenes a ejecutar pueden darse interactivamente o a través de un archivo de
órdenes. Las órdenes se ejecutan en el shell especificado en la variable $SHELL. Cualquier
información de salida de las órdenes se dirige a un archivo.
La orden at encola trabajos para una posterior ejecución. Su sintaxis es
at [-V] [-q cola] [-f archivo] [-mldbv] TIEMPO
at –c job [job…]
El primer formato se utiliza para añadir el trabajo a una cola. El segundo para ver los
detalles específicos de los trabajos, no para ejecutarlos. El formato de tiempo puede ser
bastante complejo (ver definición exacta en /usr/doc/at-3.1.7/timespec). Algunos formatos
son: HH:MM (para el mismo día); MMDDYY (para cualquier día). Tiempo relativo now +
tiempo (con tiempo en minutos, horas, días, o semanas)

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 39


Si queremos borrar ciertos archivos dentro de una hora, podemos ejecutar la orden:
% at now + 1 hours
warning: commands will be executed (in order) using a)$SHELL b)login shell c)/bin/sh
at> rm *.o
at> <control>-d
job 5 at 2002-10-11 12:25
%_
La orden atq nos permite ver los trabajos que tenemos planificados, y la orden atrm
borrar trabajos especificando el número de trabajo.

13 Procesamiento avanzado de archivos

Veamos ahora algunas órdenes para el procesamiento y manipulación de archivos más


sofisticadas que las vistas hasta ahora.

● Cortar y pegar con cut y paste


Las órdenes cut y paste permiten procesar archivos que tienen forma de tabla (conjunto de
líneas, cada una dividida en un número fijo de columnas). Veamos a continuación cada una de
estas órdenes y algunos ejemplos. La orden cut tiene el formato:

cut [opciones] … [archivos] …


entre las opciones podemos encontrar:
-b muestra sólo los bytes indicados
-c ídem los caracteres indicados.
-d carácter delimitador de campos, si no es TAB
-f lista muestra los campos indicados en lista. Permite especificar rangos:
N n-ésimo byte, carácter o campo (contado desde 1),
N- desde el carácter n hasta el final,
N-M de n al m, y
–M, desde el primero al m-ésimo.
Para ver algunos ejemplos vamos a tomar el archivo de muestra, denominado agenda, que
tiene la siguiente estructura “nombre dir_correo teléfono”.
% cat agenda
Doraimon doraimon@dibujos.com 234.456.567
Nobita nobita@dibujos.es 345.123.456
Gigante gigante@notengo.jp 567.234.567
Sisuka sisuka@micorreo.org 456.832.349
Suneo suneomk@jejeje.com 928.460.362
Podemos ver el primer y último campo del archivo con:
% cut –f1,3 agenda
Doraimon 234.456.567
Nobita 345.123.456
Gigante 567.234.567
Sisuka 456.832.349
Suneo 928.460.362
Si el archivo agenda estuviera estructurado utilizando ‘:‘como separador, en lugar de TAB,
podríamos usar la orden siguiente para recuperar la columna 2:
% cat agenda
Doraimon:doraimon@dibujos.com:234.456.567

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 40


Nobita:nobita@dibujos.es:345.123.456
Gigante:gigante@notengo.jp:567.234.567
Sisuka:sisuka@micorreo.org:456.832.349
Suneo:suneomk@jejeje.com:928.460.362
% cut –d: -f2 agenda
doraimon@dibujos.com
nobita@dibujos.es
gigante@notengo.jp
sisuka@micorreo.org
suneomk@jejeje.com

La orden paste tiene la forma


paste [opciones] … [archivos]…
y las opciones son:
-d delimitador, si no es TAB
-s pega un archivo en serie en lugar de en paralelo

Podemos pegar los archivos agenda y direcciones (observa:el resultado sale sólo por pantalla).
% cat direcciones
C/ Sol, 123
C/ Luna, 234
Plz./ Planeta s/n
C/ Perdido, 12
C/ Pijolandia, 15
% paste agenda direcciones
Doraimon doraimon@dibujos.com 234.456.567 C/ Sol, 123
Nobita nobita@dibujos.es 345.123.456 C/ Luna, 234
Gigante gigante@notengo.jp 567.234.567 Plz./ Planeta s/n
Sisuka sisuka@micorreo.org 456.832.349 C/ Perdido, 12
Suneo suneomk@jejeje.com 928.460.362 C/ Pijolandia, 15
Supongamos que ahora deseamos crear un archivo que contenga los datos de los dos archivos
anteriores con el formato: “nombre direccion correo telefono”
% cut –f1 agenda > tmp1
% cut –f2-3 agenda > tmp2
% paste tmp1 direcciones tmp2 > nuevaagenda
%cat nuevaagenda
Doraimon C/ Sol, 123 doraimon@dibujos.com 234.456.567
Nobita C/ Luna, 234 nobita@dibujos.es 345.123.456
Gigante Plz./ Planeta s/n gigante@notengo.jp 567.234.567
Sisuka C/ Perdido, 12 sisuka@micorreo.org 456.832.349
Suneo C/ Pijolandia, 15 suneomk@jejeje.com 928.460.362
Podemos, generar una versión de la agenda en la que aparezcan los campos 1 y 3, de
agenda y de direcciones. Para ello, primero pegamos y después cortamos
% paste agenda direcciones | cut –f1,3,4
Doraimon 234.456.567 C/ Sol, 123

● Empaquetado y compresión de archivos con tar y gzip

- Compresión de archivos

La compresión de archivos nos permite ahorrar espacio de disco y tiempo en la


transmisión de los mismos.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 41


Para comprimir y descomprimir archivos podemos utilizar las órdenes cuya sintaxis
aparece a continuación:
gzip [opciones] [-S sufijo] [nombre …]
gunzip [opciones] [-S sufijo] [nombre …]
zcat [-fhLV] [nombre …]
gzip comprime uno o varios archivos. Cada archivo es sustituido por uno cuya extensión es
.gz. Los archivos son restaurados con las ordenes gzip –d, gunzip o zcat.

Veamos algunos ejemplos. Primero vamos a concatenar varios archivos comprimidos


mediante las órdenes:
% gzip –c archivo1 > archivos.gz
% gzip –c archivo2 >> archivos.gz
Podemos descomprimir los archivos en la salida estándar en las dos formas siguientes:
% gunzip archivos
% zcat archivo1 archivo2
Por otra parte, se obtiene una mayor compresión si se comprimen todos los archivos a la vez
(primera orden de las 2 que siguen) que por separado (segunda orden):
% cat archivo1 archivo2 | gzip > archivos.gz
% gzip –c archiv1 archivo2 > archivos.gz
También, podemos re-comprimir archivos concatenados para obtener una mayor compresión.
% gzip –cd viejo.gz |gzip >nuevo.gz
Por último, indicar que si queremos crear un único archivo con múltiples componentes que
puedan extraerse posteriormente de forma independiente, es recomendable utilizar tar, pues
gzip esta pensado para complementar a tar, no para sustituirlo.
- Empaquetado de archivos

La compresión de archivos ahorra espacio, pero en el caso de pequeños archivos, el ahorro


no es muy significativo. Al comprimir archivos de un tamaño igual o menor que un cluster
(unidad mínima de almacenamiento en disco), el archivo seguirá ocupando el mismo espacio
en disco; sí ahorramos tiempo, por ejemplo, si tenemos que transmitirlo. La orden tar se
utiliza para empaquetar un grupo de archivos en un único archivo, que tiene la estructura de un
encabezado de 512 bytes y el archivo, repetida tantas veces como archivos empaquetemos.
El origen de la orden obedecía a almacenar jerarquías de directorios en cinta para
copias de seguridad. En la actualidad, se utiliza para ahorrar espacio (al reducir la
fragmentación interna de los clusters) y aumentar las velocidades de transmisión. En este
sentido, es la forma habitual de suministrar parches y distribuciones de sistemas.

La sintaxis de la orden tar es:


tar [opciones] [archivos]
Las opciones de tar pueden ir o no precedidas del -. Si bien tiene numerosas opciones, sólo
vamos a ver algunas de ellas:
c Crea una nueva cinta para almacenar en ella los archivos.
r Graba archivos al final de la cinta
t Enumera el contenido de la cinta.
f archivo
Vamos a ver un ejemplo Utilizade‘archivo’ comoendestino
empaquetado de los de
un archivo archivos
disco adeempaquetar. Porde
una jerarquía
defecto el valor es /dev/mto (una unidad de cinta).
directorios (todos los archivos directorios que hay en el directorio MisPracticasSOI):
x Extrae los archivos de la cinta.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 42


% cd MisPracticasSOI
% tar cvf practicasSoi.tar .
./Sesion1/archivo1

./Sesion4/achivon
% file practicasSoi.tar
practicasSoi.tar: GNU tar archive
Para restaurar posteriormente en el directorio actual todos los archivos podemos utilizar la
opción x:
% tar xvf practicasSoi.tar
Observamos como el archivo tar no desaparece al finalizar el trabajo, pues nos sirve como
copia de seguridad. Con las opciones x y t también podemos recuperar archivos concretos:
con la primera debemos conocer el camino de acceso al archivo, con la segunda no. Por
ejemplo, podemos recuperar una práctica concreta de la forma:
% tar –xvf practicasSoi.tar Sesion2/ejercicio1.tcsh

14 Expresiones regulares
Un usuario avanzado o un administrador del sistema que desee obtener la máxima potencia
de ciertas órdenes, debe conocer y manejar las expresiones regulares que son expresiones para
especificar un patrón de búsqueda dentro de una cadena o archivo. La Tabla 1.12 muestra los
caracteres de posición y de patrón, y su significado. Entre las herramientas más frecuentes que
utilizan expresiones regulares tenemos: awk, ed, egrep, grep, sed, y vi.
Para ilustrar el uso de expresiones regulares con algunas de las órdenes anteriores,
partiremos del archivo agenda que vimos en el apartado de “cortar y pegar”. Ya utilizamos
grep para reconocer patrones sencillos, ahora construiremos patrones más sofisticados.
Tabla 1.12 .- Metacaracteres utilizados en expresiones regulares.

Metacarácter Descripción Soportado por


. Iguala cualquier carácter único Todas
* Iguala el carácter previo cero ó más veces Todas
+ Iguala el carácter previo una ó más veces awk, egrep
? Iguala el carácter previo 0 ó 1 vez. awk, grep
^ Inicia la igualación al principio de la cadena. Todas
$ Iguala al final de la cadena Todas
| Operador OR awk, egrep
\ Secuencia de escape ed, sed, vi
( ) Grupo Todas
[ ] Conjunto Todas
{n} Iguala si hay n apariciones del carácter Todas
precedente
Por ejemplo, si queremos ver aquellos nombres que comienzan por alguna letra de la A la G:
% grep ‘^[A-G]’ agenda
Doraimon doraimon@dibujos.com 234.456.567
Gigante gigante@notengo.jp 567.234.567
Para ver aquellas entradas que contienen al menos ocho letras minúsculas consecutivas:
% grep ‘[a-z]\{8\}’ agenda
Doraimon doraimon@dibujos.com 234.456.567
Sisuka sisuka@micorreo.org 456.832.349

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 43


Si deseamos ver aquellas líneas que contienen nombre que empiezan por ‘Do’, tenemos dos
formas de hacerlo:
% grep ‘^Do’ agenda
% grep ‘\<Do’ agenda
Doraimon doraimon@dibujos.com 234.456.567
O para las que terminan en 7:
% grep ‘7$’ agenda
% grep ‘7\>’ agenda
Doraimon doraimon@dibujos.com 234.456.567
Gigante gigante@notengo.jp 567.234.567
Podemos, utilizar ahora egrep para ver aquellas líneas que tengan las palabras Nobita o Suneo,
y que muestre el número de línea que es:
% egrep –n ‘Nobita|Suneo’ agenda
Aquellos nombres que comienzan por S ó G podemos verlos con:
% egrep “^D|^G” agenda
Doraimon doraimon@dibujos.com 234.456.567
Gigante gigante@notengo.jp 567.234.567

15 Operaciones sobre medios separables

Linux permite que los usuarios tengan acceso a los dispositivos separables sin necesidad
de tener permisos de root. Vamos a describir brevemente una de las formas en las que un
usuario puede acceder a sistemas de archivos MS-DOS. El paquete mtools nos permite acceder
a sistemas de archivos FAT y VFAT. La Tabla 1.13 muestra las órdenes más comunes del
paquete así como su equivalente en MS-DOS. El archivo /etc/mtools.conf determina la
capacidad de las órdenes para acceder a los dispositivos con sistemas de archivos MS-DOS.
Tabla 1.13.- Ordenes de mtools.
Orden Orden MS-DOS Descripción
mattrib attrib Establece los atributos de un archivo DOS.
mbadblocks - Localiza y marca los bloques malos del disco.
mcd cd Cambia de directorio.
mcopy copy Copia archivos de/hacia un sistema archivos DOS.
mdel delete Borra archivos.
mdeltree deltree Borra directorio y los archivos bajo él.
mdir dir Muestra un listado del directorio.
mdu - Muestra el espacio en disco.
mformat format Realiza un formateo mínimo del disquete.
mlabel label
Establece la etiqueta de volumen.
mmd Crea un directorio.
mkdir
mmove Mueve o renombra archivos o directorios.
move
mrd Elimina un directorio.
rmdir Renombra un archivo.
mren
ren Muestra un archivo de texto.
mtype
type Realiza órdenes específicas sobre un disco Zip.
mzip
-

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 44


16 Algunas herramientas para vigilar procesos

Podemos ver el estado nuestro sistema mediante herramientas que permiten vigilar la
ejecución de los procesos. En Linux, estas herramientas utilizan el seudo-sistema de archivos /
proc y están contenidas en el paquete procps. En el encontramos: top, vmstat, free, uptime,
w, etc. En Red Hat algunas herramientas también están disponibles en los entornos de
ventanas, como es el caso de ktop, kpm y procinfo en kde, ó gtop (ver Figura 1.6) en gnome.
• Comprobación de los dispositivos
El pseudo-sistema de archivos /proc permite consultar los dispositivos reconocidos por
el kernel de Linux. La siguiente orden nos muestra parte de la información que podemos
obtener de este tipo:
% cat /proc/cpuinfo # información sobre el procesador
% cat /proc/devices # sobre los dispositivos
% cat /proc/interrupts # interrupciones
% cat /proc/dma # DMA
% cat /proc/ioports # puertos de entrada/salida de dispositivos
% cat /proc/pci # dispositivos PCI del sistema
% ls –l /proc/ide # dispositivos IDE
% ls –l /proc/scsi # dispositivos SCSI

Figura 1.6.- Ejemplo de ejecución de gtop.


• Orden ps
La orden ps nos da una instantánea de los procesos del sistema actuales (cuando se
ejecuta la orden). Si queremos tomar diferentes instantáneas debemos de utilizar top.
ps [opciones]
Algunas opciones de esta orden:
-e que muestra todos los procesos
-u procesos de un usuario especificado
-l formato largo
-o formato definido por el usuario, …
ps sin opciones ni argumentos da información sobre los procesos de la sesión actual.
Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 45
Por ejemplo, si deseamos ver los porcentajes de uso de CPU de los procesos del sistema (en el
manual en línea podemos ver los diferentes formatos que podemos utilizar en la orden) podemos
ejecutar la orden:
% ps –eo pid,%cpu
PID %CPU
124 0.0
230 0.3
231 …
Si bien con esta orden podemos ver las relaciones de parentesco entre los procesos del
sistema deduciéndolas de los campos PID y PPID, es más sencillo verlas con la orden pstree
que construye un árbol con los procesos del sistema para nosotros (la única pega es que lo
realiza para el árbol completo no para sub-árboles).

• Orden vmstat
La orden vmstat permite obtener una estadística del uso de memoria virtual, mostrándonos
información sobre los procesos, la memoria, la paginación, los dispositivos de E/S de bloques,
las trampas, y la actividad de CPU. Esta orden esta pensada para identificar cuellos de botella en
el sistema. La implementación de Linux no cuenta a la propia vmstat en las estadísticas.
Sobre la resultados, recordar que todas las implementaciones actuales de Linux utilizan
bloques de disco de 1KB, excepto para los CD-ROMs que son de 2KB. Hay que resaltar que los
valores de la primera línea del resultado de la orden recogen los promedios estadísticos desde el
arranque del sistema por lo que no son muy fiables.

vmstat [-n] [retraso] [contador]


La opción –n provoca que la cabecera se muestre una sola vez, en lugar de periódicamente.
retraso es el periodo que debe transcurrir entre actualizaciones (en sg), y contador el
número de actualizaciones que deseamos hacer. Los campos que muestra la orden son:
Procs
r: nº procesos esperando para ejecutarse
b: nº procesos durmiendo (bloqueados) ininterrumpidamente.
w: nº de procesos sacados de memoria (este campo es calculado).
Memory
swpd: Memoria virtual total en uso (KB).
free: Cantidad de memoria libre (KB).
buff: Cantidad de memoria utilizada como búferes (KB).
cache: Caché en uso (KB).
Swap
si: Memoria transferida desde el dispositivo a memoria (KB/s).
so: Memoria transferida al dispositivo de intercambio (KB/s).
IO
bi: Bloques enviados a un dispositivo de bloques (KB/s).
bo: Bloques recibidos desde un dispositivo de bloques. (KB/s).
System
in: nº de interrupciones por segundo (incluida la del reloj).
cs: número de cambios de contexto por segundo.
CPU
us: % del tiempo total de CPU ejecutándose en modo usuario.
sy: Ídem en modo kernel.
id: Ídem tiempo ocioso.

Para obtener una estadística con 5 instantáneas tomadas a intervalos de 2 sg cada una:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 46


% vmstat 2 5

procs memory swap io system cpu


r b w swpd free buff cache si so bi bo in cs us sy id
0 0 0 12164 956 636 11552 1 2 4 2 108 27 4 2 94
0 0 0 12164 912 644 11588 0 0 1 0 111 21 0 2 98
0 0 0 12164 924 644 11588 0 0 0 0 109 12 1 1 98
0 0 0 12164 928 644 11588 0 0 0 0 109 17 1 1 98
0 0 0 12164 936 644 11588 0 0 0 0 109 13 1 1 98

Otra herramienta que nos da información sobre la memoria es free (ver info free).

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 47


Ejercicios:
1. Desplacémonos hasta el directorio /bin, genere los siguientes listados de archivos (siempre
de la forma más compacta y utilizando los metacaracteres apropiados):
a) Todos los archivos que contengan solo cuatro caracteres en su nombre.
b) Todos los archivos que comiencen por d ó f.
c) Todos los archivos que comiencen por sa, se, ad.
d) Todos los archivos que comiencen por t y acaben en r
2. Cree un archivo, sin usar ningún editor, con tres de las órdenes que hemos realizado en los
ejercicios anteriores. Después, añada dos nuevas órdenes al archivo sin usar ningún editor.
3. Construya una línea de órdenes que liste todos los archivos del directorio /bin que comiencen
por h y que, en caso de que no hubiera ninguno, muestre el siguiente mensaje: "no hay
ninguno". Para mostrar una cadena de caracteres puede utilizar la orden echo.
4. Construya una línea de órdenes que nos dé como resultado el número de archivos y
directorios que hay en el directorio de trabajo y, a continuación, imprima la fecha y hora
actual. Para ello necesitará conocer dos ordenes más: wc (cuenta el número de líneas,
palabras y caracteres) y date (imprime la hora y fecha actual). Utilice man para conocer más
sobre ellas.
5. Cree dos ficheros con unas cuantas líneas cada uno. Escriba una orden adecuada para añadir
el contenido del primer archivo al del segundo.
6. Suponed que estáis depurando una aplicación que necesita utilizar unas variables de entorno
con unos valores determinados, mientras que el entorno de depuración necesita otros valores
para esas variables ¿Cómo podríamos hacerlo? Otro ejemplo con la misma solución:
suponed que estamos en una edición de vi y necesitamos ejecutar ciertas ordenes del shell o
de Linux sin abandonar la sesión de edición.
7. Cree un alias, que se llame intercambiar, que acepte dos argumentos. Los dos argumentos
se corresponderán con los nombres de dos archivos existentes cuyos contenidos serán
intercambiados al ejecutar el alias. Por ejemplo, si el archivo fich1 contiene la palabra hola
y el archivo fich2 contiene la palabra adios, al ejecutar el alias y darle como argumentos los
nombres de estos dos archivos, el resultado será que fich1 contendrá la palabra adios y
fich2 contendrá la palabra hola.
8. Cree una orden alias llamada crearA (crear archivo) a la cual se le pasa un argumento (el
nombre del archivo a crear). Previamente ha de visualizar un mensaje indicando que para
terminar de introducir datos en dicho archivo se ha de pulsar <Ctrl>-D. Una vez creado, ha
de cambiar los bits de modo (o bits de protección) para permitir su ejecución para el
propietario y para el grupo del propietario del archivo.
9. Cree un alias que muestre de entre todos los procesos del sistema aquellos cuyo nombre
coincide con el que se especifica como argumento. Por ejemplo, si el alias se llama psq,
podemos invocarlo
% psq portmap
rpc 437 1 11:07 ? portmap
Notas: la opción –v de grep invierte la selección. Use ps para ver los procesos.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 48


10. Escriba una orden que muestre del archivo agenda, que vimos en los ejemplo, las líneas que
empiezan por S y contienen la letra k. El resultado de la orden deberá mostrar también el
número de las líneas que satisfacen la condición.
11. Idea un procedimiento para que toda las ordenes, resultados, etc. de una sesión de trabajo
sean visibles en pantalla y además se guarden en un archivo. Por ejemplo, si durante la
sesión ejecutamos un ls –l, el resultado de este debe aparecer en pantalla y registraremos
en un archivo tanto la invocación de la orden como el resultado.
12. Es frecuente que nuestra en nuestra cuenta de trabajo de la Escuela ocupemos todo el
espacio asignado (podemos verlo con quota –v o bien con du $home). Practica con tar
y gzip para ahorrar espacio y salvar una parte de la jerarquía de directorios de tu cuenta.
13. Crea un texto “documentación aquí” que presente por pantalla la información siguiente (sin
usar la orden echo):
su path por defecto es: ‘path’
está conectado al terminal: ‘terminal’
su nombre de usuario ‘nombre’ corresponde al uid ‘uid’.

14. Formar un alias para history que imprima la lista de los eventos formateada en 3
columnas. Para hacerlo necesitamos las siguientes ordenes:
cut [-cf] [archivos] - vista en el guión.
pr [-número] [archivos] - Formatea uno o más archivos de acuerdo con las
opciones especificadas en la salida estándar.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 49


Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 50
Módulo I - Parte II:

LA PROGRAMACION DEL TCHSHELL

En las siguientes sesiones, veremos dos elementos importantes para la construcción de


programas del shell: las variables del shell y las órdenes de control de flujo.

● Respecto a las variables, veremos cómo definirlas, cómo afectan a la ejecución de


los programas y cómo destruirlas. Concretamente veremos:
 Las variables de entorno y las variables locales, y cómo definirlas,
 El uso de las variables empotradas que aceptan valores: $argv, $autologout,
$cwd, $home, $path, $prompt, $status, $time, $watch, $$.
 Variables empotradas que actúan como conmutadores: $filec, $notify,
$rmstar.
 Variables numéricas del shell.
 Formar expresiones con las variables del shell, la orden @, los operadores
aritméticos, de igualación de patrones, de asignación, postfijos, y de consulta de
archivos.
• Instrucciones de iteración, de condición, gestión de interrupciones, y archivos de
configuración del shell.
• Veremos las siguientes órdenes:
Ordenes empotradas
Orden de Linux
chmod @
uuencode break
uudecode continue
foreach
goto
if
onintr
set
setenv
shift
source
switch
tcsh
time
where
while

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 51


Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 52
1 Variables del shell

1.1 Variables de entorno y variables locales


El shell, como lenguaje de programación que es, necesita poder definir y usar variables.
Las variables del shell caen en dos categorías: locales y de entorno. Las dos pueden albergar
valores, cadenas o números, y pueden utilizarse tanto en programas shell como interactivamente.
La diferencia entre ambos tipos radica en su alcance o ámbito.
Cuando damos al shell una orden interactiva, Linux inicia un nuevo proceso para
ejecutar la orden. El nuevo proceso hereda algunos atributos del entorno en el que se creó. Estos
atributos se pasan de unos procesos a otros por medio de las variables de entorno, también
denominadas variables globales.
Por contra, las variables locales y sus valores no pasan de un proceso a otro, y sus
contenidos se descartan cuando salimos del shell en el cual se definieron. Si bien, existen
algunas excepciones, como veremos, en los cuales la asignación de una variable local provoca
también que su contenido se almacene en la correspondiente variable de entorno.
El shell trata por igual a todas las variables: todas están identificadas por un prefijo
especial, el signo dolar ($), que indica que la palabra que sigue es el nombre de una variable.
Por otra parte, para la designación de variables existe una convención: se usan letras
mayúsculas para las variables de entorno, y minúsculas para las variables locales. Como Linux
es sensible al tipo, es importante el uso de mayúsculas o minúsculas. No son lo mismo $COSA,
que $cosa, que $CoSa, etc.

1.2 Asignación de variables del shell


Las ordenes para asignar y visualizar los contenidos de las variables son:
set [-r][var [ = valor]]
set var[n] = palabra
Donde var es el nombre de la variable, compuesto por letras, dígitos y/o subrayado (_). Los
nombres de variables deben comenzar por una letra y tener un máximo de 20 caracteres. Si
una variable no existe, la orden set la crea.
set sin argumentos visualiza los valores de todas las variables locales. Con el argumento
var solo asigna un valor nulo a la variable. Si el argumento es var = valor, entonces se
asigna valor a var. El valor puede ser una palabra o (lista_de_palabras).
Antes de ser asignados, todos los valores son expandidos en órdenes y nombres de archivos.
Las variables multipalabra son matrices cuyos elementos pueden ser accesibles
individualmente, indicando su posición dentro de la matriz con [ ] (comenzando con el
elemento número 1). Con la opción –r se declaran de sólo lectura.
Ejemplos:
% set varprueba = prueba123
% set matrizpru = (prueba 1 2 3)
% set varvacia
% set matrizpru[3] = 2

Por ejemplo, si deseo ver los valores de mis variables locales y de entorno, podré hacerlo
con las órdenes set y setenv sin argumentos, respectivamente:
1% set

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 53


argv ()
autolog 10
cwd /home/jose
history 50
home /home/jose
....
2% setenv
EXINIT=set ai wm=5 ts=4
HOME=/home/jose
....
Algunas variables de entorno:

Variable Descripción
DISPLAY Designación del dispositivo de pantalla física y X server
EDIT Camino de nuestro editor favorito
EXINIT Cadena de inicialización de vi/ex
HOME Camino del directorio home
HOST Nombre del ordenador
MANPATH Camino de búsqueda para la orden man
PATH Camino de búsqueda de órdenes para el shell
PRINTER Impresora por defecto
SHELL Nombre del shell de entrada
TERM Tipo de terminal o emulador del terminal
USER ó LOGNAME Identificador de usuario
VISUAL Camino del editor a pantalla completa preferido

Ejecuta set y setenv en tu equipo para ver que variables tienes definidas. Su significado lo
iremos viéndolo en los siguientes apartados.

• Asignación de variables locales


Veamos con más detenimiento la manipulación de variables locales. Las órdenes que se
dan a continuación crean y/o asignan una serie de variables
3% set mivar
4% set nuevavar = "Datos de prueba"
5% set numvar = 5
6% set colores = (rojo naranja amarillo verde azul)
El shell no distingue entre variables que contienen cadenas o números. Con una variable
inicializada como en la orden número 3, tenemos la opción de almacenar en ella cualquier dato.
Las órdenes (4, 5 y 6) crean variables con un valor inicial. La diferencia entre las órdenes 4 y 6
es la siguiente: en 4, creamos la variable y le asignamos como valor la cadena "Datos de
prueba" (aunque contiene tres palabras, estas se tratan como un todo); en la 6, creamos una
matriz de 5 elementos.
Como indicábamos en la introducción, las variables locales no sobreviven al proceso que
las creo. Para verlo, vamos a crear una variable en un subshell y veremos que no existe cuando
salimos de él:

% set variable=”Mi casa”


% tcsh # creamos otro shell, hijo del anterior

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 54


% set variable=”Mi cole” # definimos variable con otro valor
% echo $variable
Mi cole
% exit # volvemos al shell original
% echo $variable # la variable no ha cambiado
Mi casa

• Mecanismo de sustitución de órdenes

El shell posee un mecanismo denominado sustitución de órdenes. Este es muy útil


para la generación de listas. El mecanismo se indica por el operador acento grave “`” (tecla a
la derecha de la “P”); no confundir con la comilla simple o acento agudo. Para usar la
sustitución, encerramos cualquier orden válida de Linux entre comillas graves. El shell
ejecutará la orden que hay dentro de los acentos graves antes de expandir los nombres de
archivos de una línea de órdenes. La expresión delimitada por los acentos graves es sustituida
por la salida resultante de ejecutar la orden. Entonces, se produce la evaluación de los nombres
de archivos, y, a continuación, el resto de los pasos que ejecuta el shell para procesar la orden.
Veamos cómo generar una lista de archivos y asignarla a una variable multipalabra:
7% ls
archivo1 programa.c soi
8% set listaarchi=`ls` #asigna a listaarchi el resultado de ls
9% echo $listaarchi
archivo1 programa.c soi
Otro ejemplo de uso de la sustitución de órdenes, podemos listar los archivos fuente que
contengan la palabra saldo con
10% ls `grep -l saldo *.c` # El resultado grep se usa como argumento de ls
nominas.c
Además de generar listas, otro uso es sustituir órdenes por el resultado de su ejecución. Por
ejemplo, podemos ejecutar de una vez:

11% echo “El número de procesos es `ps |wc –l`”

• Asignación de variables de entorno

El uso de variables globales es similar al de las variables locales. Las diferencias entre
ambas están en:
o Para variables de entorno usamos setenv, en lugar de set.
o setenv usa un espacio blanco en lugar de = entre el nombre de la variable y su valor.
o El valor de las variables de entorno debe ser una palabra simple o una cadena
entrecomillada, pero no una variable multipalabra.
Algunos ejemplos:
11% setenv MYVAR 500
12% setenv DISPLAY jose:0.0
13% setenv CADENA "Esto es una cadena"
Existen algunas variables de entorno muy utilizadas - $USER, $TERM, y $PATH - que son
importadas automáticamente de las variables $user, $term y $path, por lo que no hay

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 55


necesidad de ejecutar una orden setenv para ellas. Además, el shell asigna a la variable $PWD
el valor que en cada momento tenga la variable $cwd. (directorio de trabajo -ver apartado 3)
Las variables de entorno, además de ser vistas por el shell, son accesibles desde los
programas que ejecutamos desde el shell (recuerda cómo obtener las variables de entorno en
un programa en C -extern char **environ, getenv, putenv, etc). Por ejemplo, si
ejecutamos programa desde el shell podemos ver que el proceso que ejecuta programa
tiene el entorno definido por las variables de entorno del shell:
% setenv
HOME=/home/usuario
PATH=/usr/local/bin:/bin
SHELL=/bin/tcsh
USER=usuario
HOST=mipc
% programa&
% cat /proc/123/environ # muestra entorno del proceso con pid=123 ,nuestro programa.
HOME=/home/usuarioPATH=/usr/local/bin:/binSHELL=/bin/tcshUSER=usuarioHOST=mipc

2 Programas del shell

Vamos a hacer un paréntesis en la explicación de las variables para explicar con detalle
cómo crear y ejecutar programas escritos en el lenguaje del shell.

• Utilizando la orden tcsh


La orden tcsh ejecuta un tc-shell. La sintaxis para la orden tcsh es:
tcsh [-opciones] [argumentos ...]
De las diferentes opciones, unas opciones son más apropiadas para el funcionamiento
interactivo y otras para el no interactivo.
Excepto para las opciones -c, -i, -s y -t, en el resto se asume que el primer argumento
dado, que no sea una opción, es el nombre del programa u orden a ejecutar. Los siguientes
argumentos son parte de la lista de argumentos del programa u orden.

Algunas de las opciones de la orden son:


-b (opción de ruptura)
Permite separar lo que son opciones para el tcsh, que van antes de la -b, de lo que son
opciones para la orden, van después de la -b.
% csh miprograma -f -v -b -q -x
Las opciones -f y -v son procesadas por tcsh, y las -q y -x por miprograma.
-c (opción archivo de ordenes)
La usamos para iniciar el shell en modo no interactivo, es decir, que toma las órdenes de
un archivo. Cualquier argumento que aparezca en la línea de órdenes de tcsh después del
primer argumento se deposita en la variable $argv.
% csh -c archivoprograma -v -a archivo1 archivo2
Ahora, sólo -c es procesada por tcsh. Necesitamos la -c puesto que pudiera no estar
claro que miprograma es un archivo de órdenes.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 56


-f (opción arranque rápido)
Indica que no se lea el archivo de configuración .cshrc, por lo que produce un arranque
más rápido. Se usa principalmente en modo no interactivo y suele usarse a menudo en la
primera línea de los programas del tcsh.
Algunas de las opciones útiles a la hora de depurar los programas del shell:
-n (opción no ejecución)- La usamos cuando estamos escribiendo un programa shell y
queremos comprobar la sintaxis. El shell aísla e interpreta las órdenes, pero no las
ejecuta.
-v (opción verbosa) Activa la variable $verbose, de forma que la entrada de órdenes se
muestra después de la sustitución de la historia.
-x (opción ejecución) Activa la variable $echo, mostrando las órdenes inmediatamente
antes de su ejecución.
-V Activa la variable $verbose, incluso antes de ejecutar .tcshrc.
-X Activa la variable $echo, incluso antes de ejecutar .tcshrc.
Sea el programa en tcshell que realiza lo siguiente: escribe en un archivo la fecha y hora
actual y los procesos que el usuario tiene en ese momento.
% cat ejemplo
#! /bin/tcsh -f

set fecha=`date`
set dia=$fecha[3]
set mes=$fecha[2]
set hora=$fecha[4]

# obtenemos los identificadores de los procesos


# en formato “pid nombre”, y sin cabecera
set procesos=(`ps -ho "%p %c"`)

echo Fecha: $dia $mes $hora Procesos: $procesos >>traza.dat

En el programa anterior hay un error, y al ejecutarlo el shell nos diría


% tcsh –f ejemplo
set: No existe correspondencia.
Como hay varias declaraciones set, no sabemos a cual se refiere. Podemos ver cual es la que
esta mal, invocándolo de la siguiente manera:
% tcsh –fv ejemplo
set fecha=`date`
set dia=$fecha[3]
set mes=$fecha[2]
set hora=fecha[4]
set: No existe correspondencia.
Podemos observar cómo en la declaración donde se ha detenido la ejecución falta un $
para referirnos al valor de la variable.

• Archivos interpretados

Hemos visto cómo ejecutar un programa escrito en el lenguaje del shell con la orden
tcsh, pero quizás la forma más común de ejecutar programas de un shell es a través de los
archivos interpretados. Para implementar este mecanismo, los diseñadores de Unix
sobrecargaron la funcionalidad de la llamada al sistema que permite ejecutar un programa, la

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 57


llamada exec(). De forma que, cuando mandamos a ejecutar un programa, la función exec
comprueba si el archivo es binario o no. Si es un binario (resultado de una compilación) lo
ejecuta en la forma usual. Pero si el archivo no es binario, la función exec esta diseñada para
suponer que lo que contiene el archivo son órdenes para un intérprete de órdenes, por lo ejecuta
un shell y le pasa a éste el contenido del archivo del programa para que lo interprete. De aquí el
nombre de archivos interpretados. Ahora bien, el shell creado por exec por defecto es un Bourne
shell ¿Qué ocurre si queremos ejecutar un programa escrito en otro lenguaje? En este caso se
sigue el siguiente convenio: la primera línea del archivo interpretado debe contener el programa
intérprete al que se le debe pasar el resto del contenido del archivo. El convenio para indicar el
intérprete es:
#! path_absoluto_del_interprete [argumentos]
De esta manera, podemos ejecutar programas shell de una manera sencilla. Por ejemplo:
% cat >prueba # creamos un programa en tcsh
#! /bin/tcsh -f # indicamos a exec que debe crear un tcsh
set var=345
echo El valor de la variable es $var
if ( $var < 500) then
echo valor menor que 500
else
echo valor mayor que 500
endif
<CTRL>-D
% chmod u+x prueba # damos permiso de ejecución
% ./prueba # ejecutamos prueba directamente
El valor de la variable es 345
valor menos que 500
Si no hubiésemos especificado el intérprete en la primera línea, se hubiese creado un
Bourne shell para ejecutar el programa, y nos daría errores de sintaxis ya que la sintaxis de las
órdenes de Bourne shell difiere de la del tcsh. La ventaja de los archivos interpretados es que no
debemos conocer el lenguaje en el que están escritos los programas para poder ejecutarlos, es el
sistema el que se encarga de ello.

3 Variables empotradas que admiten valores

El tcshell tiene variables locales empotradas que se tratan de forma especial ya que
controlan algún aspecto de su funcionamiento. Cuando se asigna o cambia el valor de esas
variables, la conducta del shell varía. Dado que son variables locales, deben asignarse para cada
shell que ejecutemos. En esta sección, explicaremos las variables empotradas de uso más
frecuente. Una lista completa puede verse en la página del manual en línea de tcsh (man tcsh).

• La lista de argumentos $argv

Al escribir un programa del shell, podemos usar la variable $argv para tener acceso a los
argumentos de la línea de órdenes. Esta es una variable multipalabra, donde cada elemento de la
matriz corresponde con cada uno de los argumentos de la línea de órdenes, incluido el nombre
de la orden. Esto nos va a permitir utilizar $argv para ver las opciones o argumentos, y poder
así adecuar la operación del programa en función de la presencia/ausencia de determinados
argumentos. Un sinónimo de $argv[n] es $n. El elemento 0 de la lista de argumentos
($argv[0] = $0) representa la orden.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 58


Para ilustrar su uso, vamos a construir un pequeño programa permisos con dos
argumentos: el primero, el nombre de un archivo del cuál se hará un ls -l; el segundo una
cadena de hasta tres caracteres (r, x, w, o combinaciones de ellos). Este programa suprimirá para
el archivo dado como primer argumento, los permisos indicados en la cadena para el grupo y
resto de usuarios. Para ello, debemos saber que la orden para cambiar los permisos de protección
de un archivo/directorio es chmod (change mode):

La orden chmod asigna/modifica los bits de protección de los archivos o directorios


de los que somos propietarios. Tiene la sintaxis:
chmod [-R ] modo lista_archivos
- lista_archivos es el archivo(s) a los que queremos ajustar permisos, la opción -R indica que
se aplique recursivamente a los subdirectorios que haya por debajo.
- modo representa los permisos a modificar. modo tiene dos formas de escritura:
(1) Una forma simbólica: [ugoa]{+|-|=}[rwx] donde cada letra significa:
usuarios: u propietario, g grupo, o el resto, a todos
acción: + añadir, - eliminar, = asignar
permisos: r lectura, w escritura, x ejecución
(2) Un número en octal (como vimos en umask).
Ejemplos de uso con la forma simbólica de modo:
a) Dar permiso de ejecución a todos los usuarios sobre el archivo miprograma
%chmod a+x miprograma #a+x significa: poner(+) ejecución (x) a todos(a)

b) Eliminar los permisos de lectura y escritura de mis archivos de programas fuentes al resto
de los usuarios
%chmod go-rw *.c # quita(-) a grupo(g) y otros(o) permiso lectura(r), escritura (w)
Para prevenir males mayores, suele ser útil auto-restringir los permisos, en especial el de
escritura (esto nos evitará borrar archivos accidentalmente).

Para resolver nuestro ejercicio, creamos un archivo con el siguiente contenido:


14% cat > permisos
echo El programa tiene $#argv argumentos
echo Los argumentos del programa son $argv[1] y $argv[2]
ls -l midir
echo Eliminamos permisos $argv[2] del archivo $argv[1]
chmod go-$argv[2] $argv[1]
ls -l midir
<CTRL>-D
15% _
Una posible ejecución del archivo de órdenes sería.
18% tcsh –f permisos archivo rw
El programa tiene 2 argumentos
Los argumentos del programa son archivo y rw
-rwxrwxrwx 1 jose jose 324 ... archivo
Ajustamos los permisos de archivo para eliminar rw
-rwx--x--x 1 jose jose 324 ... archivo

En el ejemplo, podemos observar una metasecuencia del shell que nos va a ser de gran utilidad,
la expresión $#variable es sustituida por el shell por el número de palabras que tiene la
variable, en nuestro caso $argv. Otra metasecuencia útil es la $?variable que nos devuelve 1
si existe la variable, y 0, en caso contrario.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 59


• El cronómetro de salida automática $autologout

En la protección del sistema frente a accesos indebidos, una regla efectiva es cerrar
aquellas sesiones de trabajo de los terminales inactivos (no utilizados durante cierto tiempo).
Para ello, podemos ajustar la variable $autologout a un valor numérico que expresa un
intervalo de tiempo en minutos en el cual el shell finaliza automáticamente la sesión si el
terminal esta inactivo durante ese tiempo. Si se define pero no se asigna valor a la variable, se
esperan 60 minutos. El siguiente ejemplo ajusta $autologout a 10 minutos
15% set autologout=10 # sale tras 10 minutos de inactividad
Red Hat Linux release 7.1 (Seawolf)
Kernel 2.4.2-2 on an i856
Host login:
Podemos desactivar el cierre automático de la sesión con cualquiera de las formas siguientes:
16% set autologout=0
17% unset autologout

• El directorio de trabajo actual $cwd

La variable $cwd (Current Working Directory) refleja en todo momento el camino del
directorio en el que estamos actualmente situados, el directorio de trabajo. Como comentamos,
el shell modifica la variable de entorno $PWD para reflejar los cambios en $cwd.
22% pwd
/usr/jose
23% echo $cwd
/usr/jose
24% cd /usr/local/bin
25% pwd
/usr/local/bin
26% echo $cwd
/usr/local/bin

• El directorio home $home


La variable $home contiene el camino absoluto del directorio home del usuario. Esta
variable da gran flexibilidad a los programas pues les permite referenciar nuestro directorio
home sin tener que hacer búsquedas en el archivo de contraseñas. También, es útil en los
archivos de arranque, protegiéndonos frente a posibles cambios en la posición del directorio por
parte del administrador.
En el ejemplo siguiente, el uso de $home nos garantiza que el alias definido siempre
funcionará, aunque cambie el directorio home.
29% alias mibin "cd $home/bin"

• La lista de búsqueda del directorio $path

La variable $path contiene la lista de directorios que usa el shell para buscar los
archivos de las ordenes que le damos. Esta variable se inicializa a partir de la variable $PATH,
que es modificada por el shell cuando se realiza algún cambio en $path. El uso de una variable
de entorno para modificar una variable local permite al shell comunicar la lista de caminos de
búsqueda del login shell a sus hijos.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 60


Por ejemplo, para añadir tres directorios nuevos a nuestra lista actual:
32% set path=($home/bin . /usr/local/bin $path)
El orden en el que aparecen enumerados los directorios en la variable es el orden de búsqueda.
Por tanto, si tenemos un programa que se llame igual que una orden de LINUX y queremos que
se ejecute antes que la orden LINUX, nuestro directorio debe aparecer en $path antes que los
directorios de LINUX. Por eficiencia, los directorios que contienen las órdenes más utilizadas
deben aparecer antes en la lista de directorios.
¡Observaciones ! Dos consideraciones respecto de $path:
1) Si desasignamos $path o le asignamos la lista nula, entonces sólo se ejecutarán aquellas
órdenes que se dan con nombres absolutos.
2) Para especificar que el directorio actual se explore en busca de órdenes, debemos poner una
palabra nula ('') en la variable $path. También, se puede poner un punto (.) en la lista, pero
esta última opción esta desaconsejada por razones de seguridad.

• La cadena del indicador del shell $prompt

La variable $prompt permite controlar el contenido y formato del indicador de ordenes


de un shell interactivo (en un shell no interactivo esta variable no se asigna). Hasta ahora, hemos
visto en los ejemplos un indicador que contenía el número de evento y el signo %. Para producir
este formato de indicador podemos ejecutar:
33% set prompt='\! %'
Donde la ! indica que se debe sustituir por el número de evento actual. Pero debemos protegerlo
con \ para que no se procese inmediatamente y por tanto no permanezca fijo.
Veamos un interesante alias de cd para visualizar en todo momento el directorio en el
que estamos, por ejemplo devolvería un indicador como /usr/jose % 31 >.
34% alias cd 'cd \!*; set prompt="$cwd % \! >"'
La variable admite, entre otras, las siguientes secuencias de formatos:
Formato Se sustituye por
%/ El directorio de trabajo actual
%c[[0]n], %.[[0]n] El componente final de cwd, o los n últimos componentes
%h, %!, ! El número de evento actual de la historia de órdenes
%M El nombre completo de la máquina
%n El nombre de usuario
%t La hora en formato 12 horas
… …
Además del indicador principal:
1. Podemos definir el indicador de las órdenes de iteración que veremos en la sesión siguiente.
Este se define con la variable prompt2.
2. La variable prompt3 define el formato de la cadena utilizada para corregir órdenes.

• El estado de retorno de una orden $status


Cada vez que ejecutamos una orden o programa, éste envía al shell un código con el
estado de finalización. Este código esta disponible en la variable $status ó también es
equivalente $?. Cuando trabajamos interactivamente, este valor no tiene importancia pues

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 61


vemos los resultados de las órdenes por pantalla. Ahora bien, en los programas del shell no es
tan fácil ver el estado, por lo que normalmente deberemos consultar el valor de $status para
saber si hay error (valores distintos de cero). Por ejemplo:
35% ls –l ArchiNoExite || echo $status
ls: ArchiNoExiste: No existe el archive o el directorio
1 # estado=1 pues el archivo no existe
36% ls –l *.c && echo $status
archivo.c programa.c
0 # estado = 0 pues existen archivos

• El control del temporizador de ordenes $time


La variable $time controla el temporizador automático de órdenes del shell que nos
indica cuanto tarda una orden en ejecutarse. Cuando está activa, el shell la utiliza para controlar
cuando se informa del al duración de las órdenes y, opcionalmente, del formato de la salida.
La forma más fácil de usar la variable es asignarle un valor umbral expresado en
segundos. Si una orden excede este tiempo, una línea de temporización muestra los recursos
usados. Un segundo argumento de la variable nos permite expresar el formato y tipo de recursos.
La Tabla 2.1 muestra los valores para componer la salida de $time.
Si se elige un valor umbral para $time la información que se muestra por defecto y su
orden es: %U, %S, %E, %P, %X, %D, %I, %O, %F, y %W. En el ejemplo siguiente, se
establece el umbral a 0 segundos y se pone una etiqueta de formato.
37% set time=(0,"Lleva %E CPU, y %F faltas de página")
38% ps # ejecutamos ps, y como umbral es 0 siempre muestra mensaje
123 tcsh
124 ps
Lleva 0:00:010 de CPU, y 96 faltas de página
Tabla 2.1. Etiquetas de recursos de la variable $time.
Etiqueta Descripción
%D Utilización media del espacio de datos no compartido, en KB.
%E Tiempo transcurrido para la orden (en reloj de pared).
%F Faltas de páginas.
%I Número de bloques en operaciones de entrada.
%K Utilización media del espacio usado de pila no compartida, en KB.
%M Máxima memoria real usada durante la ejecución del proceso, en KB.
%O Número de bloques en operaciones de salida.
%P Tiempo total de CPU: Usuario+Sistema, como % del t transcurrido (E).
%S Segundos del tiempo de CPU utilizados por el kernel.
%U Segundos del tiempo de CPU utilizados por el proceso de usuario.
%W Nº de veces que el proceso de usuario fue intercambiado de memoria.
%X Utilización media de memoria, en KB.

• Vigilancia de entradas y salidas del sistema con $watch

Permite la vigilancia de entradas y salidas (login/logout) en el sistema generando una


lista de pares usuario/terminal. Tanto para usuarios como para terminales, si especificamos la
palabra ‘any’ la vigilancia se realiza sobre cualquier usuario/terminal. Por ejemplo, la orden
% set watch = (jose tty1 any console $user any)

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 62


informa de la actividad del usuario ‘jose’ en el terminal tty1, de cualquier usuario en la
consola, y de uno mismo en cualquier terminal.
Además, la variable admite un primer valor que es el intervalo de tiempo que debe
transcurrir entre dos comprobaciones, por defecto, es de 10 minutos. Por ejemplo, para vigilar
cualquier terminal y usuario cada minuto, ejecutaremos:
% set watch = (1 any any)
El formato de la cadena para los mensajes de informes se establece con la variable who.

• El identificador del proceso actual $$

La variable $$ devuelve el identificador (PID) del proceso actual. Se suele usar en


programas para crear archivos temporales, normalmente en /tmp. Si más de una persona usa un
programa, éste necesita crear archivos diferentes para cada una de las posibles ejecuciones. Para
hacerlo podemos incluir la variable $$ como parte del nombre de los archivos. Esto hará que los
nombres de los archivos sean únicos dado que el PID de un proceso es único en el sistema.

39% set architmp=/tmp/tempo1.$$


40% echo $architmp
/tmp/tempo1.1543

• Otras variables de utilidad


o $term – establece el tipo de terminal (en el directorio /usr/share/terminfo aparecen los
tipos de terminales definidos en nuestra instalación). Es útil en aquellas ocasiones que una
aplicación requiere que terminal tenga ciertas propiedades, por ejemplo, gráficas, entonces
debemos cambiar a un terminal que las soporte.
o $uid – el identificador real del usuario (número con el que el sistema conoce al usuario
y por el que determina los permisos del usuario en el sistema).
o $user - nos indica el nombre de entrada del usuario.
o $noclobber - si se activa, se imponen ciertas restricciones a las redirecciones para
asegurase que los archivos no se destruyen accidentalmente.
o $ignoreeof - Si se asigna a una cadena vacía o ‘0’ y el dispositivo de entrada es un
terminal, la orden de fin-de-archivo (generado con <CONTROL>-D) provoca que el shell
muestre el mensaje ‘Use “exit” para abandonar el tcsh’ en lugar de finalizar el
shell. Esto evita que terminemos el shell accidentalmente. Si se asigna al valor n, el shell
ignora n-1 pulsaciones de <CONTROL>-D y finaliza a la n-ésima.
Para una lista completa podemos consultar el manual en línea (man tcsh).

4 Variables que actúan como conmutadores

Este tipo de variable sólo toma dos valores: ON u OFF. La variable está a ON si se ha
definido (no hay que asignarle valor). Si no se ha definido, esta a OFF. Se asignan y desasignan
con las ordenes set y unset, como vimos anteriormente.
Existe una gran variedad de estas órdenes, en este momento sólo veremos dos. Como
muchas ordenes basan su funcionamiento en el valor de una de estas variables, las iremos
detallando más en aquellos apartados donde expliquemos las correspondientes ordenes, por
ejemplo, vimos la variable $notify en el apartado sobre control de trabajos.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 63


● Completar órdenes con $filec
Un mecanismo útil del shell cuando trabajamos de forma interactiva es la terminación
de nombres. Cuando se activa este mecanismo, el shell nos asiste al escribir nombres de
archivos y órdenes. Si conocemos sólo las primeras letras del nombre, el shell nos dará el resto
del nombre. En el tcsh este mecanismo esta activo siempre, en el csh hay que activarlo
declarando la variable $filec. Para indicarle al shell que queremos que complete un argumento,
presionamos una vez la tecla <TAB>. El shell suministra el resto de los caracteres hasta el punto
donde exista más de una posibilidad de igualación entre nombres de los archivos. Si se alcanza
este punto, el shell emite un pitido para indicar que debemos introducir uno o más caracteres
para salir de la confusión. Después de introducir más caracteres, volvemos a pulsar <TAB> de
nuevo, y así hasta que se complete el nombre de archivo. Además, cuando hemos introducido
una porción del nombre de archivo como argumento de una orden, podemos obtener una lista de
los archivos que igualan el patrón hasta este punto pulsando <Control>-D.
40% set filec
41% grep u*o textop<TAB>rueba
hemos usado como ejemplo para mostrar que
hay dentro de un archivo LINUX
42% more texto<CTRL>-d
texto1 texto2 textodatos textoprueba
more textoprueba<Return>
Este es el contenido del archivo textoprueba que
hemos usado como ejemplo para mostrar que
hay dentro de un archivo LINUX

• $rmstar

Al activarla, el shell nos pide confirmación antes de llevar a cabo la operación rm *.


Para finalizar con estas variables, indicar que el shell asigna las siguientes variables
addsuffix, argv, autologout, command, echo_style, edit, gid, group, home, loginsh,
oid, path, prompt, prompt2, prompt3, shell, shlvl, tcsh, term, tty, uid, user, y
version, en el arranque y no cambian salvo que las modifique el usuario. El shell actualiza
cwd, dirstack, owd y status cuando es necesario, y asigna logout al salir. El shell
sincroniza afsuser, group, home, path, shlvl, term, y user con las variables de entorno del
mismo nombre: cuando cambian las variables de entorno, el shell cambia las correspondientes
variables para que sean iguales. Observa que aunque cwd y PWD tienen el mismo significado,
no se sincronizan de esta manera; y que el shell convierte los formatos de path y PATH.

5 Variables numéricas

En este apartado, veremos cómo usar las variables numéricas, cómo formar
expresiones, y qué operadores (Tabla 2.3) existen para formar esas expresiones.
5.1 Expresiones
En el tc-hell se pueden formar expresiones de la misma forma que en C. Algunas de las
órdenes empotradas aceptan expresiones, p. ej. @, exit, if, set, y while. La sintaxis para una
expresión del shell es:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 64


@ [nombre_variable operador expresión]
@ se usa especialmente para escribir expresiones y asignar su resultado a la variable
nombre_variable. Esta variable puede ser simple o multipalabra, indicada esta última
por [n]. Si el destino es una variable multipalabra, ésta debe existir y tener al menos n
palabras.
@ sin argumentos, se comporta como set y muestra los valores las variables. Un ejemplo:
% @ contador = 1 # asigna 1 a contador
% @ contador = $contador + 1 # contador=contador+1.
% echo $contador
2
Observaciones:
 Separe todas y cada una de las partes de una expresión con espacios en blanco, también el
nombre de la variable y el operador.
 Use el signo @ para asignar resultados de expresiones a variables. No use $.
 Use la orden set para asignar un valor constante a una variable.
Tabla 2.3.- Operadores del C Shell, por orden de precedencia.

Grupo Operador(es) Descripción


Paréntesis () Agrupar
Operadores unarios ~ Complemento a uno
! Negación lógica (NOT)
Operadores aritméticos */% Multiplicación, división y resto
y postfijos +- Suma y resta
++ -- Suma y resta postfijas
Ops. de desplazamiento << >> Desplazamiento de bits a izqda. y drcha
Ops. Lógicos < > <= >= Menor/Mayor que, Menor o igual, mayor o igual a
== != Igual a, distinto a
Op. igualación de patrones =~ !~ Patrón de igualación, o no, de nombres
Operadores de bits & AND
^ XOR
| OR inclusivo
Operadores lógicos && AND lógico
|| OR lógico
Operadores de asignación = Asignación
+= x+=y igual que x=x+y
-= x-=y igual que x=x-y
*= x*=y igual que x=x*y
/= x/=y igual que x=x/y
%= x%=y igual que x=x%y
^= x^=y igual que x=x^y

• Operadores aritméticos
Hay que tener siempre presente que todos estos operadores son asociativos por la
derecha, por lo que pueden dar resultados inesperados. Deberemos usar paréntesis para
combinar expresiones explícitamente. Algunos ejemplos:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 65


54% @ contador = 0
55% echo $contador
0
56% @ contador = ( 3 * 5 )
57% echo $contador
15

• Operadores de igualación de patrones


Los metacaracteres vistos también pueden utilizarse para formar expresiones
condicionales y ejecutar igualación de patrones. En muchas ocasiones, necesitamos saber en los
programas shell si un nombre de archivo, o cadena, iguala, o no, con una expresión de
metacaracteres. El shell posee los operadores =~ y !~, para este propósito.
58% set archivo = .profile
59% @ resultado = $archivo =~ .??*
60% echo $resultado
1

En este ejemplo, la expresión $archivo =~ .??* devuelve TRUE (1) si el contenido de la


variable $archivo iguala la expresión de metacaracteres, en caso contrario devuelve FALSE (0).

• Operadores de asignación
Además del operador =, el shell tiene un conjunto de operadores de asignación que son
formas más compactas para escribir expresiones frecuentes. Las expresiones siguientes son
equivalentes: @ x = $x <operador> $y ≡ @ x <operador>= $y, donde <operador> es
un operador aritmético simple (+, -, *, /, %, o ^) .
59% echo $resultado
20
60% echo $valor
5
61% @ resultado *= $valor
62% echo $resultado
100

• Operadores postfijos
El lenguaje de programación tcshell comparte con el lenguaje C dos operadores
aritméticos únicos, los operadores postfijos de autoincremento (++) y autodecremento (--). Estos
simplifican la labor de los programas shell cuando incrementamos/decrementamos contadores.

• Operadores de consulta de archivos


A veces, necesitaremos saber los atributos específicos de un archivo. Para ello
disponemos de los operadores de consulta de archivos, ver Tabla 2.4. Todos estos operadores
comprueban los atributos de un determinado archivo, nombrearchivo.
Los operadores que comprueban los permisos (-r, -w, y -x) y la comprobación de
propiedad (-o) se evalúan tomando como usuario el usuario actual. Si el usuario es el propietario
de nombrearchivo, o tiene permisos de lectura, escritura o ejecución, el operador relacionado
devolverá TRUE, en cualquier otro caso, devolverá FALSE.
63% ls -l architexto /bin/ls
-rw-rw-r-- 1 jose profesor 1024 Oct 19 17:20:00 architexto

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 66


-rwxr-xr-x 1 bin bin xxxx /bin/ls
64% @ xacceso = -x architexto
65% echo $xacceso
0 # no tenemos permiso de ejecución en architexto
66% @ xacceso = -x /bin/ls
67% echo $xacceso
1 # si tenemos permiso de ejecución de ls

Tabla 2.4. Operadores de consulta de archivos.


Operador Descripción
- r nombrearchivo Devuelve TRUE si el usuario tiene permiso de lectura sobre
nombrearchivo, y en otro caso devuelve FALSE.
- w nombrearchivo Idem, pero para permiso de escritura.
- x nombrearchivo Idem, pero para permiso de ejecución (o de búsqueda para directorios).
- o nombrearchivo Devuelve TRUE si el usuario es propietario de nombrearchivo.
En otro caso, FALSE.
- d nombrearchivo Devuelve TRUE si nombrearchivo es un directorio. Si no, FALSE.
- e nombrearchivo Devuelve TRUE si nombrearchivo existe. Si no, FALSE.
- f nombrearchivo Devuelve TRUE si nombrearchivo es un archivo plano o regular.
En otro caso, FALSE.
- z nombrearchivo TRUE si el tamaño de nombrearchivo es cero. Si >0, FALSE.
… Existen algunos + (consultar página de manual del tcsh)

Estudiadas las variables del shell, vamos a ver las órdenes que admite su lenguaje de
programación. La mayoría de las órdenes que estudiaremos se pueden utilizar tanto
interactivamente como en programas, pero es en este último caso donde tienen mayor poder.

6 Ordenes de iteración

• foreach
El lenguaje de programación del tc-shell tiene dos tipos de bucles: foreach y while.
Usamos foreach cuando deseamos ejecutar el cuerpo del bucle un número determinado de
veces. Su sintaxis es:
foreach var (lista_palabras)
bloque de ordenes
end

A la variable var se le asigna en cada iteración un elemento de la lista_palabras y


se ejecuta el bloque de órdenes. Por tanto, se ejecuta tantas veces como elementos hay
en lista _palabras.
Las palabras foreach y end deben aparecer al principio de la línea. Los argumentos
de foreach deben aparecer en la misma línea que foreach. La declaración final end
debe estar sola en la línea.

El programa que sigue muestra un mensaje tantas veces como elementos tiene la lista de
palabras dada a foreach. En el mensaje también aparecen sucesivamente los valores de la lista.
% foreach i (1 2 3) # i tomará, por orden, los valores 1, 2 y 3
? echo "El valor de i es $i"
? end

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 67


El valor de i es 1
El valor de i es 2
El valor de i es 3
Como vemos, cuando damos órdenes multilínea de forma interactiva, el shell detecta que la
orden, en este caso foreach, no ha finalizado en la primera línea y nos muestra el formato del
segundo indicador (definido por prompt2) que, en este caso, es una interrogación. El shell
permanece leyendo órdenes hasta que encuentra un end solitario. Esto ocurre para el resto de
órdenes multilínea que vamos a ver.
Un ejemplo algo más sofisticado, suponed que tenemos varios archivos tar que
deseamos enviar por correo electrónico. Como estos archivos no son ASCII, antes de enviarlos
debemos convertirlos en texto ASCII. Para ello, utilizamos la orden

Codificar y decodificar un archivo binario en uno ASCII:


uuencode [archivo_fuente ] archivo

uudecode archivo

Podríamos construir un bucle de la forma:


% foreach i ( *.tar )
? uuencode $i $i | mailx -s $i amigo
? end
Este bucle se ejecuta tantas veces como archivos tar tengamos en el directorio actual. Si
no tenemos archivos, como el shell realiza la sustitución antes de seguir con la orden, la orden
finalizaría y nos mostraría de nuevo el indicador normal %. Por ejemplo, si no tenemos ningún
archivo .h en nuestro directorio.
% foeach i ( *.h )
foreach: no match
% _
Si ponemos una variable multipalabra como lista de palabras, el bucle se ejecutará tantas
veces como elementos tenga la variable. Por ejemplo, para mostrar los contenidos de los
directorios que aparecen en la variable $path:
% foreach i ( $path )
? echo Directorio $i en el path
? ls $i
? echo " "
? end
Por último, indicar que la variable del bucle no es local al bucle. Su alcance sobrepasa el
cuerpo del bucle y hay que eliminarla con la orden unset.

• while
La orden while suministra un medio de iterar a través de un bucle un número
indeterminado de veces.
while (expresion)
bloque de ordenes
end

Se ejecuta el bloque de ordenes mientras la expresión sea cierta.


while (expresion) y end deben de aparecer solas en sus respectivas líneas.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 68


Vamos a construir un bucle clásico
% set contador = 1
% while ( $contador < 3 )
? echo "Contador vale $contador"
? @ contador = $contador + 1
? end
Contador vale 1
Contador vale 2
Otro ejemplo similar, para explorar las tres primeras posiciones de la variable $path.
% @ i = 1
% while ( $i < 4 )
? echo $path[$i]
? @ i++
? end
/usr/ucb
/usr/bin
/usr/local/bin
Pero no sólo están permitidas expresiones aritméticas, son válidas cualesquiera de las
expresiones condicionales vistas, como por ejemplo las expresiones de consulta sobre archivos.
Si deseamos comprobar el estado de un proceso mientras existe un archivo, podemos escribir
este simple bucle:
% while ( -f /tmp/temporal.123 )
? ps ax
? sleep 10
? end
# salida de ps
Se pueden probar órdenes en el bucle while encerrando éstas entre llaves. Esto provoca
que se evalúe el resultado, y si ha tenido éxito, la expresión se evalúa a 1 (true). Si lo que
interesa es el estado devuelto por la orden podemos usar $status en lugar de las llaves.
% while ! ( { (who | grep x5555) > /dev/null } )
? echo El usuario x5555 no ha entrado aun al sistema
? sleep 5
? end
x5555 no ha entrado aun al sistema

x5555 no ha entrado aun al sistema


....
Además, en la orden podemos ver como la redirección hacia el seudo-dispositivo
/dev/null (que es un sumidero de información) permite que no aparezca en pantalla el resultado
de who, sino sólo el resultado de echo. En el ejemplo, también hemos visto la orden sleep que
permite bloquear a un proceso el número de segundos que aparecen en el argumento.

• break

La orden break provoca la finalización de un bucle. Las órdenes de ruptura se utilizan


para salir de un bloque de órdenes y continuar fuera del bloque. Su sintaxis es:

break
No tiene argumentos.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 69


La orden break se utiliza cuando no se quiere continuar más en el bucle, aunque queden
más iteraciones posibles. Un ejemplo de su utilización es la siguiente rutina que comprueba si un
directorio está en el path. Una vez encontrado, se informa al usuario y se finaliza la búsqueda.
% set busco = /usr/local/bin
% foreach i ( $path )
? if ( $busco == $i ) then # orden if que veremos en breve
? echo El directorio buscado esta en el path
? break
? endif
? end
El directorio buscado esta en el path
Si no hubiésemos puesto la orden break y existiera otra ocurrencia del directorio
necesario, obtendríamos dos mensajes indicando que "El directorio buscado …".
Podemos anidar tantos break como bucles tengamos. Supongamos que queremos
comprobar si existe al menos un directorio común a las variables $path y $cdpath. Por tanto,
cuando encontremos uno saldremos de los bucles.
% set cdpath =(/usr/jose /usr/jose/Doc /usr/jose/bin)
% set path =(/usr/ucb /usr/bin /usr/jose/bin /usr/local/bin)
% foreach i ( $cdpath )
? foreach j ( $path )
? if ( $i == $j ) then
? echo $i es comun a las variables
? break;break # finalizamos la búsqueda al encontrar uno
? endif
? end
? end
/usr/jose/bin
Fíjese que para salir de ambos bucles debe haber dos órdenes break separadas por un
punto y coma. Es necesario un break por cada bucle, y deben estar en la misma línea de
órdenes para que sean tratadas correctamente. Del ejemplo, si quisiéramos conocer todos los
directorios comunes a ambas variables, podríamos haber utilizado un único break y comenzar
otra vez con el siguiente elemento de $cdpath.

• continue

La orden continue se utiliza para reiniciar una iteración del bucle con el siguiente valor.
Su sintaxis es:
continue
Sin argumentos.
Al contrario que la orden break, esta orden permite que el bucle siga con el siguiente
valor pero detiene la iteración actual: si el bucle es un bucle foreach, se utiliza el siguiente
elemento de lista_palabras; si el bucle es un bucle while, se comprueba de nuevo la condición.
Suponga que en el bucle de comprobación del path visto anteriormente, no se quiere
tratar la entrada /usr/bin de la variable $path, pero sí los demás elementos. Entonces:
% echo $path
/usr/ucb /usr/bin /usr/local/bin /usr/jose/bin
% set necesario = /usr/local/bin
% foreach i ( $path )
? if ( $i == "/usr/bin" ) continue

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 70


? echo comprobando $i
? if ( $necesario == $i ) then
? echo El directorio esta
? break
? endif
? end
/usr/ucb
/usr/local/bin
El directorio esta # 2 iteraciones, hemos saltado /usr/bin
Un último ejemplo, queremos ordenar todos los nombres de archivos de nuestro
directorio actual que contengan la palabra “agenda” y dejar sin ordenar aquellos que no la
tengan. Para ello podemos construir el bucle:

% foreach nombrearchiv ( * )
? grep -v agenda $nombrearchivo && continue
? sort $nombrearchivo >>/tmp/ordenados
? end
La orden ‘grep –v agenda $nombrearchivo && continue’ produce una nueva
iteración del bucle para los archivos que no contienen el patrón buscado. La función de la orden
continue podría conseguirse fácilmente utilizando una orden if y agrupando las órdenes que
quedan en un bloque de órdenes, pero continue proporciona una buena forma de reiniciar el
bucle. Además, es más fácil leer y comprender un texto con varios continue que con varios if.

• goto

La orden goto realiza un salto incondicional al punto que se le indica como argumento,
y tiene el siguiente formato:
goto etiqueta
Las etiquetas se sitúan en el programa en la forma etiqueta: al inicio de una línea. El
shell, cuando encuentre la orden goto, continuará la ejecución desde la línea que tenga
la etiqueta dada como argumento. Si no se encuentra la etiqueta, la orden produce el
error “No se encuentra label” y terminará la ejecución del programa. Una
restricción: no está permitido poner una etiqueta dentro de los bucles foreach o while.

La orden goto se utiliza solamente en programas shell, no de forma interactiva. Puede


ser útil para el manejo de errores, por ejemplo:
if ! ( - f miarchivo ) goto fallo
...
fallo:
echo El archivo no existe
exit 1

7 Ordenes condicionales

• La orden if

La orden if tiene dos formatos:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 71


if ( expresion ) orden_simple
ó
if ( expresion ) then
bloque_ordenes
[ else if ( expresion2 ) then
bloque_ordenes2 ]
[ else
bloque_ordenes3 ]
endif
La primera forma ejecuta una única orden si se cumple una condición. La segunda
permite la ejecución de una serie de órdenes si se satisface una condición, con un
conjunto de órdenes opcionales para otras condiciones, bloque else if, y un conjunto
de órdenes si las condiciones no se cumplen, bloque else.

Un ejemplo de la primera forma de esta orden puede ser:


if ( -e miarchi ) echo “existe el archivo miarchi”
La segunda forma de la orden if es más potente:
if ( -e miarchi ) then
echo “existe el archivo miarchi”
else
echo “No existe dicho archivo”
endif

Veamos dos ejemplos de fragmentos de código muy útiles a la hora de construir programas que
usan las secuencias $# y $?:
1. Comprobar que existe una variable, es decir, que está definida, tenga o no valor:
if ($?mivariable) echo $variable
2. Comprobar que el número de argumentos que le pasamos a un programa shell es el que
deseamos:

if ($#argv != 3) then
echo “ No ha introducido todos los argumentos ”
echo “Sintaxis correcta: orden arg1 arg2 arg3”
else
…. # procesamos los argumentos correctos
endif

• switch

La orden switch se utiliza para optar por una de entre varias opciones diferentes.
Aunque, frecuentemente, esta orden se puede construir como una cadena de if , su uso clarifica
la estructura y lectura de un programa. Su sintaxis es:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 72


switch ( cadena )
case etiqueta:
bloque_ordenes
[ breaksw ]
[ default:
bloque_ordenes
[ breaksw ] ]
endsw
Pueden existir cualquier número de opciones (case). La etiqueta debe aparecer en la
misma línea que el case, y no se puede escribir nada después de los dos puntos.
Una etiqueta especial es default: el bloque de órdenes asociado a ella se ejecutará
cuando la cadena no sea igual a ninguna de las etiquetas anteriores. Debe ser la última
opción y sólo puede existir una etiqueta default en una orden switch. La opción
breaksw, finaliza el switch y se continúa por la siguiente orden que sigue al endsw.
Si no aparece, se siguen ejecutando los casos del switch que queden aún por debajo.

La orden switch y sus elementos adicionales (case, default y breaksw) son útiles
para escribir código donde exista una variedad de condiciones. Al contrario que las órdenes if y
while, esta orden no está controlada por una expresión, sino que acepta una cadena y busca a
través de las opciones (case) hasta encontrar una cadena igual. Cuando la encuentra, se ejecutan
las órdenes asociadas. Si no la encuentra y existe una opción por defecto, ejecuta las órdenes
asociadas a ésta. La cadena puede ser un nombre de archivo o archivos (utilizando
metacaracteres) o una variable. Las etiquetas no tienen esta flexibilidad y únicamente pueden ser
cadenas, en el sentido estricto. Un ejemplo, suponga que debe escribir un programa shell que
muestre un menú de opciones para que el usuario elija una, y en función de la elección se
realicen las acciones adecuadas. Para ello, construiremos un fragmento de código similar a:
principio:
cat <<FIN_MENU
Elija una opción:
1. Crear base datos
2. Añadir registros
3. Eliminar registros
4. Modificar registros
5. Salir
Introduzca el número de opción deseada:

FIN_MENU
set opcion=$< # Lee la opcion del teclado
switch ( $opcion )
case 1:
crear # programa shell que crea BD
breaksw
case 2:
añadir # programa que añade en BD
breaksw
case 3:
eliminar # programa que elimina BD
breaksw
case 4:
modificar # programa que modifica BD
breaksw
case 5:
echo “El usuario finaliza el programa”
exit # finaliza el programa shell
default:

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 73


echo Elija una opción entre 1 y 5
goto principio
endsw

Aparte del mecanismo de “documentación aquí” utilizado para crear el menú, es de destacar
cómo hemos resuelto el problema de leer una entrada suministrada por el usuario desde el
teclado. Recordad que la declaración set variable=$< nos permite asignar a variable la
cadena escrita por el usuario en el teclado hasta la pulsación de <RETURN>.
Otra forma de leer un argumento desde el teclado es mediante la orden head, que
muestra la cabecera de un archivo. En nuestro caso utilizamos head para leer una sola línea
desde la entrada estándar. Además, utilizamos otra técnica para no producir un posible salto de
línea entre el mensaje y la captura de la entrada, mediante echo –n, que evita que se produzca el
salto de línea después del mensaje de echo.


echo – n “Introduza la opción”
set variable=`head -1`

8 Otras órdenes

• Gestión de interrupciones con onintr


Un aspecto de la programación en tc-shell es la gestión de interrupciones. Puede ocurrir
que un programa shell necesite capturar la ocurrencia de la señal de interrupción, SIGINT,
generada por <CTRL>-C. La orden para capturar una señal es onintr, tiene la forma:
onintr [ - | etiqueta ]

Cuando no lleva argumentos, se establece la acción por defecto, es decir, terminar. Con
el guión, se ignora la señal (sigue la ejecución normal del programa), y si le acompaña
una etiqueta, cuando se capture SIGINT, se pasará el control a dicha etiqueta y se
ejecutarán las ordenes asociadas a ella.

El siguiente programa muestra su uso, y dos posibles ejecuciones del mismo. Se usa la orden
sleep que suspende la ejecución durante 5 segundos y nos da tiempo a pulsar <CTRL>-C:
% cat >miprograma
#! /bin/tcsh
onintr interrupcion
echo El programa estará parado 5 segundos
sleep 5
echo “Han transcurrido 5 sg, finalizamos”
exit
interrupción:
echo Se ha pulsado \<CTRL\>-C # protegemos < y > de ser interpretados
<CTRL>-D
% -

La primera es una ejecución normal, donde no pulsamos <CTRL>-C. En la segunda, pulsamos


<CTRL>-C mientras que estamos parados en sleep:
% chmod u+x miprograma
% miprograma # primera ejecución
El programa estará parado 5 segundos

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 74


Han transcurrido 5 sg, finalizamos
% miprograma # segunda ejecución
El programa estará parado 5 segundos
Se ha pulsado <CTRL>-C
% _

• shift

La orden shift se utiliza para desplazar los elementos de una variable multipalabra
una posición a la izquierda. Su sintaxis es:
shift [ variable ]

Los valores de la variable son trasladados una posición a la izquierda, y el primer valor
es descartado. Si no se especifica una variable, shift actúa sobre $argv.
Esta orden es útil para realizar un reconocimiento destructivo de los elementos de una
variable. Un ejemplo común de su utilización es cuando, una vez examinados los argumentos de
una línea de órdenes para un programa, y después de que hayan finalizado las acciones
asociadas con dichos argumentos, no es necesario que sus valores permanezcan en la variable.

● La orden source

Como vimos en la primera sesión, el shell crea un hijo para ejecutar la orden en curso.
Por otra parte, vimos que las variables locales no son visibles entre procesos. Por tanto, si en un
programa definimos o modificamos una variable, por ejemplo $path, esta modificación no es
visible a nuestro shell. Si deseamos modificar el entorno de nuestro shell de trabajo, deberemos
indicarle al shell que es él, y no un hijo, el que debe ejecutar un programa. Esto se hace con:

source [ -h ] nombre

El shell actual ejecuta las órdenes del archivo denominado nombre. Si especificamos la
opción -h, las órdenes se añaden a la historia sin ser ejecutadas.

Esta orden es una de las más potentes del tc-shell. Las peticiones de source pueden
anidarse. El único problema que pudiera ocurrir es que el anidamiento sea tan grande que
agotemos los descriptores de archivos disponibles, en cuyo caso la orden falla.
Por ejemplo, el uso inteligente de los alias permitirá crear listas de historias locales (que
cambien con el directorio) automáticamente, como muestra el ejemplo:
97% alias dhist 'history -h > .dhist'
98% alias lhist 'if ( -f .dhist ) source -h .dhist'
El primer alias salva la historia actual en el archivo .dhist. El segundo, recupera la historia del
archivo, si existe. Una vez hecho esto, debemos cambiar el alias cd. Usamos chdir en lugar de
cd para no crear un bucle en la definición de alias cd.
99% alias cd dhist\;chdir \!\*\;lhist
Hemos podido observar en sesiones anteriores que cuando ejecutamos un programa shell que
realiza modificaciones sobre el entorno, estas desaparecen cuando finaliza el programa. Esto se
debe a que si modificamos variables locales, estas no afectan al shell que lanzó la orden. Vamos
a ver como source permite eliminar este problema. Sea el programa que modifica una variable:
% cat programa.tcsh

set variable=valor

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 75


<ctrl.>-d
% ./programa.tcsh # ejecutamos el programa

% echo $var # La variable no esta definida en el shell actual
% source programa.tcsh
% echo $var # Ahora la variable esta definida en el shell actual
valor
% _

● Un ejemplo completo
Para finalizar con las órdenes de control, vamos a ver un ejemplo completo de programa
shell. El programa envía un mensaje a una lista de usuarios, notificándoles una reunión de
trabajo. Las personas que van a ser citadas aparecen en el archivo personal. En el archivo
material aparece una lista de cosas que se solicitan, si hay más personas que cosas, la lista se
reinicia para que cada uno traiga una cosa. El único usuario que no esta invitado es el root.

#! /bin/tcsh –f
# Citas – programa para llamar a reunión de trabajo al personal
# del archivo “personal”, indicando material a traer del
# archivo “material”

set archivopersonal=˜/personal
if (! -e “$archivopersonal” ) then
echo “$archivopersonal: no existe”
exit 1
endif
setenv PLACE “Sala de Reuniones”
@ hora = `date +%H` + 1
set material = (acta informes planificación “lista clientes”)
foreach persona (`cat $archivopersonal`)
if ( $persona =˜ root ) continue
# iniciamos documentación aquí
mail –v –s “Reunión” $persona <<FIN_CITA
Hola $persona nos vamos a reunir en $PLACE a
las $hora en punto. Por favor encargate de que este
para la reunión el material $material[1].
Nos vemos.
El Director General
pepito@grillo.es
FIN_CITA
shift material
if ( $#material == 0 ) then
set material = (acta informes planificación “lista clientes”)
endif
end

echo “Adios …”

9 Archivos de configuración del shell

Muchos programas de aplicación poseen una serie de archivos de configuración que


establecen el entorno de la aplicación durante la fase de arranque de la misma. En Linux, estos
archivos se conocen como archivos silenciosos o archivos punto (.). Se denominan así,

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 76


porque su nombre comienza con un punto, y son silenciosos porque la orden ls esta diseñada
para no mostrarlos (salvo que se indique la opción ls -a). Como otra aplicación más, el shell
posee una serie de archivos de configuración, que serán el objeto de este apartado.

9.1 El archivo inicial de configuración .tcshrc


El nombre de este archivo viene de la expresión tcshell reconfigure. La finalización rc
es una convención informal para nombrar archivos que controlan el arranque de aplicaciones u
órdenes que son configurables. Cuando arranca el tcsh, el primer archivo que se busca y se
ejecuta su contenido es el .tcshrc.

• Asignando variables en .tcshrc

Uno de los principales usos de este archivo es la declaración e inicialización de


variables (locales) y alias. Además de las variables del shell, a menudo necesitamos declarar
variables locales cuyos valores son típicamente el resultado de alguna orden de Linux.
Los ejemplos que siguen muestran parte del contenido de un archivo .tcshrc. Por
ejemplo, para conocer el nombre en la red del computador donde estamos:
# Determina el nombre del anfitrión local
# ejecuta la orden hostname y asigna su resultado a $host
set host=`hostname`
La modificación del camino de búsqueda es frecuente porque si bien tenemos uno
asignado, podemos desear ampliarlo para incluir nuestros directorios personales o directorios
que contienen herramientas o utilidades, que no son parte estándar de Linux. En el ejemplo
siguiente, creamos un camino por defecto y luego lo ampliamos con un directorio personal:
# Asignar el camino por defecto
#
set path = (/var/news /opt2/bin /opt2/gnu/bin /opt/bin \\
/usr/cc/bin /usr/bin /usr/openwin/bin /usr/bin)

# Insertamos un directorio adicional


set path = ($path $HOME/bin)
En el ejemplo, vemos el uso de la \\ al final de la primera línea de asignación de $path,
para indicar que la línea siguiente es parte de la misma línea de órdenes. Esto se hace cuando
la línea de órdenes es más larga que la de la pantalla.
Vamos a ver dos métodos para incluir el directorio actual en el indicador del sistema.
El primero de ellos utiliza las órdenes hostname y pwd.

# Comprobar que existe la variable prompt,


# es decir, es un shell interactivo.

if ($?prompt) then
set prompt = "\! `hostname`: $cwd>"
set interactive = yes
else
set interactive = no
endif

El ejemplo nos permite detectar si el programa que contiene el fragmento descrito va a

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 77


ser utilizado desde un programa o de forma interactiva. Esto es útil porque si no trabajamos de
forma interactica no es necesario definir el indicador.
Este método tiene la desventaja de que ejecuta las órdenes hostname y pwd para cada
aparición del indicador, por lo que es poco eficiente. Veamos otro método más eficiente que
sólo utiliza variables y órdenes empotradas.

# Establece el indicador y comprueba si la variable existe;


# Si la variable existe, entonces se redefine para incluir el
# host, el directorio actual, y el numero suceso;
# asigna 'yes' a interactivo
# redefine cd para modificar el indicador.
# Si no, entonces asigna 'no' a interactive;
#
if ($?prompt) then
# observar: las dobles barras invertidas al final de las tres
# líneas siguientes son importantes para definir un
# alias multilínea. Estas están para "eludir" el NEWLINE de
# forma que quede incluido en el alias
alias prompt 'set noglob;\\
set prompt = "${host}:${prompt[1]}%\!> ";\\
unset noglob'
alias cd 'cd \!*; prompt'
prompt # asegura el nuevo formato para el primer indicador
set interactive = yes
else
set interactive = no
endif

El tcsh lee los archivos de configuración de forma diferente a como lo hace el csh. Si
tenemos un archivo denominado .tcshrc en nuestro directorio home, tcsh lee este archivo. Si
no existe, entonces lee .cshrc. Para evitar tener dos archivos de configuración, podemos poner
únicamente uno, el .cshrc. Para que todo funcione correctamente deberemos proteger las
órdenes y variables exclusivas del tcsh para que no se puedan ejecutar desde un csh. Esto se
puede hacer con siguiente fragmento de código:

# Proteccion de declaraciones propias del tcsh


# Estamos en un tcsh si existe la variable $tcsh
if ($?tcsh) then
alias precmd ‘echo $shell` # ordenes exclusivas del tcsh
....
endif
... # ordenes comunes a csh y tcsh

9.2 El archivo .login


El archivo de arranque .login se ejecuta por el shell de entrada, una vez por sesión. El
funcionamiento es el siguiente: cada vez que se arranca un nuevo tc-shell, después de que éste
lea y ejecute los contenidos de .cshrc, mira si es un shell de entrada; si lo es, lee y ejecuta el
contenido del archivo .login. Normalmente, el archivo contiene órdenes que sólo necesitan
ejecutarse una vez durante la sesión.
Los contenidos típicos del .login incluyen órdenes para establecer las características
del terminal y sus atributos, órdenes para borrar archivos temporales, asignación de variables
de entorno, comprobación del correo, etc.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 78


¡ Precauciones ! No debemos olvidar algunos aspectos importantes sobre .login:
1. Se lee y ejecuta después del archivo .tcshrc. No pongamos órdenes en el .cshrc que
sean dependientes de alguna contenida en .login.
2. Si alguna asignación de variable o ejecución de orden es importante para la correcta
adecuación del entorno remoto, estas pertenecen al .tcshrc. Esto se debe a que los shell
remotos (que no son shell de entrada) no leen .login.
3. En un entorno de ventanas, las ventanas de shell creadas después de la entrada al sistema
no ejecutan un shell de entrada, por lo que no leen .login. En este caso, será necesario
poner las variables de entorno en .cshrc para asegurase de que las ventanas funcionan
adecuadamente.

• Cuidados adecuados y acondicionamiento de nuestro terminal

Normalmente, el archivo .login es el lugar adecuado para asignar nuestro terminal y


establecer sus características y atributos. Para ello se utilizan las órdenes stty y tset. La
orden stty se usa para definir los caracteres de control para un conjunto de funciones básicas
que usamos en el teclado. Esta tiene un gran número de opciones, desde establecer todas las
características de la línea de comunicación (línea serie, módem, etc.) hasta definir las
pulsaciones ligadas a las diferentes funciones de edición de líneas.

9.3 Los archivos .history y .cshdirs


Estos archivos permiten configurar nuestra sesión de trabajo para cubrir dos aspectos
que no se pueden hacer en .tcshrc o .login. El archivo .history permite salvar la historia de
órdenes entre sesiones. El archivo .cshdirs permite guardar la pila de directorios (que no
hemos cubierto en las prácticas por razones de tiempo). A estos archivos no tenemos porqué
darle un contenido específico, sólo debemos crearlos si queremos que el sistema guarde la
información.

9.4 El archivo .logout


El archivo .logout, como su nombre indica, se ejecuta al salir del sistema. Su contenido

# archivo .logout - ejecutado a la salida del shell interactivo


#

clear # Limpia la pantalla


/usr/games/fortune # Finaliza el dia con unas sabias palabras
echo "."
date
exit
# Fin del archivo $HOME/.logout

puede ser prácticamente cualquier cosa, pero normalmente es bastante sencillo y directo. Se
suele ejecutar la orden clear para borrar la pantalla y poco más.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 79


Ejercicios
 
1. Cree una variable local cuyo contenido sea la fecha y hora actual. Utilice la sustitución de
órdenes y la orden date. Recuerde que deberá declarar la variable con set mivar=... .
2. Escriba un programa que indique si un número, que se le pasa como argumento, es par o
impar.
3. A menudo ocurre que se añaden al path muchos directorios, y, a veces, duplicados o
inexistentes. Escribir un programa que elimine de la variable $path los nombres de
directorios duplicados o inexistentes.
Una posible solución consistirá en explorar la variable path e ir volcando en una nueva
variable, $newpath, aquellos directorios (debemos comprobar que son directorios) que no
están duplicados y que existan. El esqueleto de la solución puede parecerse a:
set newpath

foreach element ($path)

if ( -d $element ) then
foreach newelement ($newpath)
if ($element == $newelement ) set no-incluir

end
endif

end
Programe el ejercicio propuesto con las siguientes consideraciones:
a. El problema con el uso dado a la variable no-incluir es que una vez que determinado
element iguala a uno newlement, la variable no-incluir permanecerá por siempre
creada. Modifique lo anterior para establecer una variable (de tipo conmutador) de
nombre incluir antes del segundo bucle foreach, y si se encuentra que la condición
($element == $newelement ) es cierta, desactivar la variable.
b. Añada una sentencia que compruebe si existe la variable incluir, y en caso afirmativo
añada a newpath el valor actual de $element. Se deberá añadir al final del valor
actual de newpath para preservar el orden original de los elementos de path.
c. Para mejorar la velocidad de ejecución, una vez que hemos encontrado un duplicado
y hemos eliminado la variable incluir, incluya la orden break para terminar el
segundo bucle foreach.

4. La orden shift desplaza un único lugar la lista de argumentos. Escriba un programa que
desplace un número de lugares mayor que uno, y que se indica como el primer argumento de
la lista de argumentos. También, debe visualizar el estado en que quedan los argumentos tras
desplazamientos realizados.
Nota: Teniendo presente que el primer argumento es el número de desplazamientos a
realizar, y el resto son los valores a desplazar, necesitamos salvar el valor del primer
argumento y desplazar los restantes. Suponiendo que el nombre del programa es mishft, la
estructura de la solución puede parecerse a:

set count = $1
shift
while ($count > 0)
shift

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 80


end
5. Al asignar a una variable el resultado de dividir dos números cuyo resultado es
menor que 1, se trunca su valor y se almacena el valor 0. Se pide construir un
programa que acepte dos argumentos, que harán los papeles de numerador y
denominador, para mostrar en pantalla el cociente de estos dos números
particularmente conflictivo en el caso de que el numerador sea menor que el
denominador. Ejemplos de posibles ejecuciones:
% cociente 2 100
el resultado de dividir 2 entre 100 es 0.02
% cociente 6 1000
el resultado de dividir 6 entre 1000 es 0.006
% cociente 0 1000
el resultado de dividir 0 entre 1000 es 0.00000
En el caso de cocientes menores que 1, se desea llegar a visualizar hasta la primera cifra
decimal distinta de cero. Se impondrá un límite al número de cifras decimales que se
mostrarán en pantalla, por ejemplo 3. Para que el resultado tenga la apariencia 0.0000
puedes utilizar la orden empotrada glob en lugar de echo.
6. ¿Por qué cuando invocamos al shell sin la opción de arranque rápido (-f) nos aparece
el mensaje que se muestra a continuación?
% tcsh miprograma.tcsh
/proc/888/fd/15: No existe el fichero o directorio
7. Escriba un programa que busque en el directorio o directorios que se pasa como
argumentos, los archivos .ps y .gif, y los borre después de que nos pida confirmación de que
efectivamente queremos borrarlos.
8. Suponga que tiene un archivo ejecutable denominado a.out que desea enviar por correo
electrónico a un amigo ¿Qué órdenes deberás utilizar para pasar el archivo a formato ASCII
y comprimirlo antes de enviarlo?
9. ¿Dónde están los archivos de configuración de nuestro shell? ¿Cuál es su contenido?
Analiza los contenidos de los archivos /etc/csh.cshrc y /etc/csh.login.
10. Cree un programa shell que compruebe si el nombre de un archivo que se le da como
argumento existe y muestre en pantalla uno de estos mensajes (según el caso): (a) el archivo
nombre-arch existe, ó (b) el archivo nombre-arch no existe.
11. Los archivos se almacenan en una serie de bloques de disco de tamaño fijo (en Linux,
generalmente 1KB), pero su tamaño “real” en bytes (por ejemplo, el indicado por ls) no
siempre coincide con su tamaño en bloques de disco (este segundo es siempre mayor o
igual). Construya un programa que acepte como argumento el nombre de un archivo y
que calcule el espacio desperdiciado en disco (es decir, asignado al archivo en disco, pero
no ocupado por él). Para ello, utiliza la orden du.
a. Modifique el programa anterior para que genere un archivo cuyo nombre sea la
concatenación espacio-desaprovechado+espacio-libre y cuyo contenido sea:
tamaño en caracteres = <tamaño>
tamaño en bloques = <bloques>
b. Modifica el programa anterior para que compruebe inicialmente que el
argumento dado es un archivo.
12. Ejecute la orden date y fíjese en el formato estándar con el que presenta la fecha y la hora,
por ejemplo:
Fri Nov 3 12:13:28 WET 1995
Cree un programa que muestre lo siguiente:
Son las 12:13:28 del tres de Noviembre de 1995

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 81


13. Realice un programa que a través de un menú, pueda realizar las siguientes operaciones:
a. Crear varios directorios que se le pasen como argumento, por ejemplo: si le pasamos
como argumento tmp/dir1/dir2/dir3/dir4, creará:
/tmp
/tmp/dir1
/tmp/dir1/dir2
/tmp/dir1/dir2/dir3
/tmp/dir1/dir2/dir3/dir4
b. Matar un proceso pasándole el nombre de la orden (no el PID).
c. Cambiar a un directorio pasándole solo parte del nombre del mismo.
d. Bloquear el terminal hasta que se introduzca la palabra clave (que no tiene porque se
el password) elegida de nuevo. Ejemplo:
Introducir palabra clave: ********
Para desbloquear palabra clave:___________
14. Crear un programa shell que se llame cambiar y que acepte dos argumentos. El primer
argumento representa el nombre de un archivo o de un directorio existente cuyo nombre
queremos renombrar y el segundo argumento es el nuevo nombre. Debéis hacer todas las
comprobaciones pertinentes:
a. El número de argumentos al llamar al programa debe ser el correcto, si no mandad un
mensaje indicando la sintaxis correcta de esta orden.
b. El fichero o directorio dado como primer argumento debe existir, si no existe debe
mandarse un mensaje al usuario y finalizar la ejecución de la orden.
c. El segundo argumento no debe corresponder con ningún archivo o directorio existente,
si lo es, se debe mandar un mensaje al usuario y finalizar la ejecución de la orden.
15. Crear un programa que cuente el número de directorios que hay en el directorio que se le
pase como argumento, en caso de ejecutarlo sin argumentos se realizará la cuenta sobre el
directorio home. Realice todas las comprobaciones que estime oportuno, como que el
número de argumentos dados sea incorrecto o que no exista el directorio dado como
argumento. Utilice para su resolución la construcción foreach.
16. Supongamos que realizamos las siguientes acciones:
% cat > pru
#!/bin/tcsh
set prompt = "\!> "
% chmod u+x pru
% pru
¿Cuál es el resultado de la ejecución? Justifícalo.

17. Crear un programa shell que nos de el nombre de la orden o programa del sistema que
consume más CPU y la que consume más memoria.
18. Cree una máscara para conseguir que los permisos por defecto al crear nuevos archivos y
directorios sean: rw-r----- ¿Cómo conseguiría que el sistema creara estos permisos por
defecto sin tener que ejecutar esta orden en cada sesión?

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 82


Módulo II:

PROGRAMACIÓN CON HEBRAS

• En esta parte, veremos:


 Qué son las hebras.
 Qué son las hebras Posix o Pthreads.
 Algunas consideraciones al diseñar un programa multihebrado.
 La API de hebras Posix: gestión de hebras.
 Sincronización con semáforos.
 Compilación de programas multihebrados.

• En concreto, solo veremos las siguientes funciones de la biblioteca de hebras y


semáforos:

Funciones de hebras Semáforos


pthread_create sem_init
pthread_exit sem_destroy
pthread_self sem_wait
pthread_equal sem_post
pthread_join
pthread_attr_init
pthread_attr_destroy
pthread_attr_setdetachstate
pthread_attr_getdetachstate

Al final se muestra la lista completa de funciones de la API de hebras.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 83


Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 84
Visión general de la PThreads
Como hemos visto en teoría, los procesos tradicionales de un sistema operativo (p.ej.
LINUX) tienen una única hebra (thread), o flujo de control, definido en su espacio de direc-
ciones. Sin embargo, puede ser útil la existencia de varias hebras dentro de un único espacio
de direcciones: cuando se programa una aplicación donde existen varios procesos que necesi-
tan cooperar y/o intercambiar información con el fin de realizar una tarea común, es menos
costoso y más eficiente utilizar hebras que procesos independientes. Recordemos que se redu-
ce substancialmente el tiempo necesario para crear un proceso hijo (una hebra) o para cambios
de contexto entre hebras.
La diferencia entre programar con múltiples hebras y múltiples procesos es doble: in-
dependencia y comunicación. En un programa con múltiples procesos, la separación entre los
procesos está implícita. Sin embargo, la comunicación entre los mismos necesita de una mayor
atención del programador. En cambio, en un programa con múltiples hebras la comunicación
entre las hebras es fácil ya que se realiza a través de memoria común. Sin embargo, garantizar
la independencia de las hebras mediante los mecanismos de sincronización apropiados, resulta
más complicado.
Vamos a usar la biblioteca de hebras LinuxThreads que es una implementación del es-
tándar internacional POSIX.1c (Portable Operating System Interface) para procesos multihe-
brados de LINUX. Utilizar este estándar ayuda a crear programas que sean transportables a
través de distintas plataformas. Igualmente, utilizaremos el C estándar como lenguaje de im-
plementación dado que tiene una interfaz natural con las funciones del sistema y la biblioteca.

1 ¿Qué son las hebras?

Recordad que se define una hebra, o hilo, como un flujo de instrucciones que puede ser
planificado independientemente, ya sea por el sistema operativo o por la biblioteca de hebras.
Pero ¿qué significa esto? En Linux, una hebra:
• Existe dentro de un proceso y utiliza los recursos del mismo.
• Tiene su propio flujo de control independiente mientras que su padre exista y el sistema
operativo la soporte.
• Comparte los recursos del proceso con otras hebras que actúan de forma independiente.
• Muere si el proceso padre muere.
Para el desarrollador de software, el concepto de un “procedimiento” que se ejecuta
independientemente del programa principal puede describir a las hebras. Pero para comprenderlo
mejor, vamos a ver la relación entre éstas y los procesos. Un proceso es creado por el SO y
contiene información sobre los recursos y el estado de ejecución del programa (Figura 2.1). Esto
incluye:
• Identificadores del proceso y de grupo, IDs del usuario y de su grupo
• Entorno
• Directorio de trabajo
• Instrucciones del programa
• Registros
• Pila
• Heap

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 85


• Descriptores de archivos
• Manejadores de señales
• Bibliotecas compartidas
• Mecanismo de comunicación entre procesos (cauces, semáforos, etc.)

Figura 2.1.- Estructura de un proceso clásico.

Las hebras existen dentro de estos recursos y los utilizan. Pero además pueden ser
planificadas independientemente, ya que cada una posee su propio (Figura 2.2):
1. Puntero a pila
2. Copia de los registros
3. Propiedades de planificación (tales como política y prioridad)
4. Conjunto de señales pendientes y bloqueadas
5. Datos específicos de la hebra (almacenamiento local)
Dado que las hebras dentro del mismo proceso comparten los recursos:
 Los cambios realizados por una hebra sobre los recursos compartidos del sistema (tales como
cerrar un archivo) son vistos por todas las demás hebras.
 Dos punteros con el mismo valor apuntan al mismo dato.
 Es posible leer/escribir en la misma dirección de memoria (incluso en la pila), y, por tanto,
requiere la sincronización explícita por parte del programador.

2 ¿Qué son las PThreads?

Históricamente, los vendedores de hardware han implementado versiones propietarias de


hebras. Estas implementaciones difieren sustancialmente de unos a otros, haciendo difícil a los
programadores el desarrollo de aplicaciones de hebras transportables.
Al objeto de obtener beneficios de las capacidades suministradas por la hebras, se
necesitaba una interfaz de programación (API) estandarizada. Para sistemas Linux, esta interfaz
esta especificada por el estándar IEEE POSIX 1003.1c (1995). Las implementaciones que se
adhieren a este estándar se denominan hebras Posix, o Pthreads. La mayoría de los vendedores
hardware ofrecen ahora las Pthreads además de sus API’s propietarias.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 86


Figura 2.2.- Un proceso con dos hebras

Las Pthreads están definidas como un conjunto de tipos y llamadas a procedimientos del
lenguaje de programación C implementados por el archivo de cabecera pthread.h y la
biblioteca de hebras (que suele ser parte de otra biblioteca, tal como libc).
Existen varios borradores sobre el estándar de hebras Posix, por lo que es importante ser
consciente del número de borrador de una implementación dada ya que existen diferencias entre
los borradores que pueden dar problemas.

3 Diseñando programas con hebras

Para poder beneficiarnos de las Pthreads, debemos ser capaces de organizar nuestro
programa en tareas discretas independientes, que se puedan ejecutar concurrentemente. Por
ejemplo, si la rutina1 y rutina2 de la Figura 2.3 pueden ser intercambiadas, solapadas o
intercaladas, son candidatas a ser ejecutadas por hebras.

Figura 2.3.- Posibles intercalados de dos rutinas.

Las tareas que son aconsejables para utilizar hebras incluyen:


 Bloqueos por esperas largas potenciales
 Utiliza muchos ciclos CPU
 Deben responder a eventos síncronos
 Son de mayor o menor importancia que otras tareas
 Son capaces de ejecutarse en paralelo con otras tareas.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 87


Sé cuidadoso si tus aplicaciones utilizan bibliotecas u otros objetos que no garanticen
explícitamente la seguridad frente a hebras. Cuando dudes, asume que no son seguros frente a
hebras hasta que se pruebe otra cosa.
Para que un programa con múltiples hebras funcione bien se ha de asegurar que dos o
más activaciones concurrentes de una misma rutina, compartida por dichas hebras, han de es-
tar correctamente sincronizadas. Por tanto, el acceso concurrente a datos compartidos deja a
éstos en una situación consistente. Si varias hebras pueden llamar simultáneamente a funcio-
nes que pertenecen a un mismo módulo, asegurándose la seguridad y corrección en todo mo-
mento, se dice que tales funciones son mt-seguras (o reentrantes). Por ejemplo, funciones
como sin() que acceden a datos globales sólo en lectura, son trivialmente reentrantes.
Cualquier código que vaya a ser ejecutado de una manera asíncrona por hebras ha de ser
reentrante y ha de tener las siguientes características:
 No se debe cambiar ningún elemento situado en memoria global
 No se debe cambiar el estado de ningún archivo o dispositivo
 Debe hacerse referencia a un elemento situado en memoria global sólo en circunstancias es-
peciales (por ejemplo, asegurando la exclusión mutua en el acceso a datos globales)
El código que se ejecuta con mayor frecuencia, de una manera asíncrona y compartida, son las
bibliotecas del sistema. Por lo tanto, toda biblioteca que se enlace con un programa ha de ser
mt-segura o tener una interfaz mt-segura.
Las funciones de las bibliotecas, se pueden clasificar en cuatro grupos:
1) Funciones que tienen una interfaz mt-segura ya que siempre han sido mt-seguras o que
han sido modificadas para que lo sean.
2) Funciones que no son mt-seguras porque su tamaño se hubiera incrementado demasiado.
3) Funciones que tienen una interfaz no mt-segura.
4) Funciones que son equivalentes a las del tercer grupo, pero que han sido modificadas para
hacerlas mt-seguras. Las funciones de este grupo se las identifica por el sufijo _r.
Tanto las funciones mt-seguras, como las funciones que tienen interfaces mt-seguras, pueden
utilizarse por el programador de forma transparente (ésto es, no tiene que prestarles mayor
atención). Para la mayoría de las funciones con interfaces no mt-seguros han sido desarrolla-
das funciones mt-seguras con sufijo _r. Si las páginas del manual no dicen nada sobre si una
función es mt-segura, entonces lo es. Todas las funciones no mt-seguras son identificadas ex-
plícitamente en el manual.
Las funciones para las que no haya garantía de que sean reentrantes, se las puede utili-
zar en programas con múltiples hebras, si las llamadas a dichas funciones se hacen sólo desde
la hebra principal (main()). También se puede utilizar con seguridad las funciones no-reen-
trantes, siempre que se sincronice el acceso de las hebras a dichas funciones.

4 La API de Pthreads

La API (Application Programming Interface) Pthreads esta definida en ANSI/IEEE POSIX


1003.1c. Las funciones que comprenden esta interfaz están informalmente agrupadas en tres
clases principales:
• Gestión de hebras – estas funciones trabajan directamente con hebras (creándolas,
destruyéndolas, etc.) También, incluye funciones para ajustar o conocer los atributos de las
hebras (planificación, ..)
• Mutexes – Esta segunda clase trata con sincronización. Las funciones de mutex (mutual
exclusion) permite crear, bloquear y desbloquear cerrojos. Además, están suplementadas para
ajustar/consultar los atributos asociados con los mutex.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 88


• Variables de condición – esta clase de funciones abordan las comunicaciones entre hebras
que comparten un mutex. Nosotros no las veremos, pues vamos a utilizar sólo semáforos en
su lugar, lo mismo que ocurre con los mutex.
La convención de nombres que se utiliza es la siguiente: todos los nombres de la biblioteca
comienzan con pthread_
Prefijo de la función Grupo funcional
pthread_ Las propias hebras y diferentes rutinas
pthread_attr_ Atributos de las hebras
pthread_mutex_ Mutexes
pthread_mutexattr_ Atributos de los mutexes
pthread_cond_ Variables de condición
pthread_condattr_ Atributos de las variables de condición
pthread_key_ Claves de datos específicos de la hebra
El concepto de objeto opaco impregna todo el diseño de la API. Las llamadas básicas
trabajan para crear o modificar los objetos opacos – los objetos opacos pueden modificarse
mediante llamadas a funciones de atributos, que tratan con los atributos opacos.
La API Pthread contiene unas 60 funciones. Nosotros sólo nos centraremos en algunas de
ellas, especialmente en aquellas más útiles para el programador principiante. En todos los
programas que utilicen la biblioteca de Pthreads, debemos incluir la cabecera pthread.h. El
estándar actual POSIX esta sólo definido para el lenguaje C. Los programadores de otros
lenguajes como C++, Fortran, etc. deben utilizar wrappers para las funciones C.

4.1 Gestión de Hebras


● Creando hebras

Inicialmente, nuestro programa principal main() incluye una única hebra. Todas las
demás hebras deber ser creadas explícitamente por el programador. La rutina para crear una
hebra es:
#include <pthread.h>

int pthread_create (pthread_t *thread, const pthread_attr_t *attr,


void *(*start_routine) (void), void *arg) ;

Esta función crea una hebra y la hace ejecutable. Típicamente, las hebras son inicialmente
creadas dentro del main() de un único proceso, y, una vez creadas, las hebras son pares y
pueden crear a su vez otras hebras.
La función pthread_create devuelve un IDentificador de hebra a través del argumento
thread. El llamador puede utilizar este argumento para realizar varias operaciones sobre la
hebra. Este identificador puede chequearse para asegurarnos de que la hebra ha sido creada con
éxito. El parámetro attr se utiliza para establecer los atributos de la hebra. Podemos ajustar un
objeto atributo para la hebra, o simplemente poner un NULL como valor por defecto.
Discutiremos más tarde el tema de los atributos. La rutina start_routine es la función C de
nuestro programa que ejecutará la hebra recien creada. Solo podemos pasar un argumento a la
función start_routine a través de arg. Este debe ser pasado por referencia como un “cast” de
un puntero de tipo void.

● Terminado la ejecución de una hebra

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 89


Existen varias formas de finalizar una Pthread:
1. La hebra retorna de su rutina de inicio (para la hebra inicial, el main)
2. La hebra realiza una llamada a la función pthread_exit (que veremos ahora)
3. La hebra es cancelada por otra a través de la llamada pthread_cancel (no cubierta aquí).
4. El proceso entero finaliza debido a una llamada a las funciones exec o exit.
La función para finalizar explícitamente una hebra es:

#include <pthread.h>
void pthread_exit(void *retval);

Normalmente, esta función se invoca cuando la hebra ha completado su trabajo y ya no es


necesaria. Si main() acaba antes que las hebras que ha creado, y finaliza con pthread_exit(),
las otras hebras continuarán ejecutándose. En otro caso, las hebras finalizarán automáticamente
cuando el main() finalice.
El programador puede especificar opcionalmente un valor de retorno retval, que es
almacenado como un puntero void para cualquier hebra que pueda unirse (join) a la hebra
llamadora. Esto lo veremos en breve.
La función pthread_exit() no cierra archivos. Cualquier archivo abierto dentro de la
hebra permanece abierto después de que finalice la hebra. Como recomendación es bueno utilizar
pthread_exit para finalizar cualquier hebra, incluida main().
Un ejemplo sencillo ilustrará como crear y destruir hebras. En el programa siguiente, creamos 5
hebras cada una de las cuales imprime el mensaje “¡Hola Mundo!” y finaliza.

#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5

void *PrintHello(void *threadid)


{
printf("\n%d: ¡Hola Mundo!\n", threadid);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc, t;
for(t=0;t < NUM_THREADS;t++){
printf("Creando la hebra %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR: codigo de retorno de pthread_create() es %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}

● Pasando argumentos a una hebra

Como hemos visto, la función pthread_create permite al programador pasar un único


argumento a la rutina de inicio de la hebra. En el caso de que sea necesario pasar múltiples

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 90


argumentos, esta limitación puede saltarse fácilmente creando una estructura que contenga todos
los argumentos, y pasando ésta como argumento de la función. Todos los argumentos deben
pasarse por referencia y haciendo un “cast” a (void *).
A continuación se muestra un fragmento de código en el que se pasa un entero a cada
hebra. La hebra llamadora utiliza una única estructura de datos para cada hebra, asegurándose
que cada argumento de la hebra permanece intacto a través del programa.

int *taskids[NUM_THREADS];

for(t=0;t < NUM_THREADS;t++)


{
taskids[t] = (int *) malloc(sizeof(int));
*taskids[t] = t;
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) taskids[t]);
...
}

Otro nuevo ejemplo nos muestra como pasar múltiples argumentos a través de una estructura.
Cada hebra recibe una única instancia de la estructura.
struct thread_data{
int thread_id;
int sum;
char *message;
};

struct thread_data thread_data_array[NUM_THREADS];

void *PrintHello(void *threadarg)


{
struct thread_data *my_data;
...
my_data = (struct thread_data *) threadarg;
taskid = my_data->thread_id;
sum = my_data->sum;
hello_msg = my_data->message;
...
}

int main (int argc, char *argv[])


{
...
thread_data_array[t].thread_id = t;
thread_data_array[t].sum = sum;
thread_data_array[t].message = messages[t];
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) &thread_data_array[t]);
...
}

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 91


Para finalizar con el paso de argumentos, un nuevo ejemplo ilustra un paso de argumentos
erróneo. El bucle que crea las hebras modifica el contenido de la dirección pasada como un
argumento, posiblemente antes de crear las hebras pueda acceder a él.
int rc, t;

for(t=0;t < NUM_THREADS;t++)


{
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) &t);
...
}

● Identificadores de hebras

Las funciones para consular sobre identificadores de hebras son:

pthread_self()
pthread_equal (thread1, thread2)

Donde:
- pthread_self devuelve el identificador de hebra, que es único, asignado por el sistema a la
hebra llamadora.
- pthread_equal compara dos IDs: si los dos son diferentes devuelve 0; en otro caso,
devuelve un valor distinto de cero.
Observar que para estas dos funciones, los objetos identificadores de hebras son opacos y no
pueden ser inspeccionados fácilmente. Dado que los objetos IDs son opacos, el operador
equivalencia de C (==) no debe utilizarse para comparar IDs, o para comparar un ID de hebra
con otro valor.

● Uniendo hebras

La unión de hebras es un mecanismo de sincronización entre hebras. La función


pthread_join bloquea a la hebra llamadora hasta que la hebra especificada como argumento
thid termine.

#include <pthread.h>

int pthread_join(pthread_t thid, void **status);

El programador es capaz de obtener el valor del estado de finalización de la hebra por la que
esperaba en status siempre que la hebra haya realizado pthread_exit(). Es imposible unirse a
hebras que ha sido desligadas (este concepto no lo veremos por ahora).

● Desligando/uniendo hebras

Cuando creamos una hebra, uno de sus atributos define si la hebra es ligada (se puede hacer una
unión) o es desligada. Una hebra desligada significa que no se le pueden hacer nunca un

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 92


pthread_join. Por defecto, nuestra biblioteca las crea ligadas. Las funciones para manipular
estos atributos son:
#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);

int pthread_attr_destroy(pthread_attr_t *attr);

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int


*detachstate);

Para crear explícitamente una hebra como ligable o no, se utiliza el argumento attr en la
creación de la hebra. Existen cuatro pasos en el proceso:
1. Declarar una variable atributo del tipo pthread_attr_t
2. Inicializar la variable atributo con la función pthread_attr_init()
3. Establecer el atributo de estado desligada con pthread_attr_setdetachstate()
4. Cuando se termine, liberar los recursos de la biblioteca con pthread_attr_destroy()
La rutina pthread_detach() se usa para desligar explícitamente una hebra incluso si se
creó ligada. No existe la función inversa. El borrador final del estándar POSIX establece que una
hebra debe crearse ligada. Sin embargo, no todas las implementaciones lo siguen.
Como recomendaciones: (a) Si una hebra requiere la unión, considera crearla
explícitamente como ligada. Esto suministra transportabilidad ya que no todas las
implementaciones crean las hebras como ligadas por defecto. (b) Si conocemos por anticipado
que nunca haremos un join sobre la hebra, podemos considerar el crearla como desligada, lo que
permite liberar algunos recursos del sistema.
A continuación, un ejemplo demuestra como esperar la finalización de una hebra
utilizando la función pthread_join. Como no todas las implementaciones crean las hebras
ligadas por defecto, las hebras del ejemplo se crean explícitamente como ligadas.
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3

void *BusyWork(void *null)


{
int i;
double result=0.0;
for (i=0; i < 1000000; i++)
{
result = result + (double)random();
}
printf("resultado = %e\n",result);
pthread_exit((void *) 0);
}

int main (int argc, char *argv[])


{
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int rc, t, status;

/* Initializa y ajusta el atributo desligado */

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 93


pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);

for(t=0;t < NUM_THREADS;t++)


{
printf("Creando las hebras %d\n", t);
rc = pthread_create(&thread[t], &attr, BusyWork, NULL);
if (rc)
{
printf("ERROR; codigo retorno de pthread_create()
is %d\n", rc);
exit(-1);
}
}

/* Libera el atributo y espera las otras hebras */


pthread_attr_destroy(&attr);
for(t=0;t < NUM_THREADS;t++)
{
rc = pthread_join(thread[t], (void **)&status);
if (rc)
{
printf("ERROR; codigo de retorno de pthread_join()
es %d\n", rc);
exit(-1);
}
printf("Union completada con hebra %d estado= %d\n",t, status);
}
pthread_exit(NULL);
}

Un ejemplo: paralelización del código para realizar el producto escalar de dos vectores utilizando
mutex (cerrojo de exclusión mutua). El código se ha escrito generalizado para un número
arbitrarios de hebras y procesadores. Por tanto, el main() instancia estos valores, al igual que
define los vectores a los vamos a aplicar el producto escalar. Cada una de las hebras creada
calcula el valor de un componente del producto, y realiza la suma parcial, después de haber
obtenido un cerrojo para manipularla.

/* usar gcc -D_REENTRANT -lpthread para compilar */


#include<stdio.h>
#include<pthread.h>

/* definicion de una estructura adecuada para pasar los valores a las


hebras que vamos a crear */
typedef struct
{
double volatile *p_s; /* valor compartido del producto escalar */
pthread_mutex_t *p_s_lock; /* cerrojo de la variable s */
int n; /* numero de hebras */
int nproc; /* numero de procesadores a utilizar */
double *x; /* datos del primer vector */
double *y; /* datos del segundo vector */
int l; /* longitud de los vectores */
} DATA;

void *SMP_scalprod(void *arg)


{

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 94


register double localsum;
long i;
DATA D = *(DATA *)arg;
localsum = 0.0;

/* Cada hebra inicia el calculo del producto escalar para i = D.n


con D.n = 1, 2, ... , D.nproc.
Como hay D.nproc hebras exactamente el incremento sobre i es justo
D.nproc */

for(i=D.n;i<D.l;i+=D.nproc)
localsum += D.x[i]*D.y[i];

/* la hebra asegura el cerrojo s ... */


pthread_mutex_lock(D.p_s_lock);

/* ... cambia el valor de s ... */


*(D.p_s) += localsum;

/* ... y elimina el cerrojo */


pthread_mutex_unlock(D.p_s_lock);

return NULL;
}
#define L 9 /* dimension de los vectores */
int main(int argc, char **argv)
{
pthread_t *thread;
void *retval;
int cpu, i;
DATA *A;
volatile double s=0; /* la variable compartida */
pthread_mutex_t s_lock;
double x[L], y[L];

if(argc != 2)
{
printf("usage: %s <numero de CPU>\n", argv[0]);
exit(1);
}
cpu = atoi(argv[1]);
thread = (pthread_t *) calloc(cpu, sizeof(pthread_t));
A = (DATA *)calloc(cpu, sizeof(DATA));

/* Inicializo los vectores con unos valores ejemplo */


for(i=0;i<L;i++)
x[i] = y[i] = i;

/* inicializa la variable cerrojo */


pthread_mutex_init(&s_lock, NULL);

for(i=0;i<cpu;i++)
{
/* inicializa la estructura */
A[i].n = i; /* numero de hebras */
A[i].x = x;
A[i].y = y;
A[i].l = L;

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 95


A[i].nproc = cpu; /* numero de CPU */
A[i].p_s = &s;
A[i].p_s_lock = &s_lock;

if(pthread_create(&thread[i], NULL, SMP_scalprod, &A[i] ))


{
fprintf(stderr, "%s: no se creo la hebra\n", argv[0]);
exit(1);
}
}
for(i=0;i<cpu;i++)
{
if(pthread_join(thread[i], &retval))
{
fprintf(stderr, "%s: no se puede unir la hebra\n", argv[0]);
exit(1);
}
}
printf("s = %f\n", s);
exit(0);
}

4.2 Sincronización de hebras con semáforos

La biblioteca Pthreads implementa semáforos POSIX 1003.1b (que confundirlos con los
semáforos System V). Como vimos en teoría, las operaciones básicas sobre semáforos son: crear
e inicializar un semáforo, destruirlo, incrementar el semáforo, y esperar hasta que no sea cero.
Todas las funciones y macros para manipularlos están definidas en semaphore.h.
Los semáforos tienen un valor máximo pasado el cual no pueden ser incrementados. La
macro SEM_VALUE_MAX está definida para que sea el valor máximo. En la biblioteca de GNU C,
SEM_VALUE_MAX es igual a INT_MAX (valor máximo para representar un signed int), pero este
puede ser menor en otros sistemas.

● Crear e inicializar un semáforo

int sem_init (sem_t *sem, int pshared, unsigned int value)

sem_init inicializa el semáforo apuntado por sem. El contador asociado con el semáforo es
inicializado a value. El argumento pshared indica si el semáforo es local al proceso actual
(pshared es igual a 0) o es compartido entre varios procesos (pshared distinto de 0). Esta
última opción no esta soportada por la biblioteca LinuxThreads. La función devuelve un 0 si tiene
éxito y un -1 si falla, en cuyo caso, se ajusta errno a EINVAL si value excede el máximo valor
permitido para counter (SEM_VALUE_MAX).

● Destruir un semáforo

int sem_destroy (sem_t * sem)

sem_destroy destruye un objeto semáforo, liberando los recursos que pueda tener. Si algunas
hebras están esperando por el semáforo cuando se invoca a la función, esta falla y devuelve
EBUSY en errno.

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 96


En la implementación LinuxThreads, no existen recursos asociados con los objetos
semáforos, así la función sem_destroy lo único que hace es comprobar que no hay ninguna
hebra esperando en el semáforo.
● Operación wait
int sem_wait (sem_t * sem)

sem_wait suspende a la hebra llamadora hasta que el semáforo apuntado por sem tenga un valor
no-cero. Esta decrementa atómicamente el contador del semáforo. sem_wait es un punto de
cancelación. Siempre devuelve cero.

● Operación signal

int sem_post (sem_t * sem)

sem_post incrementa atómicamente el contador del semáforo apuntado por sem. Esta función
nunca bloquea.
En procesadores que soportan la operación atómica compara-e-intercambia (Intel 486,
Pentium, Alpha, PowerPC, Motorota 68K, Ultrasparc, MIPS II), esta función puede llamarse de
forma segura desde un manejador de señales. Esta es la única función de sincronización de las
hebras Posix que es segura frente a señales asíncronas. En el Intel 386, y antiguos Sparc, la
implementación actual de sem_post no es segura frente a señales asíncronas, dado que el
hardware no soporta la operación atómica necesaria.
sem_post siempre tiene éxito y devuelve 0, salvo que el contador del semáforo exceda el
valor máximo SEM_VALUE_MAX después de ser incrementado. En este caso, sem_post devuelve -1
y asigna EINVAL a errno.
A continuación veremos un ejemplo del uso de semáforos para resolver un problema de
sincronización entre varias hebras.
/* Ejemplo, donde la hebra que ejecuta la función P2 suma los numeros
impares producidos por la hebra que ejecuta P1.
*/

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>

/*declaración de semáforos */
sem_t sm;
sem_t p;
/*Declaraciones de variables globales */
int s=0;
int suma=0;
p1() {
int n=0;
int i=0;
while ( i < 10 ) {
printf(“La primera tarea\n”);
sem_wait(&sm);
n = n + 1;
i = i + 1;

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 97


if ((n%2) != 0) /* si n es impar */
{s = n;/*asigna var compartida s*/
sem_post(&p);
}
else sem_post(&sm);
}
}
p2() {
int i = 0;
while ( i<6 ) {
sem_wait(&p);
printf(“La segunda tarea\n”);
i = i + 1;
suma = suma + s;
sem_post(&sm);
}
}
main() {
int error1, error2;
pthread_t tp1;
pthread_t tp2;
sem_init( &p, 0, 0);
sem_init( &sm, 0, 1);
error1 = pthread_create(&tp1,NULL,(void *)p1,NULL);
if ( error1 ) printf(“%d\n”, error1);
error2 = pthread_create(&tp2, NULL,(void *) p2,NULL);
if (error2) printf(“%d\n”, error2);
pthread_join(tp1, NULL);
pthread_join(tp2, NULL);
printf(“%s %d”, “La suma es -> “, suma);
}

5 Compilación de un programa con hebras

Para compilar y enlazar con éxito un programa que contenga múltiples hebras, necesita
incluir los archivos de cabecera: tpthread.h, semaphore.h, y errno.h. Para compilar un
programa en C que utiliza la biblioteca de hebras, ponemos:
% cc [D_REENTRANT] -lpthread nombre_archivo.c [-o archivo_salida]
De acuerdo con http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html#H[/url], el indicador
-D_REENTRANT realiza tres cosas:
1. Utiliza los prototipos de las cabeceras reentrantes equivalentes de las funciones no reentran-
tes (por ejemplo, gethostbyname_r() en lugar de gethostbyname() ).
2. Las macros stdio.h son sustituidas por funciones reentrantes (p. ej., getc() y putc() ).
3. errno se sustituye por una versión segura frente a hebras.
Por último y como resumen, he aquí una serie de normas que es conveniente observar
cuando se programa con hebras:
 Se ha de saber qué bibliotecas o módulos utilizamos en una aplicación y si éstos son reen-
trantes.
 Un programa con hebras no debe utilizar código secuencial sin hebrar, de una forma arbi-
traria.
 Un código con hebras debe utilizar código no-reentrante sólo en la hebra principal (aque-
lla que contiene a main()).

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 98


• Las bibliotecas suministradas son reentrantes, salvo que se diga lo contrario.
• La hebra inicial debe asegurar que no hay acceso concurrente a la biblioteca stdio cuando
se ejecuta código que no asegure la propiedad de reentrancia.
• No intente realizar operaciones globales (o acciones con efectos laterales globales) entre
varias hebras. Por ejemplo, las hebras no deben cooperar en el acceso a archivos.
• Cuando se espere un comportamiento del programa en el que han de cooperar varias he-
bras, utilice las funciones adecuadas. Por ejemplo, si la terminación de main() debe signifi-
car sólo la terminación de dicha hebra, entonces el final del código de main() debe ser pth-
read_exit().

6 Relación de funciones de la biblioteca

Funciones para Pthread


Gestión de Hebras pthread_create
pthread_exit
pthread_join
pthread_once
pthread_kill
pthread_self
pthread_equal
pthread_yield
pthread_detach
Datos específicos de las hebras pthread_key_create
pthread_key_delete
pthread_getspecific
pthread_setspecific
Cancelación de hebras pthread_cancel
pthread_cleanup_pop
pthread_cleanup_push
pthread_setcancelstate
pthread_getcancelstate
pthread_testcancel
Planificación de hebras pthread_getschedparam
pthread_setschedparam
Señales pthread_sigmask

Funciones sobre atributos de Pthread


Gestión básica pthread_attr_init
pthread_attr_destroy
Ligadas o desligadas pthread_attr_setdetachstate
pthread_attr_getdetachstate
Información específica sobre la pila pthread_attr_getstackaddr
pthread_attr_getstacksize
pthread_attr_setstackaddr
pthread_attr_setstacksize
Atributos de planificación de hebras pthread_attr_getschedparam
pthread_attr_setschedparam
pthread_attr_getschedpolicy
pthread_attr_setschedpolicy
pthread_attr_setinheritsched
pthread_attr_getinheritsched
pthread_attr_setscope

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 99


pthread_attr_getscope
Funciones sobre Mutex
Gestión de Mutex pthread_mutex_init
pthread_mutex_destroy
pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_trylock
Gestión de Prioridad pthread_mutex_setprioceiling
pthread_mutex_getprioceiling
Funciones de atributos de Mutex
Gestión básica pthread_mutexattr_init
pthread_mutexattr_destroy
Sharing pthread_mutexattr_getpshared
pthread_mutexattr_setpshared
Atributos de protocolo pthread_mutexattr_getprotocol
pthread_mutexattr_setprotocol
Gestión de prioridad pthread_mutexattr_setprioceiling
pthread_mutexattr_getprioceiling
Funciones sobre Variables de Condición
Gestión básica pthread_cond_init
pthread_cond_destroy
pthread_cond_signal
pthread_cond_broadcast
pthread_cond_wait
pthread_cond_timedwait
Funciones de atributos de Variables de Condición
Gestión básica pthread_condattr_init
pthread_condattr_destroy
Compartición pthread_condattr_getpshared
pthread_condattr_setpshared
Funciones para semáforos
Gestión básica sem_init
sem_destroy
Operaciones sem_wait
sem_post

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 100


Ejercicios

Ejercicio 1:
Implemente el problema del Productor/Consumidor con búfer finito utilizando semáforos:
- Productor: produce un dato y lo pone en la siguiente posición libre en el búfer. Si el
búfer está lleno, se bloqueará hasta que haya espacio libre.
- Consumidor: toma un dato del búfer y lo imprime. Si el búfer está vacío se bloqueará
hasta que haya al menos un dato.
Programe la solución siguiendo el esquema:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>

#define veces 30
#define tamano 10

/* inserta aqui las declaraciones de semaforos y variables o estructu-


ras de datos compartidas, necesarias para resolver el problema */

productor() {
int i;
/* otras declaraciones locales */

for (i=0; i<veces; i++){


sleep(1); /* retardo */

/* codigo del productor */


}
}

consumidor () {
int i;
/* otras declaraciones locales */

for (i=0; i<veces; i++){

/* codigo del proceso consumidor */

sleep(i%5); /* retardo */
}
}

main () {
/* declaracion de las hebras productor y consumidor */
/* inicializacion de los semaforos */

setbuf(stdout,NULL); /*evita las E/S con buffering*/

/* creacion de hebras y deteccion de errores */


printf("FIN\n");
}

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 101


Ejercicio 2:
Implementar con hebras y semáforos el siguiente problema: En una carretera donde solo exis-
ten dos carriles, uno para cada sentido de la circulación, existe un tramo donde se encuentra
un puente sobre el cual solo pueden pasar coches que vayan en un sentido. Para modelar este
problema, tendremos dos clases de procesos:
 Coches que van hacia la izquierda.
 Coches que van hacia la derecha.
Suponemos que una vez que el coche pasa por el puente (en uno u otro sentido) ya no vuelve a
pasar (llegará a su destino). Cuando un coche llega al principio del puente, pueden ocurrir tres
cosas:
 Que no pueda pasar porque en el puente ya hay coches que vienen en sentido contrario y
por tanto tendrá que esperar a que el puente esté libre.
 Que el puente esté libre y entonces puede pasar sin ningún problema.
 Que el puente no esté libre pero los coches que circulan por él van en el mismo sentido.
En este caso el coche podrá continuar y proseguir su camino cruzando el puente.
Además, para evitar el riesgo de inanición que pueden sufrir los coches de ambos sentidos de-
bido a que puede ocurrir que siempre haya coches que vienen en sentido contrario por el puen-
te, se tendrá en cuenta también el siguiente requisito:
- Si hay esperando coches en el otro extremo del puente y ya hay coches accediendo, cuando
hayan pasado cinco coches (en caso de que haya) entonces pasarán el turno a los coches del
sentido contrario que están esperando.

Sugerencia: programe la solución con el siguiente esquema para cada proceso.

proceso coche_izq o coche_drcha


Parte inicial /* codigo inicial del proceso (puede ser un sleep
variable según la hebra)*/
PreProtocolo /* codigo a ejecutar antes del acceso al puente */
SeccionCritica /* pasando a través del puente */
PostProtocolo /* codigo a ejecutar despues de salir del puente */

Guía didáctica de Sistemas Operativos I – José Antonio Gómez Hernández 102