PRÁCTICAS DE SISTEMAS OPERATIVOS

EDUARDO DOMÍNGUEZ PARRA
CARLOS VILLARRUBIA JIMÉNEZ
DEPTO. DE TECNOLOGÍAS Y SISTEMAS DE INFORMACIÓN
E. S. de INFORMÁTICA
UNIV. de CASTILLA - LA MANCHA
CIUDAD REAL
Prefacio
Presentamos aquí un breve manual con material básico para el desa-
rrollo del programa de prácticas de un primer curso de Sistemas Operati-
vos en la titulación de Grado en Ingeniería Informática. No ha sido nuestra
intención desarrollar un manual completo de uso del shell bash, del com-
pilador C de GNU, del depurador gdb de GNU o de la utilidad make de GNU;
para ello remitimos al lector a la abundante y, en muchos casos, excelente
bibliografía que existe sobre estos temas, sino ofrecer, en el menor número
de páginas posible, un material didáctico que permita, en un curso de las
características mencionadas, explorar productivamente las capacidades de
un sistema típico trabajando sobre un terminal alfanumérico.
Los autores desean agradecer su colaboración a todos los compañeros
que han participado con ellos en la docencia de la asignatura de Sistemas
Operativos en la Escuela Superior de Informática de la Universidad de Cas-
tilla - La Mancha, y especialmente a los profesores asociados que, como S.
García Talegón, han desarrollado tareas correspondientes a dicha docencia
durante los últimos cursos académicos.
Del mismo modo, los autores queremos agradecer a nuestros alumnos
el interés que estas cuestiones suscitan en ellos, ya que ello es lo que nos ha
animado a producir un material que esperamos que contribuya a facilitar
en lo posible su trabajo.
Ciudad Real, Septiembre de 2011.
Los autores.
I
Índice general
Prefacio I
I El shell bash 1
1 Introducción 3
2 Primeros pasos con el shell bash 5
2.1 Comienzo y terminación de sesión (login, exit, shutdown) 5
2.2 Escritura de órdenes en UNIX . . . . . . . . . . . . . . . . . 9
2.2.1 Formato de las órdenes . . . . . . . . . . . . . . . . . 9
2.2.2 Efecto de las opciones . . . . . . . . . . . . . . . . . . 12
2.2.3 Problemas de paginación (more, less) . . . . . . . . . 12
2.3 Información de ayuda sobre las órdenes UNIX (man) . . . . 14
2.4 Sugerencias (cal, date, who, whoami, users, clear, passwd) 17
3 El sistema de archivos 19
3.1 Características del sistema de archivos en UNIX (pwd, cd) . 19
3.2 El shell y los metacaracteres . . . . . . . . . . . . . . . . . . 21
3.3 El editor de texto joe . . . . . . . . . . . . . . . . . . . . . . . 21
3.4 Operaciones básicas en el sistema de archivos . . . . . . . 24
3.4.1 Creación y eliminación de archivos y directorios (rm,
mkdir, rmdir) . . . . . . . . . . . . . . . . . . . . . . . 24
3.4.2 Exploración del contenido de un directorio (ls) . . . . 25
3.4.3 Copia, movimiento y cambios de nombre (cp, mv) . . 27
3.4.4 Exploración del contenido de un archivo (cat, head,
tail) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.4.5 Cambios en el contenido de un archivo (tee) . . . . . 30
3.4.6 Estadísticas sobre el contenido de un archivo (wc) . 30
3.4.7 Actualización de los atributos de fecha y hora de un
archivo (touch) . . . . . . . . . . . . . . . . . . . . . . 31
III
Índice general
3.4.8 Enlaces (ln) . . . . . . . . . . . . . . . . . . . . . . . . 31
3.5 Permisos en el sistema de archivos . . . . . . . . . . . . . . 33
3.5.1 Cambios de propietario y grupo (chown, chgrp) . . . 33
3.5.2 Cambios de permisos (chmod) . . . . . . . . . . . . . 34
4 Operaciones especiales asociadas a las órdenes del shell 39
4.1 Variables de shell (set, export, echo) . . . . . . . . . . . . . 39
4.1.1 El entorno de una orden (env) . . . . . . . . . . . . . 40
4.1.2 Path de los ejecutables del shell (which) . . . . . . . 40
4.2 Redirección de la entrada, la salida y la salida de error
estándares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.3 Agrupación y encadenamiento de órdenes . . . . . . . . . . 42
4.3.1 Ejecución en segundo plano . . . . . . . . . . . . . . . 42
4.4 Creación de shells hijos (sh) . . . . . . . . . . . . . . . . . . 44
5 Otras órdenes útiles 45
5.1 Búsqueda de información en el sistema de archivos . . . . 45
5.1.1 Búsqueda de archivos por sus atributos (find, locate) 45
5.1.2 Búsqueda de archivos por su contenido (grep) . . . . 50
5.2 Manipulación de archivos . . . . . . . . . . . . . . . . . . . . 51
5.2.1 Compresión de archivos (gzip) . . . . . . . . . . . . . 51
5.2.2 Serialización de archivos (tar) . . . . . . . . . . . . . . 52
5.2.3 Ordenación de líneas en archivos (sort) . . . . . . . . 52
5.2.4 Sumas de comprobación en archivos (md5sum) . . . 54
5.2.5 Visualización del contenido de un subdirectorio (tree) 54
5.3 Miscelánea . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.3.1 Información sobre el sistema de archivos (df, du) . . 55
5.3.2 Determinación del tiempo consumido por una or-
den (time) . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.3.3 Determinación de la memoria disponible (free) . . . . 56
5.3.4 Determinación de la actividad de los usuarios (w) . . 56
5.3.5 Cambio de identidad en la sesión (su) . . . . . . . . . 57
5.3.6 Reinicialización del terminal (reset) . . . . . . . . . . 57
5.3.7 Montaje de sistemas de archivos (mount) . . . . . . . 57
5.3.8 Desmontaje de sistemas de archivos (umount) . . . . 58
6 Control de procesos en UNIX 59
6.1 Información sobre procesos (ps) . . . . . . . . . . . . . . . . 59
6.2 Control de trabajos (jobs, fg, bg, kill) . . . . . . . . . . . . . 59
6.3 Otras operaciones con procesos . . . . . . . . . . . . . . . . 61
6.3.1 Espera durante un tiempo determinado (sleep) . . . 61
IV
Índice general
6.3.2 No eliminar un proceso al cerrar la sesión (nohup) . 61
6.3.3 Envío de señales a un proceso (kill) . . . . . . . . . . 62
II Uso del compilador C de GNU 63
7 Introducción 65
7.1 Obtención de un ejecutable a partir de los archivos fuente 65
7.1.1 Preproceso . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.1.2 Compilación . . . . . . . . . . . . . . . . . . . . . . . . 67
7.1.3 Ensamblaje . . . . . . . . . . . . . . . . . . . . . . . . 67
7.1.4 Enlace . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
8 El compilador C de GNU 71
8.1 Uso de gcc mediante el shell UNIX . . . . . . . . . . . . . . 71
8.2 Opciones de uso frecuente . . . . . . . . . . . . . . . . . . . 72
8.2.1 Opciones globales (-c, -S, -E, -o <outfile>) . . . . . . 72
8.2.2 Opciones que controlan los mensajes de aviso (-W
<warn>) . . . . . . . . . . . . . . . . . . . . . . . . . . 73
8.2.3 Opciones que controlan el estándar utilizado
(-std = <standard>) . . . . . . . . . . . . . . . . . . . . 73
8.2.4 Opciones que controlan la incorporación de código
para depuración (-g, -ggdb) . . . . . . . . . . . . . . . 73
8.2.5 Opciones que describen los directorios de residen-
cia de las cabeceras (-I<dir>) . . . . . . . . . . . . . . 74
8.2.6 Opciones que describen los directorios de residen-
cia de las bibliotecas (-L<dir>) . . . . . . . . . . . . . 74
8.2.7 Opciones que describen las bibliotecas que deben
usarse en el proceso de enlace (-l<library>) . . . . . . 74
III El depurador gdb de GNU 77
9 Introducción 79
9.1 Un programa ejemplo . . . . . . . . . . . . . . . . . . . . . . 79
9.2 Depuración de un ejecutable . . . . . . . . . . . . . . . . . . 84
10 El depurador gdb de GNU 87
10.1 Una sesión de depuración con gdb . . . . . . . . . . . . . . 88
10.1.1 Argumentos de la línea de órdenes . . . . . . . . . . . 88
10.1.2 Puntos de ruptura (breakpoints) . . . . . . . . . . . . 90
10.1.3 Ejecución del programa . . . . . . . . . . . . . . . . . 91
V
Índice general
10.1.4 Otras órdenes útiles del depurador gdb . . . . . . . . 106
10.2 Sugerencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
IV La utilidad make de GNU 109
11 Introducción 111
12 La utilidad make de GNU 113
12.1 Funcionamiento básico de make . . . . . . . . . . . . . . . . 114
12.2 Sintaxis de los archivos makefile . . . . . . . . . . . . . . . . 114
12.3 Sintaxis de las reglas de un archivo makefile . . . . . . . . 116
12.3.1 Disparo de una regla . . . . . . . . . . . . . . . . . . . 117
12.4 Ejemplo de ejecución de la orden make . . . . . . . . . . . . 117
12.5 Reglas implícitas . . . . . . . . . . . . . . . . . . . . . . . . . 121
Bibliografía 123
VI
Índice de figuras
2.1 Terminal sin sesión iniciada . . . . . . . . . . . . . . . . . . 6
2.2 Inicio de sesión . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Sesión iniciada . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.4 Ejemplo de salida de una orden . . . . . . . . . . . . . . . . 11
2.5 Ejemplo de salida de una orden errónea . . . . . . . . . . . 11
2.6 Ejemplo de salida de la orden cal sin opciones . . . . . . . 12
2.7 Calendario del año 2007 no paginado . . . . . . . . . . . . 13
2.8 Calendario del año 2007. Página 1 . . . . . . . . . . . . . . 14
2.9 Calendario del año 2007. Página 2 . . . . . . . . . . . . . . 15
2.10 Página 1 de manual para la orden cal . . . . . . . . . . . . 16
3.1 Pantalla inicial de joe para un archivo sin nombre y vacío 22
3.2 Pantalla inicial de joe con el panel de ayuda visible . . . . 23
3.3 Pantalla con la información de ls en formato largo . . . . . 26
4.1 Ejemplo de ejecución en segundo plano . . . . . . . . . . . 43
7.1 Operación de preproceso . . . . . . . . . . . . . . . . . . . . 66
7.2 Operación de compilación . . . . . . . . . . . . . . . . . . . 67
7.3 Operación de ensamblaje . . . . . . . . . . . . . . . . . . . 68
7.4 Operación de enlace . . . . . . . . . . . . . . . . . . . . . . 69
9.1 Listado de errores de compilación . . . . . . . . . . . . . . 81
9.2 Listado de errores de enlace . . . . . . . . . . . . . . . . . . 84
9.3 Salida de la versión 1 . . . . . . . . . . . . . . . . . . . . . . 85
10.1 Pantalla de presentación del depurador gdb . . . . . . . . 89
10.2 Paso de los argumentos de la línea de órdenes . . . . . . . 90
10.3 Punto de ruptura . . . . . . . . . . . . . . . . . . . . . . . . 91
10.4 Ejecución hasta el punto de ruptura . . . . . . . . . . . . . 92
10.5 Ejecución de la sentencia 10 . . . . . . . . . . . . . . . . . 93
10.6 Ejecución de la sentencia 11 . . . . . . . . . . . . . . . . . 94
10.7 Ejecución de la sentencia 14 . . . . . . . . . . . . . . . . . 94
VII
Índice de figuras
10.8 Orden de salida del depurador . . . . . . . . . . . . . . . . 97
10.9 Salida de la versión 2 . . . . . . . . . . . . . . . . . . . . . . 98
10.10 Ejecución del depurador con la versión 2 del programa . . 98
10.11 Impresión del valor de una expresión . . . . . . . . . . . . 99
10.12 Salida de la versión 3 . . . . . . . . . . . . . . . . . . . . . . 101
10.13 Ejecución del depurador con la versión 3 del programa . . 102
10.14 Primera iteración del bucle de impresión de argumentos . 102
10.15 Traza de una función mediante la orden s . . . . . . . . . 103
10.16 Primera ejecución de la función PrintInforme . . . . . . 104
10.17 Salida de la versión final . . . . . . . . . . . . . . . . . . . . 106
12.1 Salida de la orden make para el makefile del Listado 12.1 . 120
VIII
Índice de listados
9.1 Programa ejemplo. Versión 0a . . . . . . . . . . . . . . . . . . 80
9.2 Programa ejemplo. Versión 0b . . . . . . . . . . . . . . . . . . 82
9.3 Programa ejemplo. Versión 1 . . . . . . . . . . . . . . . . . . . 83
10.1Programa ejemplo. Versión 2 . . . . . . . . . . . . . . . . . . . 96
10.2Programa ejemplo. Versión 3 . . . . . . . . . . . . . . . . . . . 100
10.3Programa ejemplo. Versión final . . . . . . . . . . . . . . . . . 105
12.1Ejemplo de makefile . . . . . . . . . . . . . . . . . . . . . . . . 115
IX
Índice de algoritmos
12.1Disparo de Regla(< objetivo >) . . . . . . . . . . . . . . . . . . 117
XI
Parte I.
El shell bash
1
1. Introducción
El uso habitual de interfaces gráficos para trabajar con ordenadores
personales, así como el de terminales con capacidad gráfica para operar en
computadoras de todos los tamaños, no ha eliminado, en el ámbito profesio-
nal, la necesidad de usar los terminales alfanuméricos para, con el soporte
de un shell adecuado, desarrollar tareas de procesamiento, particularmente
en computadoras remotas.
Sin embargo, esa utilización habitual de interfaces gráficos de usuario,
hace conveniente incluir en el desarrollo práctico de la enseñanza de grado
algunas sesiones dedicadas a la descripción de un shell para el manejo de
un sistema operativo a través de un terminal alfanumérico, con objeto de
que el alumno no tenga que depender estrictamente de la disponibilidad de
un interfaz gráfico para poder desarrollar su trabajo.
Las breves páginas que siguen están dedicadas al desarrollo de una
somera descripción de las órdenes más habitualmente utilizadas en el shell
bash de UNIX (LINUX), y ello sin ocuparse más que de las opciones de mayor
interés.
No trataremos aquí de describir exhaustivamente las capacidades del
shell bash, sino de proporcionar a los alumnos de un primer curso de Sis-
temas Operativos en las enseñanzas de Grado en Ingeniería Informática,
una guía elemental que les permita iniciarse en el conocimiento y el uso de
dicho shell, con objeto de facilitar una tarea, que, efectuada directamen-
te sobre la abundante bibliografía existente, desanima en muchos casos al
lector por el exceso de información que tiene que manejar.
De acuerdo con ese objetivo, no se abordará ninguna de las capaci-
dades de programación del shell, dado que, en una primera etapa, no son
imprescindibles.
Para obtener un buen rendimiento en el estudio de cualquier shell,
es conveniente utilizar una instalación donde pueda usarse un terminal
del tipo adecuado. Cualquier distribución de LINUX puede ser utilizada, e
incluso, si no es UNIX el sistema operativo que habitualmente se utiliza,
un software de máquina virtual adecuado puede permitir al lector instalar
3
1. Introducción
una máquina virtual con LINUX que funcione sobre su propio sistema, y
sin cambios especiales en éste, usar este manual para entrenarse en el uso
de un terminal alfanumérico sobre UNIX.
La organización del manual en esta primera parte es la siguiente: En
el Capítulo 2 se revisan los primeros pasos necesarios para el uso del shell
bash. En el Capítulo 3 se describen las órdenes más importantes para el
manejo del sistema de archivos y el sistema de protección y se describe
el uso básico de un editor de texto. En el Capítulo 4 se revisan algunas
de las capacidades del shell para el manejo de órdenes. En el Capítulo 5
se describen algunas órdenes de utilidad suplementarias. Por último, en el
Capítulo 6 se trata el control de procesos mediante el shell bash.
4
2. Primeros pasos con el shell bash
2.1. Comienzo y terminación de sesión (login, exit,
shutdown)
El modo básico en el cual un usuario da órdenes a un sistema UNIX
consiste en escribir esas órdenes en el teclado de un terminal (un dispositi-
vo de entrada/salida (E/S) que consta de un teclado para escribir la orden
y un dispositivo de salida para visualizar los resultados, típicamente una
pantalla).
En LINUX (una implementación del sistema operativo UNIX) hay dis-
ponibles (usando el teclado y la pantalla del ordenador) seis terminales
virtuales. Al arrancar el sistema, el terminal activo es el 1 (tty1) pero en
cualquier momento se puede usar otro cualquiera pulsando la combinación
de teclas “<Alt>-Fn”, donde “Fn” es una de las teclas de función “F1”-“F6”.
En este manual mostraremos mediante imágenes la salida de pantalla
del terminal con el que estemos trabajando en un momento dado.
Por ejemplo en la Figura 2.1 vemos la pantalla de un terminal (tty2)
que tendríamos si, después de arrancar el sistema pulsamos “<Alt>-F2”.
Cuando queramos indicar qué escribimos nosotros en el teclado usa-
remos frecuentemente la notación
_

¸

Inserción de órdenes en el teclado del terminal . . .
Antes de poder dar alguna orden al sistema es necesario abrir una
sesión en un terminal. Sólo después será posible dar órdenes a través de
ese terminal.
Para abrir una sesión es necesario responder a las peticiones que nos
hará el proceso que, al principio, se está ejecutando en el terminal (proce-
so login). Ese proceso requiere que nos identifiquemos mediante nuestro
5
2. Primeros pasos con el shell bash
Figura 2.1.: Terminal sin sesión iniciada
identificador de usuario (login del usuario). Para nuestros ejemplos, ese lo-
gin será habitualmente alumno.
Cuando tengamos que escribir cualquier información en el terminal,
podremos editarla como es habitual, moviéndonos mediante las teclas del
cursor y borrando o insertando los caracteres que necesitemos.
Una vez que la entrada esté completa, pulsaremos la tecla de retorno
de carro (“<RET>”). Sólo en ese momento obtendremos la correspondiente
salida, que se irá escribiendo debajo de nuestra entrada. Cuando se llega
a la línea final de la pantalla, ésta realiza el scroll conveniente para que
podamos ver la línea final de la salida.
Así pues, si nosotros escribimos en la posición del cursor en la Figu-
ra 2.1
_

¸

alumno
o el correspondiente identificador que nos hayan asignado, obtendremos en
la pantalla la respuesta del proceso login de la Figura 2.2, que consiste en
6
2.1. Comienzo y terminación de sesión (login, exit, shutdown)
Figura 2.2.: Inicio de sesión
una nueva petición para que nosotros escribamos una palabra clave (pass-
word) que permita al proceso login confirmar que somos quien decimos ser
(autentificación). En nuestro caso escribiremos de nuevo
_

¸

alumno
o la password que corresponda a nuestro login. El proceso login no escribe
la password en pantalla, ni caracteres de máscara que permitan siquiera
contar los caracteres de la password. Si la validación resulta correcta ob-
tendremos el resultado que se observa en la Figura 2.3.
Ahora, el proceso login ha creado un proceso shell, (abreviadamente
un shell) que será el que se ocupe de que se ejecuten nuestras órdenes.
No obstante, el shell es un intérprete que, generalmente, no ejecuta por sí
mismo nuestras órdenes, sino que, después de analizarlas, pone en marcha
programas específicos para ejecutar las diferentes órdenes.
A la secuencia de operaciones realizada hasta aquí se denomina en
UNIX abrir una sesión.
Cuando hayamos terminado de trabajar con el terminal, debemos dar
por terminada la sesión, escribiendo
7
2. Primeros pasos con el shell bash
Figura 2.3.: Sesión iniciada
_

¸

exi t
lo cual nos llevará a la situación de la Figura 2.1. Nuestro shell ha termi-
nado y se ha puesto nuevamente el terminal bajo el control de un proceso
login para permitir iniciar una nueva sesión al mismo usuario o a otro.
No es necesario cerrar una sesión que tengamos abierta en un ter-
minal para poder abrir otra en otro terminal que tengamos inactivo. Por
ejemplo, aquí, antes de cerrar la sesión en tty2 podríamos haber usado el
terminal tty1 (pulsando “<Alt>-F1”) y abrir en él una nueva sesión (del mis-
mo modo que lo hemos hecho en el ejemplo previo). Si luego hubiéramos
vuelto a pulsar “<Alt>-F2” habríamos obtenido de nuevo la pantalla del ter-
minal tty2 tal como la tuviéramos cuando cambiamos de terminal. Esto nos
permite trabajar hasta con seis sesiones abiertas simultáneamente.
El proceso de cerrar una sesión del shell no termina la ejecución del
sistema operativo. Si queremos dar de baja el sistema, por ejemplo por-
que tenemos que apagar la máquina, teclearemos desde una sesión abierta
como administrador del sistema (root)
8
2.2. Escritura de órdenes en UNIX
_

¸

shutdown −h 0
lo cual cerrará todas las sesiones abiertas y dará de baja ordenadamente el
sistema.
Resumiendo lo que hemos visto hasta aquí, el administrador del sis-
tema, al darnos de alta como usuarios (o, como se dice habitualmente, al
abrirnos una cuenta), debe asignarnos un login y una password que nos
servirán para poder abrir sesiones en ese sistema UNIX.
Además en UNIX hay diferentes programas de tipo shell, de modo que
el administrador también debe asignarnos, entre otras cosas que ya iremos
precisando a lo largo de los capítulos siguientes, el programa shell que
se usará por defecto para ejecutar nuestras órdenes cuando abramos una
sesión.
En estas lecciones se supondrá siempre que el programa shell usado
será el shell bash. La información de referencia para este shell puede ser
consultada en [2], y es recomendable disponer de la información de [4] como
ayuda habitual.
Otros tipos de shell ejecutan las órdenes esencialmente de la misma
manera que el shell bash, pero difieren de éste en su forma de programa-
ción.
2.2. Escritura de órdenes en UNIX
2.2.1. Formato de las órdenes
Las órdenes que podemos escribir en el terminal, una vez que ten-
gamos abierta una sesión, pueden ser de muchas clases, pero su formato
básico es muy parecido en los distintos casos.
Una orden comienza por una palabra que identifica la clase de opera-
ción que debe hacerse y que es denominada nombre de la orden. Es común
en la bibliografía denominar comando tanto a la orden completa como a su
nombre.
Después, y separadas por uno o más espacios del nombre de la orden,
continúan opcionalmente uno o varios grupos de letras y/o cifras sepa-
rados entre sí por espacios y precedidos habitualmente cada uno por un
9
2. Primeros pasos con el shell bash
signo menos. Estos caracteres especifican opciones que deben ser tenidas
en cuenta por el shell para la ejecución de la orden.
Por último y también opcionalmente y separados entre sí y de las op-
ciones por uno o más espacios, se encuentran series de caracteres que
forman los objetivos de la orden. Habitualmente son nombres de archivos
y/o directorios sobre los que la orden debe operar.
En general, el aspecto de la orden sería
<nombre>[<opción-1>]...[<opción-m>]
[<objetivo-1>]...[<objetivo-n>]
Un ejemplo de una orden típica sería
_

¸

cal −m 6 2007
El nombre de la orden es aquí cal, las opciones se reducen a “-m”,
mientras que los objetivos de la orden son “6” “2007”.
Al escribir las órdenes debemos tener en cuenta que las letras mayús-
culas y las minúsculas no son equivalentes.
Por otra parte, debemos recordar que una orden sólo se ejecuta cuan-
do pulsamos la tecla “<RET>”, y que mientras no lo hagamos, podemos
corregir los errores que hayamos cometido al escribir. Una vez que pulsa-
mos “<RET>”, el sistema intentará ejecutar la orden tal como esté escrita, y
nos proporcionará la salida correspondiente.
En la Figura 2.4 podemos ver la salida de la orden anterior, en la que
el sistema nos proporciona un calendario de Junio de 2007.
En cambio si escribimos
_

¸

CAl −m 6 2007
obtendremos la salida de la Figura 2.5 en la que observamos el informe de
error que nos indica que la orden CAl no existe. El nombre correcto de la
orden es cal.
En ambos casos, el shell queda disponible para que escribamos una
nueva orden.
10
2.2. Escritura de órdenes en UNIX
Figura 2.4.: Ejemplo de salida de una orden
Figura 2.5.: Ejemplo de salida de una orden errónea
11
2. Primeros pasos con el shell bash
Figura 2.6.: Ejemplo de salida de la orden cal sin opciones
2.2.2. Efecto de las opciones
Veamos ahora cómo funcionan las opciones. La opción -m indica que
queremos que el calendario esté ordenado de forma que en la primera co-
lumna del mes aparezcan las fechas de los lunes. Si no usamos esa opción,
UNIX ordena el calendario de forma que en la primera columna del mes
aparezcan las fechas de los domingos, como es habitual en los países an-
glosajones.
Si escribimos
_

¸

cal 6 2007
obtendremos la salida indicada en la Figura 2.6
2.2.3. Problemas de paginación (more, less)
Hasta ahora las salidas de las órdenes que hemos obtenido tenían
pocas líneas. Cuando la salida tiene más líneas de las que caben en una
12
2.2. Escritura de órdenes en UNIX
Figura 2.7.: Calendario del año 2007 no paginado
pantalla se plantea el problema de poder leer las líneas a las que el scrolling
ha hecho salir de la pantalla. Como ejemplo podemos ver la salida de la
orden
_

¸

cal −m 2007
tendríamos la salida que se observa en la Figura 2.7. En ella se observa que
las filas de texto correspondientes a los primeros meses del año 2007 no
pueden leerse porque han “salido por encima de la pantalla”. De una salida
que presente ese problema se dice que está no paginada, es decir, que es
necesario someterla a un proceso para partirla en páginas con objeto de
poder verla entera.
La forma de resolver este problema es enviar la salida de la orden cal a
otra orden que sea capaz de procesar una entrada sin paginar para producir
una salida paginada. Una orden de ese tipo recibe el nombre de paginador.
En UNIX existen diferentes paginadores. Dos de los más comunes son more
y less.
La forma de enviar la salida de una orden (en este caso cal) para ser
usada como entrada de otra (en este caso more) es usar el operador “|”
13
2. Primeros pasos con el shell bash
Figura 2.8.: Calendario del año 2007. Página 1
(llamado operador pipe).
Si escribimos
_

¸

cal −m 2007 | more
obtendremos como salida la de la Figura 2.8.
Ahora debemos ver la página siguiente pulsando la barra espaciadora.
La pantalla tomará el aspecto de la Figura 2.9.
Podemos observar que la salida de la orden cal se ha completado por-
que el shell está esperando (ha escrito ya el indicador “alumno@ssoo:˜$”)
2.3. Información de ayuda sobre las órdenes UNIX
(man)
Las preguntas que nos hacemos ahora son
¿Cuántas y cuáles son las órdenes que podemos manejar con el shell?
14
2.3. Información de ayuda sobre las órdenes UNIX (man)
Figura 2.9.: Calendario del año 2007. Página 2
Para una determinada orden ¿cuántas y cuáles son las opciones dis-
ponibles, y cuáles son el cometido de la orden y la salida que produce?
En el primer caso no hay una respuesta definida porque eso depende
del tipo de UNIX, y, dentro de un tipo determinado, de la configuración de
la instalación en la que trabajemos. Además, cualquier shell UNIX permi-
te que el programador construya órdenes que el shell ejecutará sin hacer
ninguna diferencia con las que se entregan con el propio sistema. En todo
caso se puede responder que las órdenes básicas son aproximadamente un
centenar. En cuanto a cuáles son, en estas páginas estudiaremos las más
importantes.
La respuesta a la segunda cuestión es más sencilla. Existe en UNIX
una orden que permite al usuario obtener información de manual sobre
cualquier orden, incluida ella misma. Esa orden es man.
Cuando el usuario desea obtener información de manual sobre una
determinada orden, debe ejecutar una orden man con un objetivo que será
el nombre de la orden sobre la que se desea obtener información.
Por ejemplo, si deseamos saber para qué sirve, cual es el formato o
cuáles son las opciones de la orden cal podemos escribir
15
2. Primeros pasos con el shell bash
Figura 2.10.: Página 1 de manual para la orden cal
_

¸

man cal
y obtendremos una salida (paginada) que puede recorrerse hacia adelante
o hacia atrás mediante las teclas de movimiento habituales y cuya primera
página está representada en la Figura 2.10.
Como la orden man produce una salida paginada y, al igual que otros
paginadores, permite avanzar y retroceder por ella, el shell debe ser avisado
por el usuario de que ya ha terminado de trabajar con esa salida. Para ello,
el usuario debe pulsar la tecla “q”. Como respuesta, el shell terminará la
ejecución de la orden man y escribirá de nuevo el indicador del shell para
informar al usuario de que está esperando una nueva orden.
Naturalmente se puede obtener información de manual sobre la orden
man escribiendo
_

¸

man man
El formato de la orden man es
16
2.4. Sugerencias (cal, date, who, whoami, users, clear, passwd)
_

¸

man [ opción ] . . . [−S <l i st a −de−sec >] [ <sec >] <nombre>. . .
Se obtiene información de manual sobre los nombres que figuren en
la orden, relativa a las secciones que se especifiquen en la lista de secciones
<lista-de-sec> o en el argumento <sec>.
<sec> es un número de sección del manual. <lista-de-sec> es una serie
de numeros de sección separados por el carácter “:”. Por defecto, si no se
especifica número de sección, la información corresponderá a la sección 1
(aunque eso puede depender de la configuración del sistema).
Los contenidos de las secciones más importantes del manual son
1 Órdenes normales del shell
2 Llamadas al sistema operativo
3 Funciones de la biblioteca estándar C
8 Órdenes del shell específicas para la administración del sistema
La opción más importante es
-a Por defecto, man terminará después de mostrar la página de manual
correspondiente al nombre buscado, en la primera sección en la que
se encuentre. La opción -a permite obtener la información del resto de
las páginas de manual que correspondan a ese nombre en todas las
secciones, y no sólo de la primera que se encuentre
2.4. Sugerencias (cal, date, who, whoami, users, clear,
passwd)
El lector debe ahora obtener información de manual y practicar el uso
de las siguientes órdenes del shell simples (las opciones más importantes
figuran entre paréntesis)
cal (3, -m, -y)
date
who (-m)
whoami
users
17
2. Primeros pasos con el shell bash
clear
passwd
Como manual simple de ayuda inicial sobre UNIX, puede consultarse
[1].
18
3. El sistema de archivos
3.1. Características del sistema de archivos en UNIX
(pwd, cd)
El sistema de archivos nativo de UNIX es un sistema jerárquico, en el
que tanto los archivos como los demás nodos presentes en el sistema, se
encuentran organizados en directorios. La jerarquía es única, no hay una
por cada unidad física.
Un directorio no es más que un tipo de archivo especial, que contiene
un asociación entre los identificadores (nombres) de los nodos (típicamente
archivos y otros directorios, pero no sólo ellos) descritos en él y los atributos
de éstos (propietario, tamaño, permisos de uso, posición en el almacena-
miento, . . . ).
Una referencia a un archivo (o a cualquier otro tipo de nodo) se realiza
dando la serie de nombres de directorio que describen un camino (path) que
lleve hasta el nodo en la jerarquía total, seguida del nombre del archivo (o
del nodo de que se trate).
Los nombres de archivos y directorios en UNIX se pueden formar prác-
ticamente con cualquier serie de caracteres. No obstante, conviene tener en
cuenta que la longitud total del path y el nombre de un nodo está limitada
(típicamente a 256 caracteres). Por otra parte es inconveniente usar carac-
teres especiales que no sean el signo menos, el de subrayado o el punto
intercalados en el los nombres, dado que algunos de los otros caracteres
especiales pueden ser interpretados de manera especial por el shell. Suele
usarse como primer carácter una letra, pero ello no es obligatorio. En UNIX
los nombres no tienen ningún significado especial para el sistema, aparte
de servir de identificadores, y, como siempre, las letras mayúsculas y las
minúsculas no son equivalentes.
El path de búsqueda comienza en un directorio especial, denominado
directorio raíz, cuyo nombre siempre es el carácter “/”. Éste es el único nodo
que no se encuentra referenciado en ningún directorio. Ese mismo carácter
19
3. El sistema de archivos
“/” se usa como separador entre los nombres de directorio que forman el
path y entre éstos y el nombre del nodo.
En cada momento una aplicación tiene asociado un directorio deno-
minado directorio de trabajo actual. Si una aplicación referencia un nodo
dando un path que no comience por el carácter “/” (path relativo), el path
completo (absoluto) del nodo se forma siempre anteponiendo al path relati-
vo el path del directorio de trabajo actual.
El directorio de trabajo actual al abrir una sesión del shell es un direc-
torio asignado al usuario por el administrador del sistema, que se denomina
directorio home del usuario. Para el usuario alumno que nos ha servido de
ejemplo en la Sección 2.1 es “/home/alumno”.
Si tenemos que usar el nombre de nuestro directorio home como parte
inicial de un path, podemos usar como alias el carácter “˜”.
En cada directorio hay dos entradas cuyos nombres son “.” y “..”, que
se refieren respectivamente al propio directorio y al directorio antecesor en
la jerarquía (directorio padre).
La orden del shell para saber cuál es el directorio de trabajo actual en
un momento dado es
_

¸

pwd
Para cambiar el directorio de trabajo actual se usa
_

¸

cd <nombre−nuevo−di rectori o−actual >
Por ejemplo, si nuestro directorio de trabajo actual es nuestro direc-
torio home,
“/home/alumno”, la orden
_

¸

cd . .
haría que nuestro nuevo directorio de trabajo actual fuese “/home”. Se ha
citado el directorio padre mediante un path relativo. El nombre completo
20
3.2. El shell y los metacaracteres
del directorio que tomará la orden será entonces “/home/alumno/..”; com-
pruébese usando la orden pwd.
En cualquier momento podemos hacer que nuestro directorio home
vuelva a ser nuestro directorio de trabajo actual mediante la orden cd sin
argumentos.
_

¸

cd
3.2. El shell y los metacaracteres
Como veremos en todo lo que sigue, muchos de los objetivos de las
órdenes del shell son nombres de archivos o directorios, y en la misma
orden puede haber varios de estos objetivos.
Una forma de simplificar la escritura de las órdenes consiste en usar
ciertos caracteres (metacaracteres o caracteres comodín) para que el shell
los interprete de modo especial, y ello le permita generar diversos nombres
objetivo a partir de ciertos patrones que escribimos en la línea de órdenes.
Así pues, una palabra que contenga estos caracteres se sustituye por una
lista con los nombres de archivo o de directorio que se ajusten al patrón.
Los metacaracteres más usados son
* Representa cualquier cadena (incluida la cadena vacía). El carácter “.”
no es generado en las sustituciones de “*” cuando está en la primera
posición del nombre
? Representa cualquier carácter
[<lista>] Representa cualquier carácter incluido en <lista>. En la lista se
pueden poner caracteres o bien intervalos con el comienzo y el final
separados por un signo menos
[!<lista>] Representa cualquier carácter que no pertenezca a <lista>.
3.3. El editor de texto joe
Hay muchas maneras de crear archivos durante una sesión de tra-
bajo, pero una de las más frecuentes es utilizar un editor de texto para
hacerlo.
21
3. El sistema de archivos
Figura 3.1.: Pantalla inicial de joe para un archivo sin nombre y vacío
En nuestro trabajo utilizaremos habitualmente el editor joe para esa
tarea. joe es un editor adecuado para usar en un terminal alfanumérico. Se
trata de un editor de pantalla completa de tipo WordStar.
La orden para la creación de un archivo nuevo o bien para la edición
de uno ya existente es
_

¸

j oe <nombre−de−archivo>
El nombre de archivo es necesario sólo si se va a editar un archivo
existente. Si se trata de un archivo nuevo, se puede no asignarle nombre
hasta el momento de guardarlo.
En la Figura 3.1 podemos ver la pantalla de trabajo de joe con una
línea de ayuda en la parte superior y una línea de estado en la inferior.
En la zona central el usuario puede escribir el texto del archivo en la
forma usual.
joe es un editor con un interfaz unimodal, que admite multitud de
órdenes mientras se escribe el texto. Todas esas órdenes se dan mediante
22
3.3. El editor de texto joe
Figura 3.2.: Pantalla inicial de joe con el panel de ayuda visible
combinaciones especiales de teclas, pero no es necesario recordarlas todas
para trabajar de forma útil desde el principio. Si se observa la línea de
ayuda de la Figura 3.1 podemos ver que se puede obtener ayuda mediante
la pulsación de las teclas “<Ctrl>-K-H”.
El efecto puede verse en la Figura 3.2.
En ella podemos observar el panel de ayuda con una serie de combina-
ciones de teclas para realizar diversas funciones agrupadas por categorías.
Existen varios paneles de ayuda suplementarios. Pueden verse pulsando
repetidamente la combinación de teclas “<Esc> .” como puede observarse
en la línea superior de la pantalla; para volver hacia atrás por la serie de
paneles de ayuda, la combinación de teclas es “<Esc> ,”.
La notación usada en los paneles de ayuda usa el símbolo “ˆ” para
indicar la pulsación de la tecla “<Ctrl>”. Así por ejemplo, en el grupo de
comandos EXIT, puede verse que si necesitamos salir del editor guardando
el archivo que estamos editando, debemos usar “ˆKX”, lo que significa que
debemos mantener pulsada la tecla “<Ctrl>” mientras pulsamos sucesiva-
mente las teclas “K” y “X”.
Al pulsar la combinación de teclas que activa un comando puede que
obtengamos más peticiones de información en la línea de estado. Por ejem-
23
3. El sistema de archivos
plo, en el caso anterior, si el archivo no tenía nombre, se nos pedirá que
tecleemos el nombre que deseemos asignarle.
Al menos en la etapa inicial, hasta acostumbrarse al uso de joe, se
recomienda mantener en la pantalla del terminal la información de ayuda
mientras se edita un archivo.
3.4. Operaciones básicas en el sistema de archivos
3.4.1. Creación y eliminación de archivos y directorios (rm, mkdir,
rmdir)
La creación de archivos y directorios requiere que el usuario tenga
los permisos necesarios, de modo que un usuario no puede crear, borrar o
mover archivos o directorios a cualquier posición en la jerarquía del sistema
de archivos. Discutiremos este problema en la Sección 3.5. Por el momento
supondremos que todas nuestras operaciones tienen lugar en la parte de la
jerarquía del sistema de archivos que desciende de nuestro directorio home.
Ya hemos visto en la Sección 3.3 una de las formas más comunes
de crear archivos (mediante un editor de texto), pero los archivos a veces
también necesitan ser borrados.
Para eliminar archivos, la orden del shell es rm. Su formato es
_

¸

rm [ opción ] . . . <nombre−de−archivo >. . .
Las opciones más importantes son
-f Ignorar los nodos que no existan. No preguntar
-i Preguntar antes de eliminar cada nodo. Si no se responde pulsando la
tecla “y”, no se borrará el nodo
-r Eliminar recursivamente el contenido de los directorios. La opción se
puede escribir también como -R y debe ser manejada con extremo
cuidado para no perder archivos valiosos
1
La orden para crear directorios es mkdir. Su formato es
1
Véase más abajo la orden rmdir específica para borrar directorios
24
3.4. Operaciones básicas en el sistema de archivos
_

¸

mkdir [ opción ] . . . <nombre−nuevo−di rectori o >. . .
Las opciones más importantes son
-p Crear un directorio creando a la vez los directorios intermedios del path
que no existan
-m<modo> Fijar los permisos del directorio. El formato para <modo> es el
mismo que se usa en la orden chmod (véase la Sección 3.5)
Por ejemplo
_

¸

mkdir −p pepe/juan
creará en nuestro directorio de trabajo actual el subdirectorio “pepe”, y
dentro de éste creará el subdirectorio “juan”.
Para eliminar un directorio, la orden a usar es rmdir. Su formato es
_

¸

rmdir [ opción ] . . . <nombre−de−di rectori o >. . .
La opción más importante es
-p Eliminar un directorio eliminando a la vez los directorios antecesores en
el path que aparezcan citados explícitamente
Para poder eliminar un directorio mediante rmdir debe estar vacío
(esto es, sólo debe contener las entradas “.” y “..”)
3.4.2. Exploración del contenido de un directorio (ls)
Una de las operaciones que hay que realizar más frecuentemente con-
siste en obtener el listado de las referencias que se guardan en un directo-
rio, o como diremos por brevedad, listar el contenido de un directorio. La
orden del shell para hacer ese trabajo es ls. Su formato es
_

¸

l s [ opción ] . . . [ <nombre−de−nodo > ] . . .
25
3. El sistema de archivos
Figura 3.3.: Pantalla con la información de ls en formato largo
Salvo que las opciones indiquen otra cosa, ls listará los nombres de
los archivos que aparezcan como objetivos y los contenidos de los directo-
rios por orden alfabético (si existen). Según las opciones que se usen, esa
información puede estar suplementada por otras. Los nodos cuyos nombres
comienzan por el carácter “.” son considerados ocultos y no aparecerán en
los listados a menos que se use la opción -a.
Si no se especifica ningún objetivo, se listará el directorio de trabajo
actual.
Las opciones más importantes son
-a Listar todos los nodos, incluidos aquellos cuyo nombre comience por el
carácter “.”
-i Imprimir el número de nodo-índice asociado a cada nombre
-l Listar, junto al nombre de cada nodo, sus atributos
-n Como la opción -l pero identificando al propietario y al grupo del nodo
mediante los valores numéricos correspondientes
-R Listar recursivamente los contenidos de los directorios objetivo
Las opciones pueden agruparse y aparecer en cualquier orden. La op-
ción -l proporciona una salida organizada en líneas (una para cada nodo)
26
3.4. Operaciones básicas en el sistema de archivos
con la información siguiente (ver Figura 3.3)
El primer carácter de la línea indica el tipo de nodo. Un carácter “-”
indica un archivo regular; un carácter “d” indica que el nodo es un
directorio
Los 9 caracteres siguientes indican los permisos aplicables al nodo.
Trataremos de ellos en la Sección 3.5
Los siguientes caracteres no blancos indican el número de enlaces
físicos del nodo (ver la Subsección 3.4.8)
La columna siguiente contiene el login del propietario del nodo
La columna siguiente contiene el identificador del grupo de usuarios
del sistema al que está asociado el nodo (abreviadamente grupo del
nodo)
La siguiente columna contiene el tamaño del nodo en bytes
Las columnas siguientes contienen la fecha y la hora de la última
modificación del nodo
La última columna contiene el nombre del nodo
El la Figura 3.3 puede apreciarse que aparecen listados los nombres
del directorio de trabajo actual incluidos los nodos ocultos (se ha empleado
la opción -a).
3.4.3. Copia, movimiento y cambios de nombre (cp, mv)
La orden del shell para copiar archivos es cp. Su formato es
_

¸

cp [ opción ] . . . <origen >. . . <destino>
<origen>. . . indica el (los) nombres de los nodos que deben ser co-
piados. Si alguno de ellos es un nombre de directorio, se necesita usar la
opción -r
2
.
<destino> indica el nombre del nodo donde el nodo origen debe ser
copiado.
Pueden darse los siguientes casos
2
Ver opción -r más abajo
27
3. El sistema de archivos
1. Si <destino> ya existe y es un nombre de archivo, los nodos origen se
copiarán en ese destino, de modo que el contenido de <destino> será
el del último nodo copiado
2. Si <destino> ya existe y es un nombre de directorio, se copiarán los
nodos <origen> en el directorio de destino conservando sus nombres
3. Si <destino> no existe, se creará un archivo con ese nombre y se co-
piarán los nodos origen como en el caso 1
Las opciones más importantes son
-i Modo interactivo. Preguntar antes de sobreescribir
-r Copiar recursivamente los directorios origen. Se puede utilizar esta op-
ción con la forma -R
La orden del shell para mover nodos de un directorio a otro o cam-
biarlos de nombre es mv. La orden tiene dos formatos
_

¸

mv [ opción ] . . . <origen> <destino>
Pueden darse los siguientes casos
1. Si <destino> no existe (es un nombre no usado) se cambiará el nombre
de <origen> para que pase a ser <destino>
2. Si <destino> existe y es un nombre de directorio, <origen> será movido
al directorio de destino
3. Si <destino> existe y es un nombre de archivo
a) Si <origen> es también un nombre de archivo el archivo <des-
tino> será eliminado
3
y luego se cambiará el nombre del archivo
<origen> como en el caso 1
b) En otro caso se señalará un error
_

¸

mv [ opción ] . . . <origen >. . . <di rectori o >
En esta forma, <directorio> debe ser un directorio existente. Cualquier
archivo o directorio <origen>. . . será movido al directorio <directorio>.
3
Ver opciones más abajo
28
3.4. Operaciones básicas en el sistema de archivos
En ambos formatos puede darse el caso de que haya que eliminar
archivos o directorios existentes. En esas situaciones, la orden pedirá por
defecto confirmación para ello si el nodo correspondiente no tiene permiso
de escritura.
Las opciones más importantes son
-i Modo interactivo. Preguntar antes de sobreescribir
-f No preguntar antes de sobreescribir
3.4.4. Exploración del contenido de un archivo (cat, head, tail)
La orden cat se usa para concatenar el contenido de varios archivos
entre sí y enviar el resultado a la salida estándar. Su formato es
_

¸

cat [ opción ] . . . [ <archivo > ] . . .
Si en la orden no figura ningún nombre de archivo, se leerán caracte-
res de la entrada estándar. Si la entrada estándar está asociada al teclado
del terminal, el fin de archivo se indicará, como en todos los casos en los
que se intente leer un archivo a partir del teclado, pulsando la combinación
de teclas “<Ctrl>-D”.
Para explorar parcialmente el contenido de un archivo pueden usarse
las órdenes head y tail. Sus formatos, respectivamente, son
_

¸

head [ opción ] . . . [ <archivo > ] . . .
_

¸

t ai l [ opción ] . . . [ <archivo > ] . . .
La orden head imprime en la salida estándar (por defecto) las 10 pri-
meras líneas de los archivos objetivo. Si hay varios nombres de archivo en la
orden, el grupo de líneas de cada uno irá precedido del nombre del archivo.
La orden tail imprime en la salida estándar (por defecto) las 10 últimas
líneas de los archivos objetivo. Si hay varios nombres de archivo en la orden,
el grupo de líneas de cada uno irá precedido del nombre del archivo.
Las opciones más importantes, aplicables a ambas órdenes, son
29
3. El sistema de archivos
-c Seguida de un número, indica el número de caracteres que deben ser
escritos de cada archivo
-n Seguida de un número, indica el número de líneas que deben ser escritas
de cada archivo
3.4.5. Cambios en el contenido de un archivo (tee)
La orden tee tiene por objeto tomar caracteres de la entrada estándar
y enviarlos a la salida estándar y, opcionalmente, a varios archivos. Su
formato es
_

¸

tee [ opción ] . . . [ <archivo > ] . . .
La opción más importante es
-a Añadir la entrada al final de los archivos. No sobreescribir
3.4.6. Estadísticas sobre el contenido de un archivo (wc)
La orden wc tiene por objeto contar los bytes, las palabras y las líneas
de varios archivos. Su formato es
_

¸

wc [ opción ] . . . [ <archivo > ] . . .
Si hay más de un archivo objetivo se imprime una línea de totales
además de las cuentas para cada archivo. Si no se indica ningún archivo
objetivo, o entre los objetivos aparece el signo “-”, las cuentas se realizan
sobre la entrada estándar.
Las opciones más importantes son
-c Contar los bytes de cada archivo objetivo
-l Contar los caracteres de nueva línea de cada archivo objetivo
-w Contar las palabras de cada archivo objetivo
30
3.4. Operaciones básicas en el sistema de archivos
3.4.7. Actualización de los atributos de fecha y hora de un archivo
(touch)
La orden touch permite cambiar por defecto a la fecha y hora actual
(ver opción -t) los atributos de fecha y hora de uno o varios archivos. Su
formato es
_

¸

touch [ opción ] . . . <archivo >. . .
Si alguno de los archivos no existe, se crea por defecto (ver opción -c)
con tamaño igual a 0.
Las opciones más importantes son
-a Cambiar la fecha y hora de acceso
-c No crear ningún archivo nuevo
-m Cambiar la fecha y hora de modificación
-t Seguida de una fecha y hora con formato [[CC]YY]MMDDhhmm[.ss] cam-
bia los atributos de fecha y hora de los objetivos a la fecha y hora
indicada en vez de la actual
3.4.8. Enlaces (ln)
A diferencia de lo que ocurre con otros sistemas de archivos, los sis-
temas nativos UNIX permiten que un mismo nodo esté referenciado varias
veces en el mismo o distintos directorios, o como diremos por brevedad,
tenga varios nombres.
Así pues, un nodo debe tener al menos un nombre, pero puede tener
más de uno. Ello implica que tanto sus atributos, como el contenido de ese
nodo pueden alcanzarse usando cualquiera de los nombres que tenga.
Por esa razón, a los nombres de los nodos se les denomina en UNIX
enlaces. En esta sección trataremos la orden del shell que permite crear
enlaces. Esa orden es ln. Sus tres formatos más usados son
_

¸

ln [ opción ] . . . <obj eti vo > <nombre−enlace>
31
3. El sistema de archivos
En este caso se creará un nuevo enlace denominado <nombre-enlace>
asociado al mismo contenido y atributos que <objetivo>.
_

¸

ln [ opción ] . . . <obj eti vo >
En este caso se creará en el directorio de trabajo actual un enlace con
el mismo nombre que <objetivo>.
_

¸

ln [ opción ] . . . <obj eti vo >. . . <di rectori o >
En este caso se crearán enlaces para los distintos nodos <objetivo>
con los mismos nombres en el directorio <directorio>.
En UNIX pueden crearse dos tipos de enlaces, denominados respecti-
vamente enlaces físicos (duros) y enlaces simbólicos (blandos). Por defecto,
ln crea enlaces físicos
4
.
Cuando se crea un archivo o un directorio, el enlace correspondiente
es un enlace físico. Nuevos enlaces físicos pueden ser creados solamente
para los archivos. Cualquier nuevo enlace físico que se cree para un archivo
puede usarse de modo equivalente a como se usa el nombre original.
Lo anterior plantea el problema de qué sucede con los enlaces físicos
de los archivos cuando borramos un archivo. Lo que sucede en realidad es
que se borra la referencia al archivo en el directorio correspondiente a ese
enlace, de modo que ese nombre en ese directorio no estará ya asociado
al archivo. Todos los demás enlaces físicos que el archivo tenga podrán
seguir usándose como antes. El sistema de archivos lleva una cuenta de
los enlaces físicos que tiene cada archivo (en la Subsección 3.4.2 puede
verse esa información en los listados que proporciona la orden ls con la
opción -l), y cuando, al borrar un enlace, el sistema detecta que es el último
enlace físico de ese archivo, entonces borra el contenido del archivo (esto
es, declara libre el sitio que ocupa en el almacenamiento).
Los enlaces simbólicos tienen otro comportamiento. Pueden crearse
tanto para archivos como para directorios. En los listados proporcionados
por la orden ls, los enlaces simbólicos aparecen con el formato
<enlace-simbólico> -> <enlace>
4
Para crear enlaces simbólicos véase más abajo la opción -s
32
3.5. Permisos en el sistema de archivos
donde <enlace> es otro enlace existente previamente (físico o simbólico) que
fue el usado para crear <enlace-simbólico>. Además, el carácter que, en los
listados largos de la orden ls (opción -l), se usa para identificar el tipo de
nodo (el primero de cada línea), en el caso de los enlaces simbólicos es el
carácter “l”.
La diferencia esencial entre los enlaces simbólicos y los físicos es que
con los enlaces simbólicos, el acceso a los atributos y a la información del
nodo se logra recorriendo la cadena de posibles enlaces simbólicos hasta
que se encuentra un enlace físico, lo cual permite el acceso a los datos.
Pero cuando se borra el enlace físico final de la cadena o cualquiera de
los enlaces simbólicos intermedios, el enlace simbólico inicial, y todos los
anteriores al punto de rotura en la cadena no se eliminan y se vuelven
entonces enlaces denominados colgantes (esto es, no apuntan a ninguna
parte). Cuando luego intentamos usar un enlace colgante, el sistema de
archivos no puede encontrar el contenido o los atributos del nodo mediante
ese nombre, y señala un error.
Por otra parte, los enlaces simbólicos, al poder realizarse entre direc-
torios, pueden dar lugar a ciclos en el grafo del sistema de archivo, lo cual
puede degradar el rendimiento de determinadas búsquedas.
Las opciones más importantes son
-f Si el nombre del enlace que debe crearse ya existe, borrar el nodo previa-
mente existente y crear luego el enlace
-s Crear un enlace simbólico
3.5. Permisos en el sistema de archivos
Como hemos comentado en la Subsección 3.4.2 y observado en la
Figura 3.3, los archivos y directorios (en general todos los nodos del sistema
de archivos) tienen un propietario, están asociados a un grupo de usuarios
(grupo del nodo) y tienen unos permisos de uso.
En esta sección revisaremos las órdenes del shell que permiten cam-
biar esos atributos para un nodo determinado.
3.5.1. Cambios de propietario y grupo (chown, chgrp)
Las órdenes para cambiar el propietario y el grupo de un nodo son
respectivamente chown y chgrp. Sus formatos son
33
3. El sistema de archivos
_

¸

chown [ opción ] . . . [ <usuario >] [ : [ <grupo >] ] <nodo >. . .
_

¸

chgrp [ opción ] . . . <grupo> <nodo >. . .
En chown <usuario> indica el nuevo propietario para los nodos obje-
tivo.
En las dos órdenes <grupo> indica el nuevo grupo para los nodos
objetivo.
Tanto <usuario> como <grupo> pueden darse mediante los nombres o
mediante los números correspondientes.
En chown, si se especifican <usuario> y <grupo> se cambiarán el pro-
pietario y el grupo de los nodos. Si sólo se especifica <usuario> sólo se cam-
biará el propietario, y si sólo se especifica <grupo> (precedido del carácter
“:”) sólo se cambiará el grupo (el funcionamiento en este caso es idéntico
al de chgrp). Si no se especifica ninguno de los dos datos no se realizará
ningún cambio.
Para ejecutar cualquiera de las dos órdenes es necesario estar operan-
do en el terminal como superusuario (root).
La opción más importantes para ambas órdenes es
-R Operar en archivos y directorios recursivamente
3.5.2. Cambios de permisos (chmod)
Para el manejo de los permisos de un nodo, UNIX distingue tres tipos
de usuarios: el propietario del nodo, los usuarios del grupo del nodo y el
resto de los usuarios del sistema.
Para cada nodo, UNIX almacena los permisos aplicables a cada uno
de los tres tipos de usuarios. Para cada tipo, UNIX almacena si ese tipo de
usuarios tiene permiso de lectura, si tiene permiso de escritura y si tiene
permiso de ejecución. En cada caso el permiso puede estar concedido o no.
Hay, por tanto, tres permisos para cada tipo de usuario, lo que hace en
total 9 permisos que pueden estar concedidos o no.
Para cualquier operación en la que se necesite manipular un nodo, el
sistema de archivos controla si se tiene permiso para hacerla o no a partir
del identificador del usuario que pretende hacer la operación.
34
3.5. Permisos en el sistema de archivos
El algoritmo usado es el siguiente: Si el usuario que pretende hacer
la operación es el propietario del nodo, se le aplicarán los permisos del
propietario; si no, si el usuario pertenece al grupo de usuarios del nodo, se
le aplican los permisos del grupo. Si no, se le aplican los permisos del resto
de los usuarios.
Como ya hemos visto en la Figura 3.3, esos permisos para cada nodo
están descritos por los 9 caracteres que siguen al primero en la línea del
informe que corresponde a cada nodo. La notación utilizada es la siguien-
te: los tres primeros caracteres son los permisos del propietario, los tres
siguientes los permisos del grupo y los tres restantes los permisos de los
demás usuarios.
En cada caso los tres permisos de cada tipo de usuarios están orde-
nados de la misma forma: lectura, escritura y ejecución. Si hay permiso de
lectura habrá una “r”, si hay permiso de escritura habrá una “w”, si hay
permiso de ejecución habrá una “x”. Si cualquiera de los permisos no está
concedido, en su lugar habrá un carácter “-”.
Por ejemplo
rw-r-x-w-
indica permisos de un nodo en el que el propietario tiene permiso de lectura
y escritura, pero no de ejecución; los usuarios del grupo del nodo tienen
permisos de lectura y de ejecución, pero no de escritura; el resto de los
usuarios tiene permiso de escritura, pero no de lectura ni de ejecución.
El significado de los permisos difiere un poco en el caso de los archivos
y en el caso de los directorios.
Tener permiso de lectura en un archivo significa que se puede leer su
contenido.
Tener permiso de escritura en un archivo significa que su contenido
puede ser cambiado.
Tener permiso de ejecución en un archivo significa que, si el archivo
contiene un programa ejecutable o una macro del shell (el texto de un pro-
grama interpretable por el shell), podrá pedirse al shell que intente ejecutar
el programa correspondiente. Recuérdese que los nombres de los archivos
no tienen en UNIX ningún significado especial. La forma en que UNIX dis-
tingue los archivos que (en principio) contienen programas es mediante los
permisos de ejecución que tenga el archivo.
35
3. El sistema de archivos
Tener permiso de lectura en un directorio significa que podemos ex-
plorar su contenido (por ejemplo podemos listar su contenido mediante ls).
Tener permiso de escritura en un directorio significa que podemos
cambiar las entradas del directorio. Por ejemplo si queremos crear un ar-
chivo necesitamos permiso de escritura en el directorio donde vayamos a
hacerlo para poder crear la entrada. Lo mismo sucede si queremos borrar
un archivo o cambiar sus permisos, etc.
Tener permiso de ejecución en un directorio significa que podemos
atravesar ese directorio para buscar un archivo o un directorio en él. Así
pues, para acceder a un archivo, necesitamos tener permiso de ejecución
en todos los directorios del path que usemos en el nombre completo del
archivo, además de los permisos de acceso al archivo mismo, que depen-
derán de lo que necesitemos hacer con él. Otro caso en el que necesitamos
permiso de ejecución en un directorio es cuando necesitamos convertirlo en
nuestro directorio de trabajo actual (mediante la orden cd).
Una aplicación, cuando crea un nodo, le asigna unos permisos, y el
propietario y el grupo del nodo quedan asignados como el login y el identi-
ficador de grupo del usuario que ejecuta la aplicación.
Posteriormente, el propietario del nodo y sólo él, aparte del superusua-
rio, puede cambiar los permisos del nodo.
La orden del shell para cambiar esos permisos es chmod. Su formato
básico es
_

¸

chmod [ opción ] . . . <nuevo−modo> <nodo >. . .
<nuevo-modo>es un número de entre una y cuatro cifras octales. Las
cifras que no figuren se considerarán ceros a la izquierda.
Nosotros, en esta introducción sólo usaremos las tres últimas cifras
octales; la de más a la izquierda la consideraremos cero. Esa cifra se utiliza
para usos especiales que no discutiremos aquí.
Como tres cifras octales corresponden a números de 9 bits, que son
el número de permisos que tenemos que manejar, formaremos el número
<nuevo-modo> de modo que tenga un bit 1 en el lugar correspondiente a
un permiso que esté concedido, y un bit 0 en el lugar correspondiente a un
permiso no concedido. La ordenación de los bits deberá ser la misma que
hemos descrito antes.
Por ejemplo si queremos que los nuevos permisos sean
36
3.5. Permisos en el sistema de archivos
r--rwx-w-
<nuevo-modo> deberá ser 472, puesto que ese número octal, en binario, se
escribiría 100111010.
La orden chmod cambiará los permisos (modo) de los nodos objetivo
a <nuevo-modo>.
Esta forma de especificar los permisos mediante un número octal se
denomina absoluta.
Los permisos de los enlaces simbólicos no pueden ser cambiados me-
diante chmod. En realidad esos permisos se asignan siempre de la misma
forma por el sistema de archivos y son inmutables. Cuando se usa chmod
con un enlace simbólico como objetivo, en realidad, el cambio de permisos
afecta al nodo apuntado por la cadena de enlaces que comienza en el enlace
simbólico (si es que el nodo existe).
La orden chmod admite otro formato, en el que se utiliza una forma
de especificar los permisos que se denomina simbólica
_

¸

chmod [ opción ] . . . <modo>[ , <modo> ] . . . <nodo >. . .
Con este formato se usan las letras “u”, “g”, “o” para indicar a qué
tipo de usuarios se refieren los nuevos permisos (“u” para el propietario,
“g” para el grupo de usuarios del nodo y “o” para el resto de los usuarios).
Se usan los signos “=”, “+”, “-” para indicar si los nuevos permisos deben
añadirse a los actuales (“+”), deben retirarse de los actuales (“-”) o deben
ser exactamente los que se indican (“=”). Se usan las letras “r”, “w”, “x” para
referirse a permisos de lectura (“r”), de escritura (“w”), y de ejecución (“x”).
Si hay que asignar distintos permisos a diferentes tipos de usuarios,
se pueden encadenar las expresiones simbólicas mediante el carácter “,”.
Por ejemplo
ug+rw,o=rx
indicaría que el nuevo modo aplicable a los nodos objetivo debería añadir
(“+”) permisos de lectura y escritura (“rw”) a los permisos que tenga cada
nodo para el propietario y para el grupo del nodo (“ug”), mientras que se
desea que, en cada nodo, los permisos del resto de los usuarios (“o”) sean
37
3. El sistema de archivos
exclusivamente de lectura y ejecución (“rx”), con independencia de los que
tuvieran previamente (“=”).
La opción más importante es
-R Operar en archivos y directorios recursivamente
38
4. Operaciones especiales asociadas
a las órdenes del shell
4.1. Variables de shell (set, export, echo)
Como cualquier otro intérprete, el shell admite la definición de varia-
bles. Las variables del shell tienen nombres que se escriben convencional-
mente en letras mayúsculas.
Uno de los usos más corrientes de las variables del shell consiste en
describir valores que deben ser usados en el entorno de ejecución de ciertas
órdenes (véase Subsección 4.1.1). Al abrir una sesión del shell, el usuario
dispone de un entorno con una serie de variables ya definidas. Entre ellas,
algunas de las más importantes son PATH, BASH, HISTSIZE, HISTFILESI-
ZE, HISTFILE, HOME, HOSTNAME, LANG, USER, UID.
La orden set permite manipular los valores de las variables ya defini-
das. Su formato es
_

¸

set [ opciones ] [−o <nombre−opción>] [ <argumento > . . . ]
Cuando set se usa sin opciones ni argumentos, permite obtener un
listado de todas las variables del shell definidas junto con sus correspon-
dientes valores.
La orden export sirve para definir una variable que será pasada a los
procesos hijos del shell formando parte de su entorno. Su formato es
_

¸

export [ opciones ] [ <nombre>[=<valor >] ]
<nombre> es el nombre de la variable, mientras <valor> es corriente-
mente una única palabra (típicamente un número entero) o bien una ca-
dena de caracteres entre comillas dobles si la cadena contiene caracteres
blancos.
39
4. Operaciones especiales asociadas a las órdenes del shell
La orden echo sirve para imprimir un mensaje en la salida estándar.
Su formato es
_

¸

echo [ opción ] . . . [ <cadena > ] . . .
<cadena> es la cadena de caracteres que desea enviarse a la salida.
Si la cadena de caracteres es de la forma $<nombre_de_variable> lo que se
enviará a la salida es el valor de la variable.
4.1.1. El entorno de una orden (env)
Cuando el shell genera un proceso hijo para ejecutar una orden, por
defecto le pasa como variables de entorno todas las que él tenga definidas.
No obstante, si se quieren fijar explícitamente las variables de entorno
que una orden debe usar, se puede usar la orden env para ejecutar la orden
del shell deseada. El formato de la orden env es
_

¸

env [ opción ] . . . [ <nombre>=<valor > ] . . . [ <Comando> [ <argumento > ] . . . ]
4.1.2. Path de los ejecutables del shell (which)
La orden which permite obtener el path de un archivo ejecutable si
se conoce su nombre. La búsqueda recorre el sistema de archivo buscando
en los directorios citados en la variable PATH, por orden, y por defecto (ver
opción -a) se detiene en cuanto encuentra el nombre del ejecutable por
primera vez. El formato de la orden es
_

¸

which [ opciones ] [−−] <nombre_de_programa >. . .
which permite por tanto encontrar un ejecutable que resida en uno
de los directorios citados en la variable PATH, y en particular cualquiera de
las órdenes del shell.
La opción más importante es
-a Imprimir todos los path del ejecutable que se encuentren en los directo-
rios citados en la variable de entorno PATH, y no sólo el primero
40
4.2. Redirección de la entrada, la salida y la salida de error estándares
4.2. Redirección de la entrada, la salida y la salida de
error estándares
Muchas órdenes del shell usan como dispositivo de entrada el que esté
definido como entrada estándar. Comunmente ese dispositivo es el teclado
del terminal, pero el shell permite que una orden use como dispositivo de
entrada estándar un archivo. El modo de hacerlo consiste en añadir a la
orden tal como se escribiría normalmente el nombre del archivo del que
queremos que se obtengan los caracteres de entrada precedido del operador
“<” (puede haber espacios de separación entre ambos). Por ejemplo
_

¸

cat < entrada
tomaría los caracteres del archivo cuyo nombre es entrada y los enviaría a
la salida estándar (típicamente la pantalla del terminal).
La operación anterior se denomina redirección de la entrada estándar.
Es posible también redirigir la salida estándar. La forma de conseguir-
lo es análoga a la anterior, pero el operador es el signo “>”. Por ejemplo
_

¸

cat uno dos > sal i da
enviaría la concatenación de los contenidos de los archivos uno y dos a la
salida estándar, pero ésta no sería la pantalla del terminal sino un archivo
llamado salida. Si el archivo existe, su contenido previo se pierde.
Si se usa el operador “>>” para la redirección de la salida, ésta se
envía al archivo de destino, pero se añade al final de él (el archivo no se
sobreescribe).
También se puede redirigir la salida de error estándar de la misma
forma, salvo que el operador correspondiente es “2>”.
Se pueden usar los operadores anteriores en la forma <$<dígito>,
>$<dígito>, donde <dígito> es un descriptor de archivo abierto, típicamente
el de la entrada estándar, el de la salida estándar o el de la salida de error
estándar. Los descriptores de la entrada estándar, la salida estándar y la
salida de error estándar son respectivamente 0, 1 y 2.
41
4. Operaciones especiales asociadas a las órdenes del shell
4.3. Agrupación y encadenamiento de órdenes
Ante el indicador (prompt) del shell podemos escribir una orden tal
como lo hemos hecho hasta ahora, pero también es posible escribir varias,
separadas por uno de los separadores siguientes: “;”, “&&” y “||”.
Las órdenes agrupadas de esa forma se ejecutarán una tras otra in-
condicionalmente si están separadas por el separador “;”.
El separador “&&” ejecuta la orden siguiente sólo si la anterior devuel-
ve 0 como estado de salida (éxito). En caso contrario, el resto de las órdenes
parciales no se ejecutan.
El separador “||” ejecuta la orden siguiente sólo si la anterior devuelve
un valor distinto de 0 como estado de salida (fracaso). En caso contrario, el
resto de las órdenes parciales no se ejecutan.
Otra operación posible con las órdenes es el encadenamiento. Se pue-
de usar la salida de una orden (<orden_1>) como entrada de otra (<or-
den_2>), y la salida de ésta como entrada de otra, etc. La forma de hacerlo
es conectar las órdenes parciales mediante el operador “|” de la forma si-
guiente
_

¸

<orden_1> | <orden_2> | . . .
Por ejemplo
_

¸

l s −al | wc −l
escribirá en la salida estándar el número de líneas del informe que produ-
ciría la orden ls.
El operador ‘’|” se llama operador pipe. Se pueden agrupar pipes de la
misma forma que las órdenes simples.
4.3.1. Ejecución en segundo plano
Hasta ahora hemos ejecutado órdenes de modo que hay que esperar
a que termine la ejecución de la orden antes de que el shell nos permita
escribir y pedir la ejecución de otra.
42
4.3. Agrupación y encadenamiento de órdenes
Figura 4.1.: Ejemplo de ejecución en segundo plano
Esa forma de trabajar se denomina ejecución en primer plano.
A veces, sin embargo, nos interesa poder ejecutar una orden, y, sin
esperar a que termine, pedir al shell que ejecute otra. Para ello, la primera
orden debe ser seguida del operador “&”, lo que indicará al shell que desea-
mos ejecutarla en segundo plano, concurrentemente (UNIX es un sistema
multiprogramado) con otras que escribiremos luego.
Cuando hacemos eso, el shell imprime un número que es el identifi-
cador de proceso de la orden en segundo plano y vuelve inmediatamente
a escribir el prompt para permitirnos escribir nuevas órdenes (que pueden
ejecutarse en primer plano o también, si queremos, en segundo plano).
Es corriente redirigir los descriptores de la entrada, la salida y la salida
de error estándares de las órdenes que se ejecutan en segundo plano para
evitar que lean o escriban en los mismos dispositivos que estemos usando
para las órdenes que ejecutemos en primer plano.
Por ejemplo, si escribimos
_
`
¸

which wc > sal i da &
l s −al
43
4. Operaciones especiales asociadas a las órdenes del shell
se ejecutará la orden which en segundo plano, enviando su salida al ar-
chivo salida. Inmediatamente de comenzar la ejecución de which podemos
escribir la orden ls y obtener su listado de salida. En él, y puesto que la
ejecución de which es muy corta, es posible que ya figure el archivo salida,
producido por which, por lo que podría obtenerse la salida de la Figura 4.1,
que indica el identificador de proceso de la orden which (en el ejemplo 1152)
y, más tarde, que la orden which ha terminado su ejecución.
4.4. Creación de shells hijos (sh)
A veces es necesario crear en el shell un proceso hijo que ejecute otro
shell (subshell), para poder ejecutar en él ordenes distintas de las que que-
ramos ejecutar en el shell de partida. La orden para crear un subshell es
sh. Su formato es
_

¸

sh
La orden sh crea un nuevo proceso que ejecuta otro shell, sobre el que
podremos ejecutar órdenes o macros del shell y del que podremos salir al
terminar mediante la orden exit como habitualmente.
44
5. Otras órdenes útiles
5.1. Búsqueda de información en el sistema de archivos
Dada la gran cantidad de información que está almacenada habitual-
mente en cualquier sistema de archivos, uno de los problemas más fre-
cuentes que se plantea al usuario del sistema es localizar los archivos que
le interesan.
Distinguiremos dos tipos genéricos de búsquedas: aquellas en las que
se intenta localizar el o los archivos que nos interesan a partir de condicio-
nes sobre su nombre u otros atributos y aquellas otras en las que queremos
localizar las líneas de los archivos que contienen (en su interior) determina-
das cadenas de caracteres.
5.1.1. Búsqueda de archivos por sus atributos (find, locate)
La orden find
La orden find se usa para localizar archivos particulares y posible-
mente ejecutar algún tipo de acción sobre ellos. Esta orden busca, explo-
rando recursivamente uno o varios directorios, los nodos que cumplan las
condiciones expresadas en la orden y les aplica las acciones que se indi-
quen.
A lo largo de la ejecución de la orden find, se va tratando cada no-
do de los directorios afectados (nodo objetivo) de modo que se comprueba
si se cumplen las condiciones expresadas para aplicar o no las acciones
correspondientes.
En GNU, el formato de la orden es
_

¸

f i nd [−H] [−L] [−P] [ <path > . . . ] [ <expresión >]
45
5. Otras órdenes útiles
Las opciones -H, -L y -P regulan el comportamiento de la orden en el
tratamiento de los enlaces simbólicos
1
. De esas opciones sólo se usará la
última que aparezca. Si no figura ninguna opción, por defecto se usará -P,
lo que significa que los enlaces simbólicos no se seguirán, y la aplicación de
las condiciones se realizará sobre el propio enlace simbólico.
<path> es el nombre de un path de búsqueda en el que debe realizarse
la exploración recursiva. Si no se especifica ninguno, se tomará el nombre
del directorio de trabajo actual. La lista de posibles paths de búsqueda
termina en cuanto aparezca un argumento que comience por el signo “-”.
<expresión> es un argumento formado por opciones, tests y acciones
interconectadas por operadores lógicos. Las distintas partes de la expresión
son evaluadas como operandos lógicos, siguiendo las reglas de preceden-
cia de los operadores, y evaluando los operandos del mismo operador de
izquierda a derecha. La evaluación de la expresión termina en cuanto se
conozca su resultado lógico (en cuanto el operando izquierdo de un AND
lógico sea falso o cuando el operando izquierdo de un OR lógico sea cierto).
A continuación se continúa con el tratamiento del siguiente nodo objetivo.
Si no figura ninguna expresión en la orden se tomará por defecto -
print.
Opciones: Todas ellas retornan el valor true. Salvo -follow y
-daystart
2
, las demás tienen efecto para todos los nodos objetivo, inde-
pendientemente de dónde aparezcan escritas las opciones en la expresión.
Por ello se colocan generalmente, por claridad, al principio de ella. Las op-
ciones más importantes son
-maxdepth Seguida de un entero no negativo n, indica descender recursi-
vamente como máximo n niveles por debajo del path de búsqueda que
figura en la orden. Si n es 0 sólo se aplicará la expresión a los paths
de búsqueda de la orden (sin hacer un recorrido recursivo)
-mindepth Seguida de un entero no negativo n, indica que no se debe apli-
car ningún test ni acción a los nodos situados a menos de n niveles
por debajo del path de búsqueda que figura en la orden. Si n=1 se tra-
tarán todos los nodos que estén por debajo estrictamente de los paths
de búsqueda de la orden
Tests: Son condiciones que deben ser comprobadas sobre cada nodo
objetivo. Su resultado será true o false según se cumpla la condición o no.
En ellos pueden figurar argumentos numéricos en las siguientes formas
1
Para los detalles se recomienda la consulta de la página de manual de find
2
Para más detalles véase la página de manual de find
46
5.1. Búsqueda de información en el sistema de archivos
+n Su significado es un número mayor que n
-n Su significado es un número menor que n
n Su significado es exactamente n
Los tests más importantes que suelen usarse son
-gid Seguido de un entero n, comprueba si el grupo del nodo objetivo tiene
el identificador numérico n
-group Seguido de un nombre, comprueba si el grupo del nodo objetivo
tiene ese identificador. Se permite también un identificador numérico
-inum Seguido de un entero n, comprueba si el inodo del nodo objetivo tiene
el valor numérico n
-links Seguido de un entero n, comprueba si el número de enlaces del nodo
objetivo tiene el valor numérico n
-name Seguido de un nombre, comprueba si el nombre del nodo objeti-
vo coincide con éste. Si el nombre incluye metacaracteres (ver Sec-
ción 3.2) es necesario encerrarlo entre apóstrofos para evitar la expan-
sión por el shell
-perm Seguido de un modo expresado en octal o de forma simbólica (véase
la Subsección 3.5.2), comprueba si el modo del nodo objetivo es el
indicado
-size Seguido de un entero n, comprueba si el tamaño del nodo objetivo
tiene el valor numérico n. Por defecto el tamaño se mide en bloques
de 512 bytes pero se puede cambiar la unidad añadiendo sufijos al
valor de n. El caso más importante es el del sufijo “c” para que n se
interprete como tamaño medido en caracteres. Otros sufijos posibles
son “k” (para especificar kilobytes), “M” (para especificar megabytes) y
“G” (para especificar gigabytes)
-type Seguido de un descriptor de tipo, comprueba si el tipo del nodo ob-
jetivo es el correspondiente al descriptor. Los descriptores de tipo más
importantes son
f Archivo regular
d Directorio
l Enlace simbólico
b Dispositivo de bloques
c Dispositivo de caracteres
47
5. Otras órdenes útiles
-uid Seguido de un entero n, comprueba si el propietario del nodo objetivo
tiene el identificador numérico n
-user Seguido de un nombre, comprueba si el propietario del nodo objetivo
tiene ese identificador. Se permite también un identificador numérico
Acciones: Son los elementos de la expresión que en la orden find
indican qué debe hacerse con un nodo objetivo cuando la evaluación de la
expresión hasta el momento en que se alcanza la acción toma el valor true.
Se pueden incluso ejecutar otras órdenes del shell en ese caso
3
, pero las
acciones más corrientes son
-delete Se usa para borrar el nodo objetivo. Su resultado es true si el bo-
rrado tiene éxito y false caso contrario
-print Se usa para imprimir en la salida estándar el nombre completo del
nodo objetivo seguido de un carácter de nueva línea. Su resultado es
true
-printf Seguida de una cadena de formato, se usa para imprimir en la salida
estándar los valores de diferentes atributos del nodo objetivo con un
formato determinado. Su resultado es true. La cadena de formato es-
pecifica los atributos a imprimir, su orden y el formato correspondiente
de modo parecido a como funciona la función printf de la biblioteca
estándar C. A diferencia de -print, -printf no añade al final de su
salida un carácter de nueva línea a no ser que la cadena de formato lo
especifique. Para más información sobre la construcción de la cadena
de formato y la sintaxis de los grupos de sustitución véase la página
de manual de la orden find. Esta acción no es conforme al estándar
POSIX
-ls Su resultado es true. Se usa para imprimir en la salida estándar los
atributos del nodo objetivo en el mismo formato que lo haría la orden
ls con las opciones -dils. Esta acción no es conforme al estándar POSIX
Si en una expresión no hay acciones distintas de la acción -prune
4
la acción tomada por defecto cuando la expresión resulta true para el nodo
objetivo es -print.
Operadores lógicos: Las diferentes partes de una expresión pueden
aparecer conectadas mediante operadores lógicos. La evaluación sigue nor-
mas de prioridad para los distintos operadores, pero el orden de evaluación
3
Consúltese en la página de manual de find el uso de la acción -exec y otras análogas
4
Para más detalles véase la página de manual de la orden find
48
5.1. Búsqueda de información en el sistema de archivos
puede ser alterado mediante el uso de paréntesis. Cuando se usan parén-
tesis deben escribirse como “\(” y “\)” para evitar su interpretación especial
por el shell. Los operadores lógicos por orden de prioridad decreciente son
! Su forma es !expr. El valor es true si expr es false y viceversa
-a Su forma es expr1 -a expr2. El valor es true si ambas expr1 y expr2 son
true, y false en otro caso. Si expr1 es false, expr2 no se evalúa. Si entre
dos expresiones no aparece ningún operador se presupone -a
-o Su forma es expr1 -o expr2. El valor es true si alguna de las expresiones
expr1 o expr2 o ambas son true, y false en otro caso. Si expr1 es true,
expr2 no se evalúa
En la forma GNU de find puede formarse una lista de subexpresiones
separándolas mediante el carácter “,”. En ese caso las subexpresiones se
evalúan todas, de izquierda a derecha y el valor de la expresión completa
es el de la última subexpresión. Como operador, la coma es el de menor
prioridad. Este operador no es conforme al estándar POSIX.
Ejemplos:
_

¸

f i nd /etc −si ze +20 −pri nt
busca los archivos de un tamaño mayor de 20 bloques de 512 bytes por
debajo del directorio /etc e imprime sus nombres completos.
_

¸

f i nd $HOME \( −name ’ ∗ . [ cf ] ’ −o −name ’ ∗ . old ’ \) −type f −l s
busca recursivamente en el directorio HOME del usuario los nodos con
nombres que terminen en “.c”, “.f” o “.old” y que además sean archivos
regulares, e imprime en la salida estándar la información de directorio co-
rrespondiente en formato largo.
La orden locate
La orden locate permite a un usuario buscar en una base de datos
indexada los archivos cuyo nombre contiene una determinada cadena de
caracteres. Sus dos formas básicas más corrientes son
49
5. Otras órdenes útiles
_

¸

l ocate −u [−o <nombre−base−datos >]
_

¸

l ocate [−−database=<path>] <cadena−búsqueda>
La primera forma se usa para crear una base de datos o actualizarla.
Sólo el administrador del sistema puede crear la base de datos por defecto
y omitir la opción -o y el <nombre-base-datos>. La base de datos creada
contendrá información de todos los archivos del sistema de archivos, puesto
que se construye a partir del directorio raíz. Para otras opciones consúltese
la página de manual de locate.
La segunda forma busca en la base de datos que se especifique, o en
la base de datos por defecto en caso contrario, los nodos del sistema de
archivo que contengan en su nombre <cadena-búsqueda>.
5.1.2. Búsqueda de archivos por su contenido (grep)
La orden grep obtiene las líneas que responden a un patrón en uno o
varios archivos. Su formato es
_

¸

grep [ opciones ] <patrón> [ <nombre−archivo > . . . ]
grep busca en los archivos cuyos nombres se citan en la orden, o en
la entrada estándar si en la orden no aparece ningún nombre de archivo,
las líneas que corresponden a un patrón y, por defecto, las imprime en la
salida estándar.
<patrón> es una expresión regular construida análogamente a las ex-
presiones aritméticas mediante el uso de diferentes operadores para com-
binar expresiones más simples. Las expresiones regulares más simples son
los caracteres. Todas las letras y cifras son expresiones regulares que con-
cuerdan consigo mismas. Para una información detallada sobre la sintaxis
de las expresiones regulares admitidas por grep véase la página de manual
correspondiente.
Las opciones más importantes son
50
5.2. Manipulación de archivos
-c Suprimir la salida normal y en vez de ella imprimir el número de líneas
coincidentes en cada archivo
-n Añadir a cada línea coincidente un prefijo con el número de línea que le
corresponde dentro del archivo en el que aparece
-r Leer todos los archivos por debajo de cada directorio recursivamente. La
opción se puede escribir también en la forma -R
-H Imprimir el nombre del archivo para cada coincidencia
5.2. Manipulación de archivos
5.2.1. Compresión de archivos (gzip)
La orden gzip permite comprimir (y opcionalmente descomprimir) ar-
chivos usando una codificación LZ77. Su formato es
_

¸

gzi p [ opciones ] [ <nombre−archivo > . . . ]
Si no se indica ningún <nombre-archivo> o si <nombre-archivo> es el
carácter “-”, la entrada se tomará de la entrada estándar y la salida se en-
viará a la salida estándar. En cualquier caso, gzip sólo comprimirá archivos
regulares, y en particular ignorará los enlaces simbólicos. Por defecto, si se
especifica algún <nombre-archivo>, gzip creará un archivo con el nombre
formado a partir del nombre del archivo original, añadiéndole los caracteres
“.gz”
Las opciones más importantes son
-c Enviar la salida a la salida estándar
-d Descomprimir uno o varios archivos comprimidos
-f Forzar la compresión o descompresión incluso si el archivo tiene varios
enlaces o el archivo de salida ya existe
-r Recursivo. Si cualquiera de los nombres que aparecen en la orden es un
nombre de directorio, se recorrerá éste recursivamente comprimiendo,
o en su caso descomprimiendo, los archivos que encuentre
Es conveniente revisar la forma de la orden gunzip para descomprimir
archivos en la misma página de manual de gzip.
51
5. Otras órdenes útiles
5.2.2. Serialización de archivos (tar)
La orden tar permite serializar archivos o directorios creando un ar-
chivo único (archivo tar) que puede ser deserializado en cualquier momento
para regenerar los archivos o la estructura de directorio originales. Su for-
mato es
_

¸

tar [ opciones ] [ <nombre−archivo > . . . ] [ <nombre−di rect ori o . . . >]
El archivo tar correspondiente es enviado por defecto a la salida es-
tándar. Las opciones más importantes son
c Crear un archivo tar
f Seguida de un nombre de archivo, causará que se cree un archivo tar con
ese nombre en vez de enviar la salida a la salida estándar
r Permite añadir archivos a un archivo tar
t Listar el contenido de un archivo tar
x Extraer el contenido de un archivo tar (deserializarlo)
u Añadir a un archivo tar los archivos que se indican sólo si son más mo-
dernos que los contenidos en el archivo tar
v Listar los archivos procesados
z Comprimir el archivo tar mediante gzip
De entre las opciones anteriores debe usarse una sola entre las “c”,
“r”, “t”, “x”, “u”.
5.2.3. Ordenación de líneas en archivos (sort)
La orden sort permite ordenar las líneas de varios archivos. Su for-
mato es el siguiente
_

¸

sort [ opción ] . . . [ <nombre−archivo > . . . ]
52
5.2. Manipulación de archivos
Se ordenan los diferentes archivos de acuerdo con las opciones indi-
cadas y se envía la concatenación de los resultados a la salida estándar.
Si no figura ningún <nombre-archivo> se toma la entrada de la entrada
estándar. Salvo que las opciones indiquen lo contrario, el criterio de orde-
nación corresponde al del código utilizado para representar los caracteres.
Las opciones más importantes son las siguientes
-b Ignorar los caracteres blancos a la izquierda de los campos
-f Ignorar la diferencia entre mayúsculas y minúsculas y ordenar como si
todo fueran mayúsculas
-n Ordenar usando para los números su valor, no por caracteres. Así, con
la opción -n, 1234 es posterior a 32, pero sin la opción -n el orden es
el contrario
-r Orden inverso. Ordenar de mayor a menor
-k Seguido de una especificación de claves (ver más abajo) se usa para
ordenar las líneas del archivo por distintos campos
La especificación de claves de ordenación es de la forma POS1[,POS2],
donde POS1 indica la posición en la que comienza la clave de ordenación
y POS2 la posición en la que termina la clave de ordenación. Si POS2 no
existe, el fin de la clave es el fin de línea. Si no se usa la opción -k, la clave
de ordenación es la línea completa.
El formato de POS1 (y de POS2) es F[.C][<opciones>]. “F” es el número
de campo (los campos son series de caracteres separadas por caracteres
blancos o tabuladores) mientras “C” es el número de carácter dentro del
campo.
Las <opciones> que pueden acompañar la especificación de la posi-
ción de comienzo o final de una clave son letras que indican opciones de
ordenación que se aplican para esa clave en vez de las opciones generales
especificadas en la orden. Para más información véase la página de manual
de sort.
El uso de la opción -k es propio de la versión GNU de sort. En la
mayoría de las versiones de UNIX, sort especifica las claves anteponiendo
un signo “+” a la especificación de la posición inicial y un signo “-” a la
de la posición final, sin utilizar la coma. No obstante, en esta forma, la
clave comienza en el campo siguiente al especificado y termina en el que se
especifique. Por ejemplo
_

¸

sort +2 −3
53
5. Otras órdenes útiles
ordenará la entrada estándar usando como clave los caracteres del tercer
campo de cada línea.
Pueden especificarse varias claves de ordenación. En ese caso la orde-
nación tiene lugar por la primera clave que aparezca en la línea de órdenes,
y, para las líneas en las que esa clave resulte igual, se realizará una orde-
nación por la siguiente clave, etc.
5.2.4. Sumas de comprobación en archivos (md5sum)
La orden md5sum permite generar una suma MD5 (128 bits) tal como
se describe en la RFC 1321 para cada uno de los archivos cuyo nombre
aparezca en la línea de órdenes. Si no se da ningún nombre de archivo o se
usa el carácter “-” se lee de la entrada estándar.
La orden se utiliza para comparar los contenidos de uno o más archi-
vos, puesto que dos archivos de contenidos diferentes tendrán sumas de
comprobación MD5 diferentes. El formato de la orden es el siguiente
_

¸

md5sum [ opciones ] [ <nombre−archivo > . . . ]
Las opciones más importantes son las siguientes
-b Los archivos se leen en modo binario
-t Los archivos se leen en modo texto (opción por defecto)
5.2.5. Visualización del contenido de un subdirectorio (tree)
La orden tree permite obtener una representación en forma de árbol
del contenido de uno o varios subdirectorios recursivamente, y resulta es-
pecialmente cómoda para orientar la navegación por el grafo de directorios
del sistema de archivos.
_

¸

tree [ opciones ] [ <nombre−di rectori o > . . . ]
Si no se indica ningún directorio se lista el contenido del directorio de
trabajo actual. Los enlaces simbólicos se indican en la forma <nombre> ->
<path-real>.
Las opciones más importantes son las siguientes
54
5.3. Miscelánea
-a Se usa para conseguir que en el listado aparezcan también los nodos
ocultos
-f Si se usa, en el listado aparecerá el path completo de cada nodo
-L Seguida de un valor entero indica el máximo nivel de profundidad que
queremos alcanzar en el listado recursivo. Si no se usa esta opción, la
recursividad se realiza completa
5.3. Miscelánea
5.3.1. Información sobre el sistema de archivos (df, du)
La orden df permite obtener en la salida estándar información sobre
el espacio disponible en los sistemas de archivos que contienen archivos
determinados. Su formato es el siguiente
_

¸

df [ opción ] . . . [ <nombre−archivo > . . . ]
Si no se especifica ningún nombre de archivo se proporciona la infor-
mación sobre todos los sistemas de archivos montados actualmente.
Si el nombre de archivo que se usa corresponde a un dispositivo que
contiene un sistema de archivos, la información que se obtiene es la corres-
pondiente a ese sistema de archivos, y no la del sistema de archivos en el
que reside el archivo mismo (típicamente el sistema de archivos raíz). Las
opciones más importantes son
-h Imprimir los tamaños en formato legible por personas
-i Listar la información de i-nodos en vez de la de bloques usados
-T Imprimir el tipo de sistema de archivos correspondiente
La orden du permite obtener información sobre el espacio de disco
ocupado por archivos o (recursivamente) por directorios. Su formato es
_

¸

du [ opción ] . . . [ <nombre−archivo > . . . ]
55
5. Otras órdenes útiles
Por defecto du proporciona la información de tamaño en bloques de
512 bytes, y en caso de no usarse ningún <nombre-archivo>, la información
proporcionada corresponderá al directorio de trabajo actual. Las opciones
más importantes son
-a En el caso de directorios imprimir el espacio ocupado por cada archivo
no sólo el total del directorio
-c Imprimir una línea de total general
-h Imprimir los tamaños en formato legible por personas
-s Imprimir solamente una línea de total para cada argumento
5.3.2. Determinación del tiempo consumido por una orden (time)
La orden time permite ejecutar una orden del shell y enviar a la salida
de error estándar información sobre el tiempo total consumido así como el
tiempo de CPU consumido. Su formato es
_

¸

time [ opciones ] <comando> [ <argumento > . . . ]
Para más información véase la página de manual de time.
5.3.3. Determinación de la memoria disponible (free)
La orden free permite obtener información sobre la cantidad de me-
moria física y de swap libre y ocupada. Su formato es
_

¸

f ree [ opciones ]
Para más información véase la página de manual de free.
5.3.4. Determinación de la actividad de los usuarios (w)
La orden w permite obtener información sobre qué usuarios están
conectados al sistema y lo que están haciendo. Su formato es
56
5.3. Miscelánea
_

¸

w [ opciones ] [ <usuario >]
Para más información véase la página de manual de w.
5.3.5. Cambio de identidad en la sesión (su)
La orden su permite cambiar la identidad del usuario de una sesión
para continuar operando como un usuario distinto. Uno de los usos más
comunes consiste en usar la orden sin argumentos para continuar ope-
rando como superusuario (root). Para recuperar la identidad previa basta
ejecutar la orden exit. El formato simplificado es
_

¸

su
Para más información véase la página de manual de su.
5.3.6. Reinicialización del terminal (reset)
La orden reset permite reinicializar el terminal, lo que resulta de gran
utilidad en particular cuando se ha cambiado inadvertidamente la página
de códigos de aquel. Su formato más simple es
_

¸

reset
Para más información véase la página de manual de reset.
5.3.7. Montaje de sistemas de archivos (mount)
La orden mount permite incorporar nuevos sistemas de archivo, resi-
dentes en nuevas unidades de almacenamiento, al grafo de directorios del
sistema. Su formato típico es
_

¸

mount −t <tipo > <di sposi ti vo > <di rectori o >
57
5. Otras órdenes útiles
La orden mount sólo puede ser usada por el superusuario.
El argumento <tipo>indica el tipo de sistema de archivo que desea
montarse. Por ejemplo para montar un sistema de archivo de tipo FAT,
típico de los dispositivos de almacenamiento USB formateados en Windows,
<tipo>es vfat.
El argumento <dispositivo>indica el dispositivo físico en el que reside
el sistema de archivo, de acuerdo con la nomenclatura de nuestro sistema
UNIX. En el ejemplo anterior, <dispositivo>sería /dev/sda1.
El argumento <directorio>indica el nombre de un subdirectorio del
sistema UNIX (que debe estar vacío) que constituirá el punto de montaje.
Para más información véase la página de manual de mount.
5.3.8. Desmontaje de sistemas de archivos (umount)
La orden umount permite desmontar un sistema de archivo que haya
sido montado previamente. Su formato es
_

¸

umount <di sposi ti vo >
La orden umount sólo puede ser usada por el superusuario.
El argumento <dispositivo>tiene el mismo significado que en la orden
mount.
Para más información véase la página de manual de umount.
58
6. Control de procesos en UNIX
UNIX es un sistema operativo multiprogramado, lo que significa que
el sistema controla simultáneamente la ejecución de varios procesos.
Así pues, el usuario necesita soporte para poder determinar el estado
y el modo de trabajo de los procesos que tenga en curso.
6.1. Información sobre procesos (ps)
La orden ps sirve para que el usuario pueda obtener información sobre
el estado de los procesos que están en un momento dado bajo control del
sistema operativo. Su formato es
_

¸

ps [ opciones ]
Por defecto, ps selecciona todos los procesos que tienen el mismo
usuario efectivo que el usuario en curso y están asociados al mismo ter-
minal. La información proporcionada es el identificador del proceso, el ter-
minal asociado, el tiempo de CPU consumido y el nombre del ejecutable.
Las opciones más importantes son
-a Seleccionar todos los procesos salvo los líderes de sesión y los no aso-
ciados con un terminal
-e Seleccionar todos los procesos. Se puede escribir -A con el mismo efecto
-f Imprimir información detallada sobre los procesos que interesen
-l Imprimir la información en formato largo
6.2. Control de trabajos (jobs, fg, bg, kill)
Las órdenes de control de trabajos del shell son habitualmente coman-
dos internos, que permiten controlar los trabajos que están activos en una
59
6. Control de procesos en UNIX
determinada sesión: terminar determinado trabajo (matarlo), suspenderlo
(pararlo), reanudar un trabajo suspendido o cambiar su modo de ejecución.
En UNIX existen dos modos de ejecución para cualquier proceso: el
modo de ejecución en primer plano (foreground) y el modo de ejecución en
segundo plano (background).
En cada momento sólo puede haber un proceso en primer plano, y
éste proceso es el que utiliza el teclado del terminal.
La forma de obtener información sobre los procesos dependientes del
shell con indicación del número de trabajo, el estado en que se encuentra
(ejecutando en segundo plano o suspendido) y el nombre del ejecutable, es
utilizar el comando jobs. Su formato es
_

¸

jobs
Para suspender el proceso en primer plano y pasarlo al segundo plano
se utiliza la combinación de teclas “<Ctrl>-Z”. El proceso que queda en
primer plano es el shell.
Un trabajo suspendido puede reanudarse de dos maneras.
Si un trabajo suspendido desea reanudarse de modo que siga ejecu-
tándose en segundo plano el comando a usar es bg. Su formato es
_

¸

bg %<num−trabajo>
donde <num-trabajo> es el número de trabajo proporcionado por jobs.
Si un trabajo suspendido desea reanudarse de modo que siga ejecu-
tándose en primer plano el comando a usar es fg. Su formato es
_

¸

f g %<num−trabajo>
donde <num-trabajo> es el número de trabajo proporcionado por jobs.
Si se ejecuta fg sobre un trabajo que está ejecutándose en segundo
plano, simplemente pasará a ejecutarse en primer plano.
60
6.3. Otras operaciones con procesos
Si lo que se desea es eliminar un trabajo que está ejecutándose en
primer plano se debe usar la combinación de teclas “<Ctrl>-C”.
Si se desea eliminar un trabajo que está ejecutándose en segundo
plano, la orden a usar es kill. Su formato es
_

¸

ki l l %<num−trabajo>
donde <num-trabajo> es el número de trabajo proporcionado por jobs.
Si se desea suspender un trabajo que está ejecutándose en segundo
plano, debe primero pasarse a primer plano mediante fg y después suspen-
derlo usando “<Ctrl>-Z”.
6.3. Otras operaciones con procesos
6.3.1. Espera durante un tiempo determinado (sleep)
El comando sleep permite esperar una determinada cantidad de tiem-
po (por defecto medida en segundos). Su formato es
_

¸

sleep <tiempo>[ <suf i j o >]
donde <tiempo> indica la cantidad de tiempo que se desea esperar. <sufijo>
permite cambiar las unidades de <tiempo>. Se puede usar el sufijo ‘m’ para
indicar minutos, el sufijo ‘h’ para indicar horas y el sufijo ‘d’ para indicar
días.
6.3.2. No eliminar un proceso al cerrar la sesión (nohup)
La orden nohup permite que la orden del shell que le siga inmediata-
mente sea ejecutada de modo que no se elimine el proceso correspondiente
aunque se cierre la sesión del shell. Su formato es
_

¸

nohup <comando> [ <arg > ] . . .
61
6. Control de procesos en UNIX
donde “<comando> [<arg>]...” es la orden que debe continuar ejecutándose
incluso aunque se cierre la sesión actual.
Si no se usa la orden nohup, cualquier orden del shell que no haya
terminado su ejecución cuando se cierra la sesión, será abortada antes de
cerrar la sesión efectivamente.
6.3.3. Envío de señales a un proceso (kill)
Es frecuente que se necesite enviar una notificación a un proceso acer-
ca de algún acontecimiento que interesa que conozca. El mecanismo que
usa UNIX para hacer ese trabajo se denomina envío de señales.
Para enviar una señal a un proceso se utiliza la orden kill. Esta orden
no debe confundirse con el comando interno del shell del mismo nombre
(ver Sección 6.2). Su formato básico es
_

¸

ki l l −s <señal > <pid >. . .
donde <señal> es el número o el nombre de la señal que queremos enviar y
<pid> el identificador del proceso destinatario tal como se obtiene mediante
la orden ps (ver Sección 6.1).
El siguiente formato de la orden kill
_

¸

ki l l −l [ <señal >]
imprime por defecto una lista con los nombres de las señales disponibles en
el sistema (no todos los sistemas UNIX usan las mismas) y sus respectivos
números de señal. Si se indica un nombre de señal, esta forma proporcio-
nará el número correspondiente y viceversa.
62
Parte II.
Uso del compilador C de GNU
63
7. Introducción
El desarrollo práctico de la actividad de programación con un lengua-
je compilado como C, cuando la formación previa de programación se ha
desarrollado utilizando un lenguaje interpretado como Java, Python, etc.,
presenta al alumno un problema añadido al del dominio del correspondien-
te lenguaje. En la nueva situación se necesita manejar una herramienta
de desarrollo que permita controlar las distintas fases que nos conducen a
obtener un programa ejecutable a partir de los archivos correspondientes
escritos en lenguaje fuente.
El proyecto GNU de software libre dispone de un gestor de compila-
ción para los lenguajes C y C++, denominado gcc, que puede ocuparse de
las diferentes fases del proceso de creación de un ejecutable (preproceso,
compilación, ensamblaje, enlace).
En lo que sigue, como es habitual, nos referiremos a gcc como el
compilador C de GNU, aunque la herramienta no realiza sólo la tarea de
compilación estrictamente considerada.
7.1. Obtención de un ejecutable a partir de los archivos
fuente
La obtención de un ejecutable a partir de archivos fuente conlleva la
realización de varias fases en secuencia
1. Preproceso
2. Compilación
3. Ensamblaje
4. Enlace
La herramienta gcc permite desarrollar esas tareas desde cualquier
fase y detenerlas en el punto que nos interese.
65
7. Introducción
Figura 7.1.: Operación de preproceso
Por defecto, gcc supone que los archivos fuente tienen nombre que
utilizan una extensión “c” o bien una extensión “h”. Éstos últimos son de-
nominados archivos de cabecera, por lo cual usaremos la denominación de
archivos fuente sólo para los primeros.
7.1.1. Preproceso
En esta fase, se realizan sobre cada archivo fuente determinadas ope-
raciones de inclusión de texto procedente de distintos archivos de cabecera,
sustitución y desarrollo de macros y selección condicional de texto de pro-
grama para ser compilado posteriormente.
A partir de un archivo fuente, se obtiene así un archivo preprocesado
(unidad de compilación) que, más tarde, será procesado por el compilador
(ver Figura 7.1).
66
7.1. Obtención de un ejecutable a partir de los archivos fuente
Figura 7.2.: Operación de compilación
7.1.2. Compilación
Durante esta fase, los compiladores puros producen código objeto pa-
ra la plataforma hardware sobre la que trabajan, adecuado a la arquitectura
de ésta y al sistema operativo que más tarde tendrá que dar soporte a la
ejecución de la aplicación.
No obstante, la herramienta gcc es una herramienta multiplataforma,
y hace ese trabajo en dos partes. En la fase de compilación, gcc produce có-
digo ensamblador para la plataforma deseada y más tarde, el código objeto
se obtiene mediante el ensamblaje correspondiente.
En la Figura 7.2 se representa la operación de gcc en la fase de com-
pilación.
7.1.3. Ensamblaje
La herramienta gcc produce un módulo objeto a partir de cada módulo
ensamblador obtenido en la fase de compilación (ver Figura 7.3).
7.1.4. Enlace
La fase de enlace realiza la tarea de reunir los distintos módulos objeto
obtenidos de las fases anteriores y los que se deban extraer de las bibliote-
cas disponibles en un programa ejecutable (ver Figura 7.4).
Después de que se han desarrollado estas fases hasta el final, se dis-
pone de un archivo que puede ser ejecutado sin la presencia de los archivos
fuente, sino solamente con el soporte del sistema operativo (supuesto que
67
7. Introducción
Figura 7.3.: Operación de ensamblaje
no se usan bibliotecas de enlace dinámico). En otras palabras, los archivos
fuente no son ya necesarios para ejecutar el programa.
No obstante, mientras una aplicación se encuentra en fase de desa-
rrollo, la corrección de los errores de programación que causan mal fun-
cionamiento del ejecutable, requiere generalmente el uso de una nueva he-
rramienta, denominada depurador, que necesita habitualmente referirse al
código fuente para permitir al programador una traza de la operación del
programa en ejecución. Así pues, el código fuente deja de ser necesario sólo
cuando la aplicación se encuentra en fase de explotación.
68
7.1. Obtención de un ejecutable a partir de los archivos fuente
Figura 7.4.: Operación de enlace
69
8. El compilador C de GNU
8.1. Uso de gcc mediante el shell UNIX
La herramienta gcc puede ejecutarse desde el shell UNIX para realizar
todas o algunas de las fases de creación de un ejecutable a partir de los
archivos fuente que han sido descritas en el Capítulo 7.
La orden del shell para desarrollar este trabajo, en una forma simpli-
ficada, tiene el formato siguiente
_
`
¸

gcc [−c|−S|−E] [−W <warn>] [−std= <standard>] [−g | −ggdb]
[[ −I <dir >] . . . ] [[ −L <dir >] . . . ] [−o <out f i l e >] <i nf i l e > . . .
[−l <l i brary > . . . ]
El formato anterior sólo contiene las opciones más comunes. Para una
información completa sobre las opciones disponibles debe consultarse la
página de manual de gcc, así como la información que sobre gcc puede
obtenerse usando la herramienta info. En todo caso, debe recordarse que,
en general, las opciones no deben agruparse, ya que hay algunas que son
de varios caracteres, y gcc puede interpretar de modo distinto las opciones
escritas separadamente de las agrupadas.
En condiciones normales, gcc pasa de unas fases a otras creando ar-
chivos temporales intermedios que son eliminados al terminar la operación
completa, pero hay opciones que permiten que la comunicación entre las
fases consecutivas se realice por medio de tuberías. Para más información
consúltese la página de manual de gcc.
Las tareas que gcc debe realizar dependen de los archivos que recibe
como entrada (denotados en el formato de la orden como <infile> ...).
gcc reconoce los distintos tipos de archivo por la extensión utilizada en sus
nombres. Las alternativas principales son las siguientes
.c Esta extensión se usa para los archivos fuente en lenguaje C
71
8. El compilador C de GNU
.h Esta extensión se usa para los archivos de cabecera
.i Esta extensión se usa para los archivos fuente que no necesitan ser pre-
procesados
.s Esta extensión se usa para los archivos en ensamblador
.o Esta extensión se usa para los archivos objeto producidos por el ensam-
blador
Comentaremos ahora el uso de las opciones más frecuentes.
8.2. Opciones de uso frecuente
8.2.1. Opciones globales (-c, -S, -E, -o <outfile>)
La opción -c se usa para indicar que la tarea de gcc debe detenerse
después de realizada la operación de ensamblaje, es decir cuando se ha-
yan producido los archivos objeto correspondientes, supuesto que no haya
habido errores.
La opción -S se usa para indicar que la tarea de gcc debe detenerse
después de realizada la operación de compilación, es decir cuando se ha-
yan producido los archivos ensamblador correspondientes, supuesto que
no haya habido errores.
La opción -E se usa para indicar que la tarea de gcc debe detenerse
después de realizada la operación de preproceso, es decir cuando se hayan
producido los archivos preprocesados correspondientes, supuesto que no
haya habido errores.
Si no se usa ninguna de las opciones anteriores, se intentará construir
un ejecutable a partir de los archivos de entrada.
La opción -o, seguida de un nombre de archivo, especifica el nombre
que se desea para el archivo de salida. Esto se aplica independientemente
de la clase de salida producida, tanto si es un archivo ejecutable como si es
un archivo objeto, un archivo ensamblador o un archivo preprocesado.
Si se especifica la opción -o cuando hay varios archivos de entrada o
cuando se está construyendo un ejecutable, todos los archivos de entrada
serán procesados de una vez (en particular se compilarán todos los archivos
fuente) y se generará un único archivo objeto o un ejecutable.
72
8.2. Opciones de uso frecuente
Si no se especifica la opción -o, y se está construyendo un ejecutable,
el nombre de éste será a.out. Para los demás archivos de salida, a partir de
una entrada source.<x> con una extensión .<x>, se producirá un archivo
de salida con nombre source y extensión .o para los archivos objeto y .s
para los archivos ensamblador. Los archivos preprocesados se enviarán a
la salida estándar.
8.2.2. Opciones que controlan los mensajes de aviso (-W <warn>)
La opción -W permite controlar qué clases de mensajes de aviso (war-
ning) se quieren recibir durante la operación de gcc.
Para nuestro uso la única opción que utilizaremos será de la forma
-Wall, lo que permitirá que gcc nos proporcione todos los avisos de cual-
quier clase que se produzcan durante la operación de gcc. Para más infor-
mación consúltese la página de manual de gcc.
8.2.3. Opciones que controlan el estándar utilizado
(-std = <standard>)
gcc permite utilizar varios dialectos del lenguaje C. La opción -std
permite especificar el tipo de dialecto usado. Si <standard> se escribe como
c89 se usará el estándar iso9899:1990. Esa será la única especificación que
nosotros usaremos. Dicha especificación admite una forma simplificada: -
ansi. Para más información sobre otros estándares consúltese la página de
manual de gcc.
8.2.4. Opciones que controlan la incorporación de código para
depuración (-g, -ggdb)
Para incorporar a un ejecutable el código que permite luego utilizar
un depurador para investigar su posible mal funcionamiento, puede usarse
la opción -g si se va a usar un depurador genérico. La opción -ggdb permi-
te incorporar código de depuración específicamente asociado al depurador
GNU, denominado gdb. Nosotros, para el trabajo habitual siempre incluire-
mos una de estas opciones, con objeto de que luego, nuestros ejecutables
se puedan siempre depurar. Para más información consúltese la página de
manual de gcc.
73
8. El compilador C de GNU
8.2.5. Opciones que describen los directorios de residencia de las
cabeceras (-I<dir>)
gcc usa una cadena de directorios, que depende de la configuración
de la herramienta, para buscar los archivos de cabecera necesitados en las
operaciones de preproceso. En particular, las cabeceras de la biblioteca es-
tándar se encuentran en directorios incluidos en esta cadena de directorios,
y para ellas no es necesario usar la opción -I. Si se usa la opción -I seguida
de un nombre de directorio, se añade ese directorio en cabeza de la lista
de búsqueda. Si el directorio ya figura en la lista de búsqueda, la opción se
ignora.
Por lo tanto, cuando nosotros queremos manejar alguna cabecera no
estándar (por ejemplo nuestra) en nuestro código fuente, con la directi-
va #include <cabecera.h> o bien #include ”cabecera.h”, necesitamos
generalmente usar la opción -I para indicar el directorio en que deben bus-
carse esas cabeceras.
8.2.6. Opciones que describen los directorios de residencia de las
bibliotecas (-L<dir>)
gcc usa una cadena de directorios, que depende de la configuración
de la herramienta, para buscar las bibliotecas necesitadas en la operación
de enlace. Si se usa la opción -L seguida de un nombre de directorio, se
añade ese directorio a la lista de búsqueda.
Así pues, el esquema de búsqueda de las bibliotecas necesarias en la
fase de enlace sigue una pauta análoga a la de las cabeceras en la fase de
preproceso
8.2.7. Opciones que describen las bibliotecas que deben usarse en
el proceso de enlace (-l<library>)
Cuando al intentar construir un ejecutable (durante el proceso de en-
lace) gcc encuentra que ciertas funciones utilizadas no se encuentran defi-
nidas en los archivos objeto que participan en la operación, busca por de-
fecto esas funciones en la biblioteca estándar. Si las encuentra, las extrae
de la biblioteca y continua el proceso de enlace. En otro caso la herramienta
informa del error correspondiente y el ejecutable no llega a ser generado.
74
8.2. Opciones de uso frecuente
Si las funciones que necesitamos no pertenecen a la biblioteca están-
dar, o incluso, en la configuración más corriente, son funciones matemáti-
cas de la biblioteca estándar, debemos indicar a gcc en qué biblioteca debe
buscar. La opción -l, seguida de un nombre de biblioteca permite pasar a
gcc esa información. Para indicar a gcc en qué directorio buscar la biblio-
teca véase la Subsección 8.2.6.
La biblioteca matemática estándar para gcc se denomina libm.a (pa-
ra la versión de enlace estático) o bien libm.so (para la versión de enlace
dinámico, que es la utilizada normalmente), por lo cual, cuando se necesi-
ta utilizar alguna función de cálculo matemático de la biblioteca estándar,
nuestro código debe enlazarse con los módulos de esta biblioteca usando
la opción -lm (se usa el nombre de la biblioteca sin el prefijo lib y sin la
extensión).
75
Parte III.
El depurador gdb de GNU
77
9. Introducción
Durante el desarrollo de cualquier aplicación, con independencia del
lenguaje que se use para ello, se producen errores de programación que
pueden ser detectados en diferentes fases del ciclo de trabajo.
Cuando, como es nuestro caso, estamos interesados en el ciclo de
desarrollo utilizado con un lenguaje compilado como C, esos errores pue-
den producirse en cualquier fase del proceso de generación del ejecutable
(errores en tiempo de compilación o enlace) o bien se producen cuando se
ejecuta la aplicación (errores en tiempo de ejecución).
9.1. Un programa ejemplo
Para distinguir cómo se presentan y en qué momento se detectan los
diferentes tipos de errores utilizaremos un programa ejemplo.
Su especificación será la siguiente: El programa debe obtener de la
línea de órdenes una serie de opciones seguida de una serie de argumentos
suplementarios. Las opciones son cadenas de caracteres que comienzan por
el carácter “-”. Los argumentos suplementarios son cadenas de caracteres
cualesquiera, de las cuales la primera no puede comenzar por el carácter
“-”.
El programa debe enviar a la salida estándar una línea con las op-
ciones que haya en la línea de órdenes separadas por espacios en blanco.
Después debe producir una línea para cada argumento suplementario don-
de figuren ese argumento y la correspondiente longitud de la cadena de
caracteres.
Supongamos que nuestra solución inicial para el programa ejemplo
sea la descrita en el Listado 9.1.
El compilador detecta los errores sintácticos de nuestro programa en
tiempo de compilación. Cuando intentamos generar el correspondiente eje-
cutable obtenemos el resultado que puede verse en la Figura 9.1.
79
9. Introducción
1
#include <stdi o . h>
3 #include <stri ng . h>
5 void PrintInforme ( const char ∗cadena ) ;
7 int main( int argc , char ∗argv [ ] )
{
9
pri nt f ( "Las opciones son: " ) ;
11 while(−−argc > 0 && ∗++argv [ 0] == ’-’)
pri nt f ( "%s " , ∗++argv ) ;
13 }
15 pri nt f ( "\n" ) ;
17 pri nt f ( "Otros argumentos de la linea de ordenes\n" ) ;
while( ∗ argv ++) {
19 PrintInforme ( ∗ argv ) ;
argv++;
21 }
23 return 0;
}
Listado 9.1: Programa ejemplo. Versión 0a
80
9.1. Un programa ejemplo
Figura 9.1.: Listado de errores de compilación
Como puede observarse se obtiene un informe de errores que esen-
cialmente nos indica que se ha alcanzado el final de la función main sin
haber devuelto el valor entero que indica su cabecera, y después hay más
texto que causa otros errores.
El error básico consiste en que en el primer bucle while el cuerpo
está delimitado por una llave de fin de bloque (carácter “}” en la línea 13
del Listado 9.1) pero no la correspondiente de comienzo. En realidad la llave
de fin de bloque es innecesaria, puesto que el cuerpo del bucle sólo consiste
en una sentencia.
Si corregimos el error eliminando ese delimitador, tendríamos nuestro
programa en la forma descrita en el Listado 9.2.
Cuando intentamos generar el correspondiente ejecutable obtenemos
el resultado que puede verse en la Figura 9.2.
El informe ahora contiene errores que no son de sintaxis. Es el en-
lazador el que nos informa de que no es capaz de encontrar la función
PrintInforme. Efectivamente, nuestro programa no incorpora una defini-
ción de la función citada, sino sólo la declaración de su prototipo.
Si incorporamos la definición de la función PrintInforme, nuestro
programa podría quedar en la forma descrita en el Listado 9.3.
81
9. Introducción
1
#include <stdi o . h>
3 #include <stri ng . h>
5 void PrintInforme ( const char ∗cadena ) ;
7 int main( int argc , char ∗argv [ ] )
{
9
pri nt f ( "Las opciones son: " ) ;
11 while(−−argc > 0 && ∗++argv [ 0] == ’-’)
pri nt f ( "%s " , ∗++argv ) ;
13
15 pri nt f ( "\n" ) ;
17 pri nt f ( "Otros argumentos de la linea de ordenes\n" ) ;
while( ∗ argv ++) {
19 PrintInforme ( ∗ argv ) ;
argv++;
21 }
23 return 0;
}
Listado 9.2: Programa ejemplo. Versión 0b
82
9.1. Un programa ejemplo
1
#include <stdi o . h>
3 #include <stri ng . h>
5 void PrintInforme ( const char ∗cadena ) ;
7 int main( int argc , char ∗argv [ ] )
{
9
pri nt f ( "Las opciones son: " ) ;
11 while(−−argc > 0 && ∗++argv [ 0] == ’-’)
pri nt f ( "%s " , ∗++argv ) ;
13
pri nt f ( "\n" ) ;
15
pri nt f ( "Otros argumentos de la linea de ordenes\n" ) ;
17 while( ∗ argv ++) {
PrintInforme ( ∗ argv ) ;
19 argv++;
}
21
return 0;
23 }
25 void PrintInforme ( const char∗ cadena)
{
27 int longitud ;
29 longitud = strl en ( cadena ) ;
pri nt f ( "%s\tlongitud= %d\n" , cadena, longitud ) ;
31
return;
33 }
Listado 9.3: Programa ejemplo. Versión 1
83
9. Introducción
Figura 9.2.: Listado de errores de enlace
Ahora, nuestro intento de generar el ejecutable tiene éxito sin que se
produzca ningún error en las fases de compilación y enlace.
No obstante, si intentamos ejecutar el programa producido con la or-
den
_

¸

./ejemplo−gdb1 −al −c − −menos una −−tres " dos y cuatro "
obtenemos la salida que se puede observar en la Figura 9.3.
Como puede observarse, nuestro programa no produce la salida es-
perada, pero ahora, para encontrar los errores que pueda haber (errores
en tiempo de ejecución), no disponemos de un informe de errores como en
los casos anteriores, sino que sabemos que nuestro programa es incorrecto
porque la salida que produce no es la esperada.
9.2. Depuración de un ejecutable
Podemos intentar localizar los errores de nuestro programa que hacen
que no produzca la salida esperada inspeccionando el código fuente, pero
84
9.2. Depuración de un ejecutable
Figura 9.3.: Salida de la versión 1
en un programa medianamente complejo eso resulta inviable.
Una práctica frecuente consiste en insertar sentencias de escritura
que permitan seguir la traza de ejecución del programa, con objeto de deter-
minar dónde es incorrecta su lógica. Esa solución es inadecuada, al menos
porque eso introduce sentencias innecesarias para la lógica del programa,
que más tarde habrá que eliminar, o al menos, evitar que se ejecuten cuan-
do el programa se ponga en producción. Ello tiene el riesgo de que nuestro
programa deje de funcionar como lo hacía con las sentencias auxiliares que
nos hayan permitido corregirlo.
Un depurador simbólico (abreviadamente un depurador) es una he-
rramienta de desarrollo que permite observar, paso a paso si es necesario,
la ejecución de un programa, para que el programador pueda determinar
dónde se encuentran los fallos del programa que hacen que se comporte en
tiempo de ejecución de una manera no acorde con su especificación. Pa-
ra realizar esa tarea, el depurador no necesita que se modifique el código
fuente del programa, lo cual permite que, después de corregidos los errores,
nuestro programa pueda ponerse en producción sin otros cambios.
En las siguientes páginas se describirá el uso elemental del depura-
dor gdb de GNU para resolver el problema de comprobar la lógica de un
85
9. Introducción
programa, utilizando para ello el mismo ejemplo que hemos usado hasta
aquí.
86
10. El depurador gdb de GNU
El funcionamiento de un depurador simbólico se basa en incorporar al
ejecutable referencias a las sentencias del código fuente durante las fases
de compilación y enlace. Para ello es imprescindible que el compilador y el
enlazador hagan ese trabajo, lo cual, en el caso del sistema de desarrollo
GNU se realiza utilizando la opción -g en la línea de órdenes del gestor de
compilación y enlace gcc (véase la Subsección 8.2.4). En nuestro caso, y
dado que utilizaremos el depurador gdb de GNU, podemos utilizar la opción
-ggdb para poder usar las características particulares de dicho depurador.
Por tanto, como regla general, durante el desarrollo de cualquier apli-
cación, deberá utilizarse sistemáticamente la opción -ggdb (o bien -g) en
las compilaciones de los archivos fuente y en los procesos de enlace que
conducen a la generación de nuestros ejecutables, con objeto de poder uti-
lizar el depurador para inspeccionar la ejecución de nuestros programas si
ello resulta necesario.
Por otra parte, debe señalarse que, para que el depurador pueda reali-
zar su trabajo, es necesario que aquel pueda acceder a los archivos fuente,
donde reside el código que ha sido compilado y luego enlazado para for-
mar el ejecutable, porque, como ya se ha dicho, el ejecutable, cuando se ha
construido utilizando la opción de depuración, sólo contiene referencias a
las sentencias fuente, y no las sentencias mismas. Por tanto, no será posible
inspeccionar la traza de ejecución de las funciones de nuestro programa de
las que no dispongamos del código fuente. No obstante, ello no debe causar
problemas especiales, puesto que el código que supuestamente debemos
someter a depuración es el producido por nosotros, del cual, naturalmente,
sí tendremos el código fuente.
Para los ejemplos que utilizaremos en el presente capítulo supondre-
mos que nuestro directorio de trabajo actual es el que contiene nuestro
ejecutable ejemplo y que los archivos fuente correspondientes se encuen-
tran situados en el directorio en el que se encontraban cuando se generó
aquel. En particular supondremos que nuestro archivo fuente de partida
será ejemplo-gdb1.c correspondiente al Listado 9.3, que estará situado
87
10. El depurador gdb de GNU
en el directorio <HOME>/gdb-ejemplo, el cual será nuestro directorio de
trabajo.
Nuestro ejecutable se obtendrá mediante la orden
_

¸

gcc −Wall −ansi −ggdb −o ejemplo−gdb1 ejemplo−gdb1. c
lo cual dará lugar a que dicho ejecutable (denominado ejemplo-gdb1), que
como ya se vio en la Sección 9.1 se genera sin errores de compilación ni
enlace, contenga el código de depuración correspondiente.
Como también se vio en la Figura 9.3, la salida del programa no es
la esperada, por lo cual, se procederá ahora a usar el depurador gdb para
seguir la traza de ejecución con esos mismos datos en la línea de órdenes,
con objeto de determinar en qué consisten los posibles errores.
10.1. Una sesión de depuración con gdb
La forma más habitual de comenzar una sesión de depuración es invo-
car el depurador en la línea de órdenes con un argumento, que es el nombre
del ejecutable que debe depurarse. En nuestro caso sería
_

¸

gdb ejemplo−gdb1
lo cual producirá una pantalla de presentación del depurador tal como se
observa en la Figura 10.1, en la que se puede observar que el prompt ahora
ha cambiado de forma (es (gdb)) para indicarnos que quien nos atiende
ahora es el depurador y no el shell como antes.
Las órdenes que se den a partir de ahora se envían al depurador, y
cuando hayamos terminado la sesión de depuración deberemos cerrarla
(con la orden q) para volver al shell normal.
10.1.1. Argumentos de la línea de órdenes
Nuestro ejecutable recibía argumentos a través de la línea de órdenes.
Antes de intentar ejecutarlo con el depurador debemos informar a éste de
88
10.1. Una sesión de depuración con gdb
Figura 10.1.: Pantalla de presentación del depurador gdb
cuáles son los argumentos que debe pasar a nuestro ejecutable. La orden
correspondiente es set args
1
. Su formato es
_

¸

set args [ <argumento> . . . ]
donde la lista de argumentos es la que deseemos pasar luego al programa
en depuración (excluido el nombre del programa).
En nuestro caso utilizaremos los mismos que causaban los errores
observados en la Figura 9.3.
_

¸

set args −al −c − −menos una −−tres " dos y cuatro "
con lo cual tendremos en el terminal la salida indicada en la Figura 10.2.
1
Las órdenes utilizadas en este capítulo para el depurador gdb se han escrito generalmente
en formato abreviado. Para obtener información sobre los nombres completos de las
citadas órdenes consúltese la página de información del depurador (ver al final de la
Subsección 10.1.4)
89
10. El depurador gdb de GNU
Figura 10.2.: Paso de los argumentos de la línea de órdenes
10.1.2. Puntos de ruptura (breakpoints)
La siguiente tarea es decirle al depurador en qué lugar del programa
queremos que detenga la ejecución normal para que nosotros podamos ins-
peccionar lo que sucede a partir de ese punto. La orden correspondiente es
br y va seguida o bien de un nombre de función, o bien de un nombre de
archivo fuente seguido del carácter ‘:’ y de un número de línea. En el primer
caso el depurador detendrá el flujo de ejecución del programa en cuanto se
realice una llamada a la función antes de ejecutar su primera sentencia.
En el segundo caso el depurador detendrá el flujo de ejecución en cuanto
corresponda ejecutar la línea del archivo citado o la primera ejecutable que
haya después de ella. En ambos casos la ejecución se detiene sin haber
ejecutado la sentencia correspondiente.
En nuestro caso, queremos trazar la ejecución desde el principio, así
que lo más cómodo es usar la primera forma,
_

¸

br main
con lo que obtendremos en el terminal el resultado indicado en la Figu-
ra 10.3.
90
10.1. Una sesión de depuración con gdb
Figura 10.3.: Punto de ruptura
En ella podemos observar que nuestro punto de ruptura se encuentra
situado en la línea 10 del archivo ejemplo-gdb1.c.
10.1.3. Ejecución del programa
Ahora debemos ordenar al depurador que ejecute el programa con las
condiciones anteriormente establecidas. Para ello se utiliza la orden run.
Su formato es el siguiente
_

¸

run [ <argumento> . . . ]
En la lista de argumentos se pueden pasar los argumentos de la línea
de órdenes con los que se desee ejecutar el programa si no se han especi-
ficado antes mediante set args (véase la Subsección 10.1.1). Al igual que
con set args, en esos argumentos no se especifica de nuevo el nombre del
ejecutable.
En nuestro caso ejecutaremos
91
10. El depurador gdb de GNU
Figura 10.4.: Ejecución hasta el punto de ruptura
_

¸

run
La respuesta del depurador es la que se observa en la Figura 10.4.
Como puede verse, la ejecución se ha detenido antes de ejecutar la primera
sentencia de la función main, que se encuentra en la línea 10 del archivo
fuente. En adelante nos referiremos a las sentencias por su número de
línea.
Desde ahora, las órdenes al depurador controlarán la ejecución del
programa. Típicamente necesitaríamos ahora ejecutar la sentencia número
10. La ejecución de la sentencia inmediata cuando el programa está dete-
nido se logra mediante la orden n
_

¸

n
lo cual, en nuestro caso produce el resultado que se observa en la Figu-
ra 10.5, donde podemos ver que se ha enviado la cadena de caracteres “Las
opciones son: ” al stream de salida estándar (no se ha escrito en una
92
10.1. Una sesión de depuración con gdb
Figura 10.5.: Ejecución de la sentencia 10
línea del terminal porque, en el actual modo de trabajo de éste, no se escri-
birá nada en él mientras no se complete la línea, es decir, mientras no se
escriba un carácter de nueva línea).
Además observamos que la sentencia inmediata que será ejecutada (la
número 11) es la cabecera del bucle while que tiene por objeto obtener de
la línea de órdenes todas los argumentos que sean opciones (todos los que
comienzan por el carácter “-” hasta el primero que no comience por dicho
carácter exclusive).
Para ejecutar la sentencia 11 podemos usar de nuevo la orden n como
antes o bien pulsar <RET>, lo cual podrá ser utilizado en cualquier momen-
to para repetir la última orden emitida. Nuestro resultado puede verse en
la Figura 10.6.
Lo que observamos es que la siguiente sentencia a ejecutar (la 14) está
ya fuera del bucle de obtención de las opciones. Cuando la ejecutemos se
escribirá la línea correspondiente en el terminal, con el rótulo de la senten-
cia 10 seguido del carácter de nueva línea, puesto que no se ha encontrado
ninguna opción. Por otra parte, la siguiente sentencia a ejecutar será la 16.
Efectivamente, eso es lo que observamos en la Figura 10.7.
93
10. El depurador gdb de GNU
Figura 10.6.: Ejecución de la sentencia 11
Figura 10.7.: Ejecución de la sentencia 14
94
10.1. Una sesión de depuración con gdb
Es evidente el mal comportamiento del programa. Con nuestros argu-
mentos en la línea de órdenes hay varias opciones que no han sido locali-
zadas por el bucle while de la línea 11, que, en consecuencia, está progra-
mado incorrectamente. No se ha ejecutado el cuerpo del bucle ni una sola
vez, así que el problema está en la expresión de control del bucle. En ella
se usa un operador and lógico, en el que el primer operando lleva la cuen-
ta de argumentos procesados en la línea de órdenes, en la que se empieza
descontando el primero para no tener en cuenta el nombre del programa
y resultará falsa cuando se acaben los argumentos de la línea de órdenes.
Esa condición parcial es correcta.
El otro operando intenta determinar si el primer carácter del argu-
mento correspondiente de la línea de órdenes es un signo “-”. Si es así,
se trata de una opción, que se enviará a la salida estándar y se iterará de
nuevo mientras en los nuevos argumentos el primer carácter siga siendo un
signo “-”.
Sin embargo la expresión
*
++argv[0] no tiene el valor del primer ca-
rácter del argumento de la línea de órdenes en cada caso. En efecto: ini-
cialmente argv[0] es un puntero al primer carácter del primer argumento
de la línea de órdenes (el nombre del programa). ++argv[0] es un puntero
al segundo carácter de ese argumento, no al primer carácter del segundo
argumento, que es lo que necesitamos, así que
*
++argv[0] es el segundo
carácter del nombre del programa, que en nuestro caso es un carácter “h”
(el nombre de nuestro programa es
/home/alumno/gdb-ejemplo/ejemplo-gdb1).
Ahora debemos corregir ese error. La expresión correcta es
(
*
++argv)[0], la cual incrementa argv para apuntar al siguiente argu-
mento de la línea de órdenes (inicialmente el segundo) y toma el valor del
primer carácter. Para ello debemos salir del depurador mediante la orden q.
_

¸

q
La salida es la observada en la Figura 10.8, en la que el depurador pide
confirmación, puesto que el programa sigue en ejecución y posteriormente,
termina y el shell normal vuelve a tomar el control.
Ahora debemos modificar el código fuente en la línea 11, volver a ge-
nerar el ejecutable y ejecutar de nuevo para ver si nuestro problema se ha
solucionado. La nueva versión del código puede verse en el Listado 10.1.
95
10. El depurador gdb de GNU
1
#include <stdi o . h>
3 #include <stri ng . h>
5 void PrintInforme ( const char ∗cadena ) ;
7 int main( int argc , char ∗argv [ ] )
{
9
pri nt f ( "Las opciones son: " ) ;
11 while(−−argc > 0 && (∗++argv ) [ 0] == ’-’)
pri nt f ( "%s " , ∗++argv ) ;
13
pri nt f ( "\n" ) ;
15
pri nt f ( "Otros argumentos de la linea de ordenes\n" ) ;
17 while( ∗ argv ++) {
PrintInforme ( ∗ argv ) ;
19 argv++;
}
21
return 0;
23 }
25 void PrintInforme ( const char∗ cadena)
{
27 int longitud ;
29 longitud = strl en ( cadena ) ;
pri nt f ( "%s\tlongitud= %d\n" , cadena, longitud ) ;
31
return;
33 }
Listado 10.1: Programa ejemplo. Versión 2
96
10.1. Una sesión de depuración con gdb
Figura 10.8.: Orden de salida del depurador
El resultado de la generación del nuevo ejecutable y la ejecución con
nuestros datos puede verse en la Figura 10.9.
Como puede observarse, no hay errores de compilación ni enlace pero
la salida de la versión 2 sigue siendo incorrecta. Las opciones no se captu-
ran bien y al listar el resto de los argumentos se produce una excepción de
memoria.
En la Figura 10.10 podemos ver la situación de partida para seguir la
traza de ejecución de la versión 2 del programa desde la función main y con
los mismos argumentos que en el caso de la versión 1.
Si ejecutamos la orden n (dos veces) para ejecutar las sentencias de
las líneas 10 y 11, podemos ver que ahora sí ejecutamos el cuerpo del bucle.
En la primera iteración se deberá imprimir la primera opción. No obstante,
lo que realmente se va a imprimir es el valor de la expresión
*
++argv.
Podemos decirle al depurador que nos informe del valor de la expresión
ejecutando la orden p con un operando que es la expresión a evaluar.
_

¸

p ∗++argv
97
10. El depurador gdb de GNU
Figura 10.9.: Salida de la versión 2
Figura 10.10.: Ejecución del depurador con la versión 2 del programa
98
10.1. Una sesión de depuración con gdb
Figura 10.11.: Impresión del valor de una expresión
El resultado puede verse en la Figura 10.11. Puede observarse que la
orden p permite obtener el valor de una expresión, y en particular, que si
esa expresión es un puntero a una cadena de caracteres, lo que se obtiene
es la cadena completa.
Ahora podemos ver por qué la salida de nuestro programa es inco-
rrecta. La primera iteración del bucle de captura de las opciones no envía
a la salida estándar la primera opción, sino la segunda directamente. Ello
se debe a que la expresión utilizada para imprimir (
*
++argv) es incorrecta.
Justo antes de ejecutar la sentencia 12, argv apunta al puntero que a su
vez apunta a la cadena que contiene la opción, así que la expresión que
debería imprimirse mediante printf es
*
argv omitiendo el operador ++.
Debemos, por tanto, salir del depurador, corregir el programa fuen-
te, regenerar el ejecutable y ejecutar de nuevo para ver el comportamiento
ahora. El código fuente corregido puede verse en el Listado 10.2.
La salida de la nueva versión puede verse en la Figura 10.12. Observa-
mos que no hay errores de compilación ni enlace y que ahora las opciones
se obtienen correctamente, pero al listar los argumentos suplementarios
(los que van a continuación de las opciones), el programa sigue fallando. No
se listan más que algunos y al final se obtiene una excepción de memoria.
99
10. El depurador gdb de GNU
1
#include <stdi o . h>
3 #include <stri ng . h>
5 void PrintInforme ( const char ∗cadena ) ;
7 int main( int argc , char ∗argv [ ] )
{
9
pri nt f ( "Las opciones son: " ) ;
11 while(−−argc > 0 && (∗++argv ) [ 0] == ’-’)
pri nt f ( "%s " , ∗argv ) ;
13
pri nt f ( "\n" ) ;
15
pri nt f ( "Otros argumentos de la linea de ordenes\n" ) ;
17 while( ∗ argv ++) {
PrintInforme ( ∗ argv ) ;
19 argv++;
}
21
return 0;
23 }
25 void PrintInforme ( const char∗ cadena)
{
27 int longitud ;
29 longitud = strl en ( cadena ) ;
pri nt f ( "%s\tlongitud= %d\n" , cadena, longitud ) ;
31
return;
33 }
Listado 10.2: Programa ejemplo. Versión 3
100
10.1. Una sesión de depuración con gdb
Figura 10.12.: Salida de la versión 3
Volveremos por tanto a ejecutar la nueva versión del programa bajo
control del depurador con los mismos datos que en los casos anteriores.
Nuestro punto de ruptura lo situaremos ahora al comienzo del segundo bu-
cle while (línea 17 del archivo fuente), que es el que debe imprimir el resto
de los argumentos y funciona incorrectamente. Al ejecutar, obtendremos la
situación mostrada en la Figura 10.13.
Podemos observar que se ha producido la impresión correcta de la
línea del informe de salida que contiene las opciones de la línea de órdenes
y la impresión del rótulo de cabecera del informe de los otros argumentos.
Después el depurador ha detenido la ejecución al comienzo del bucle while
que debe imprimir esos argumentos.
Podemos ver cuál es el siguiente argumento a procesar imprimiendo
el valor de
*
argv y después ejecutar la línea 17, lo cual nos llevará a dete-
nernos antes de ejecutar la primera iteración del bucle. La situación sería
la de la Figura 10.14.
Ahora no nos interesa usar la orden n para que el depurador eje-
cute la sentencia 18. Ello ejecutaría directamente la llamada a la función
PrintInforme y la ejecución se detendría antes de ejecutarse la sentencia
19. Lo que nos interesa es que la ejecución continúe paso a paso dentro de
101
10. El depurador gdb de GNU
Figura 10.13.: Ejecución del depurador con la versión 3 del programa
Figura 10.14.: Primera iteración del bucle de impresión de argumentos
102
10.1. Una sesión de depuración con gdb
Figura 10.15.: Traza de una función mediante la orden s
la función PrintInforme. Esto puede conseguirse mediante la orden s.
_

¸

s
Si ejecutamos esa orden, el resultado que obtendremos es el mostra-
do en la Figura 10.15. Podemos observar que el depurador nos informa del
valor del argumento que se pasa a la función. En nuestro caso, la cadena
“--tres”. Pero esto no es lo que esperábamos. El argumento que debería
haberse pasado es la cadena “una”. Ahora está claro por qué obtenemos
“--tres” en la primera línea del informe de argumentos en nuestra ejecu-
ción (errónea) del programa (ver Figura 10.12).
Si ahora ejecutamos la orden n, podemos almacenar en longitud la
longitud de la cadena, y luego, mediante la orden p podemos ver el valor de
esa longitud. Si ahora ejecutamos n repetidamente, obtendremos la prime-
ra línea del informe, luego se ejecutará la sentencia de final de la función
PrintInforme y se volverá a la función main en la línea 19; después se
vuelve de nuevo a la cabecera del bucle. Todo ello puede verse en la Figu-
ra 10.16.
103
10. El depurador gdb de GNU
Figura 10.16.: Primera ejecución de la función PrintInforme
La función PrintInforme hace su trabajo correctamente. Como he-
mos visto, lo que sucede es que se le pasa un argumento incorrecto. En
la cabecera del bucle se comprueba si
*
argv no es nulo para ejecutar el
cuerpo del bucle, y además se incrementa argv, con ánimo de utilizar el si-
guiente argumento de la línea de órdenes en la iteración siguiente. Pero esto
último es incorrecto, porque ello afecta a la llamada a PrintInforme. Es
necesario modificar la sentencia 17 para no incrementar argv. El cambio
de argumento de la línea de órdenes lo hará la sentencia 19.
Por otra parte este error explica la excepción de memoria que obtene-
mos al final. Al incrementar indebidamente argv (de hecho lo incrementa-
mos dos veces una en la línea 17 y otra en la 19) nos pasamos del final de
la tabla de punteros sin detectar el puntero nulo que señala su fin.
Como siempre, abortaremos la ejecución del depurador, corregiremos
el error en el programa fuente, reconstruiremos el ejecutable y realizaremos
la ejecución de prueba. El código corregido puede verse en el Listado 10.3.
La salida de ejecución puede verse en la Figura 10.17.
Ahora la salida obtenida es correcta para los datos utilizados y no
se produce la excepción de memoria que antes originaba la terminación
anormal de la ejecución del programa.
104
10.1. Una sesión de depuración con gdb
1
#include <stdi o . h>
3 #include <stri ng . h>
5 void PrintInforme ( const char ∗cadena ) ;
7 int main( int argc , char ∗argv [ ] )
{
9
pri nt f ( "Las opciones son: " ) ;
11 while(−−argc > 0 && (∗++argv ) [ 0] == ’-’)
pri nt f ( "%s " , ∗argv ) ;
13
pri nt f ( "\n" ) ;
15
pri nt f ( "Otros argumentos de la linea de ordenes\n" ) ;
17 while( ∗ argv ) {
PrintInforme ( ∗ argv ) ;
19 argv++;
}
21
return 0;
23 }
25 void PrintInforme ( const char∗ cadena)
{
27 int longitud ;
29 longitud = strl en ( cadena ) ;
pri nt f ( "%s\tlongitud= %d\n" , cadena, longitud ) ;
31
return;
33 }
Listado 10.3: Programa ejemplo. Versión final
105
10. El depurador gdb de GNU
Figura 10.17.: Salida de la versión final
10.1.4. Otras órdenes útiles del depurador gdb
Durante la ejecución de un programa bajo control del depurador gdb
pueden utilizarse otras órdenes que pueden ser de utilidad en otras situa-
ciones. Una de las más comunes es la orden l. Su formato es
_

¸

l [ −][ <numero−de−l i nea >]
La orden l cuando se usa sin argumentos opcionales, lista las líneas
del archivo fuente en torno a la sentencia que vaya a ejecutarse (típicamente
cuando se usa por primera vez un intervalo de 10 líneas en total). Ello sirve
para podernos situar en el código fuente y saber qué sentencias hay antes
o después de la actual. Si se ejecuta l repetidamente se listan las líneas en
torno a la última listada la vez anterior.
Si se especifica <numero-de-linea> se listan las líneas en torno a la
linea indicada, no la que toca ejecutar. Si se especifica el signo “-” se listan
las líneas previas a la actual o a la que se indique.
Para más información sobre las órdenes del depurador gdb consúltese
la página de información sobre el depurador
106
10.2. Sugerencias
_

¸

i nf o gdb
en la línea de órdenes del shell normal. La página de manual normal de gdb
contiene un información limitada.
10.2. Sugerencias
La solución discutida en la Subsección 10.1.3 para el programa ejem-
plo en su versión final daba una salida correcta para la línea de órdenes
utilizada, pero sigue siendo una solución incorrecta. Véase por ejemplo su
comportamiento cuando se utiliza una línea de órdenes vacía.
Se recomienda al lector que utilice el depurador para ver qué es lo
que sucede y corrija el problema como ejercicio para utilizar las técnicas
anteriores.
107
Parte IV.
La utilidad make de GNU
109
11. Introducción
En el ámbito del desarrollo y el mantenimiento de software, la realiza-
ción de tareas complejas basadas en el uso de múltiples recursos que son
generados separadamente, es un proceso que, cuando se realiza manual-
mente, es tedioso por lo repetitivo y muy propenso a errores.
Tomemos como ejemplo la construcción de uno o varios ejecutables a
partir de los archivos fuente en un ciclo de edición-compilación-enlace. Las
operaciones de edición de los archivos fuente se realizan de modo interac-
tivo y producen los recursos de partida para las otras dos fases del ciclo de
construcción, que, a su vez, se desarrollan en modo batch.
Si la compilación de los archivos fuente se intenta hacer desde la lí-
nea de órdenes del shell, obtendremos frecuentemente informes de error
correspondientes a los posibles errores sintácticos que haya en los archi-
vos fuente, lo cual nos llevará a intentar corregirlos y a las correspondien-
tes recompilaciones. El problema no es grave mientras nuestro proyecto se
compone de unos pocos archivos fuente, pero está clara la dificultad de
controlar el proceso manualmente en cuanto la escala aumenta.
Aparte de ello, debe tenerse en cuenta que, por razones de economía,
deberían recompilarse sólo aquellos archivos en los que se hubieran regis-
trado errores, y no los demás, pero lo único que es verdaderamente simple
es recompilar todo cada vez, lo que, cuando la escala aumenta, es inacep-
table.
El asunto se complica si tenemos en cuenta que los productos de las
compilaciones se deben usar como archivos de entrada para la fase de en-
lace, en la que también pueden producirse errores, que para ser corregidos
pueden necesitar modificar los archivos fuente y por tanto causar que deba
realizarse al menos una recompilación parcial.
Por último, si nuestros ejecutables no funcionan como se esperaba,
habrá que modificar los fuentes, y de nuevo nos encontramos con el mismo
problema.
Las consideraciones anteriores sólo se han referido al ciclo de cons-
trucción básico, pero debemos tener en cuenta que, durante el desarrollo,
111
11. Introducción
se suelen intentar mejoras en las implementaciones, lo que conduce a nue-
vas versiones de algunos de los archivos fuente, con las consecuencias co-
rrespondientes sobre la generación de los ejecutables respectivos, proceso
en el cual, algunos fuentes anteriores no necesitan recompilarse pero otros
sí, etc.
Resulta evidente que las mismas consideraciones pueden hacerse res-
pecto a otras tareas comunes en el tratamiento de información, como la
generación y mantenimiento de documentación, de donde se deriva el in-
terés que, en el ámbito profesional, tiene el uso de herramientas que nos
permitan controlar y automatizar estos procesos.
La utilidad make se usa para resolver esta clase de problemas y las si-
guientes páginas están dedicadas a una breve introducción al uso elemental
de la versión GNU de dicha herramienta en el desarrollo de aplicaciones en
lenguaje C, aunque gran parte de lo que se tratará puede aplicarse casi sin
cambios a la solución del problema análogo en otros lenguajes de progra-
mación o en otros tipos de tareas, así como a otras versiones de la utilidad
make presentes en diferentes sistemas de desarrollo.
112
12. La utilidad make de GNU
Como ya hemos visto en el Capítulo 11, la utilidad make, en cualquie-
ra de sus versiones, permite la generación y el mantenimiento ordenado de
los archivos objeto y los ejecutables que se obtienen a partir de otros archi-
vos fuente, reduciendo las operaciones de compilación y enlace al mínimo
necesario.
Para realizar su tarea, make utiliza un archivo denominado genérica-
mente makefile en el que el programador especifica un conjunto de reglas
que make debe aplicar al objeto de conseguir sus objetivos. La forma de
especificar dichas reglas y su contenido son el objeto del presente capítulo.
La utilidad make se ejecuta habitualmente desde la línea de órdenes
del shell y el formato de la orden es
_

¸

make [−f <makefile >] [ opciones ] . . . [ obj eti vos ] . . .
La opción más importante es
-f Seguida de un nombre de archivo hace que ese archivo sea utilizado como
makefile. Si no se usa la opción, se utilizará por defecto un archivo
en el directorio actual denominado makefile o, si ese no existe, uno
denominado Makefile
La orden make puede contener como argumentos opcionales varios ob-
jetivos de las reglas que figuren en el archivo makefile. Si en la orden figura
algún objetivo, en la ejecución de make se disparará la regla correspondien-
te (ver Subsección 12.3.1). Si en la orden make no figura ningún objetivo, se
disparará la primera regla que figure en el archivo makefile.
Para una información detallada de las opciones de la orden make con-
súltese la página del manual UNIX correspondiente.
En el resto del capítulo describiremos el formato del archivo makefile
en sus formas más simples. La información completa sobre el uso de la
utilidad make de GNU puede consultarse en línea mediante la orden
113
12. La utilidad make de GNU
_

¸

i nf o make
Para consultar esa documentación en otros formatos véase [3].
12.1. Funcionamiento básico de make
La utilidad make funciona basándose en las reglas especificadas en el
archivo makefile. En esas reglas se expresa, para cada archivo que deba
construirse, de qué otros archivos depende.
Por ejemplo, puede decirse de qué archivos objeto depende un ejecu-
table que deba construirse.
Además, en esas reglas se expresan las órdenes del shell que deben
ejecutarse cuando el archivo que queremos construir no existe o es más
antiguo que alguno de los archivos de los que depende (se dice en ese caso
que el archivo que necesitamos construir no está al día).
12.2. Sintaxis de los archivos makefile
Un archivo makefile es un archivo de texto puro, que puede prepa-
rarse por tanto con cualquier editor de texto que permita escribir texto sin
formato.
Los archivos makefile pueden contener básicamente comentarios, de-
claraciones de variables, reglas que especifican las acciones a realizar por
make (ver Sección 12.1) y directivas que indican a make algunas formas
especiales de interpretación del contenido del archivo makefile.
En el Listado 12.1 puede verse un ejemplo simple del contenido de un
archivo makefile.
En él pueden verse líneas como la 1 que es un comentario. Cualquier
texto que siga la carácter “#” hasta el fin de línea se considera un comenta-
rio y es ignorado por make.
Las líneas 3 a 8 son definiciones de variables. Por convenio, los nom-
bres de variables se escriben en mayúsculas. Los valores de las variables
son cadenas de caracteres que siguen al separador “=”. Posteriormente a la
114
12.2. Sintaxis de los archivos makefile
1 # Ejemplo de makefile
3 CFLAGS = −c −ggdb −Wall −ansi
LNKFLAGS = −ggdb −Wall −ansi
5 CC = gcc
7 OBJS = uno. o dos . o tres . o
NOMEXEC = ej ecutabl e
9
.PHONY : clean
11
al l : $(NOMEXEC)
13
clean :
15 rm −f ∗~ core $(OBJS) $(NOMEXEC)
17 $(NOMEXEC) : $(OBJS)
$(CC) $(LNKFLAGS) −o $(NOMEXEC) $(OBJS)
19
uno. o : uno. c uno. h dos . h
21 $(CC) $(CFLAGS) −o uno. o uno. c
23 dos . o : dos . c
$(CC) $(CFLAGS) −o dos . o dos . c
25
tres . o : tres . c dos . h
27 $(CC) $(CFLAGS) −o tres . o tres . c
Listado 12.1: Ejemplo de makefile
115
12. La utilidad make de GNU
definición, puede usarse el valor de la variable en cualquier expresión que
lo requiera mediante la sintaxis $(<nombre_de_variable>). Por ejemplo,
en la línea 18 se está usando de esa forma el valor de la variable CC de esa
manera.
Las líneas 12 a 27 contienen reglas. El contenido esencial de un ar-
chivo makefile son las reglas que dirigen la actividad de make.
Cada regla se compone de una línea de cabecera y de cero o más accio-
nes que consisten en órdenes que podrán ser ejecutadas condicionalmente
por el shell. Entre cada regla y la siguiente debe haber al menos una línea
en blanco.
12.3. Sintaxis de las reglas de un archivo makefile
El formato genérico de una regla es
_

¸

<obj eti vo > : [ <pre_requisito_1> <pre_requisito_2> . . . ]
[ orden_shell_1 ]
[ orden_shell_2 ]
[ . . . ]
La primera línea es la cabecera de la regla. En ella, <objetivo> es
generalmente un nombre de archivo
1
. Los objetivos de las distintas reglas
deben ser todos distintos, de modo que el objetivo de una regla puede usarse
como un identificador para ella. Denotaremos una regla cuyo objetivo es
<objetivo> como Regla(<objetivo>).
Igualmente, los prerrequisitos son nombres de archivos que son opcio-
nales y están separados del objetivo por el carácter (obligatorio) “:” y entre
sí por espacios en blanco.
Las acciones son líneas que contienen órdenes que posiblemente se
pasarán al shell para su ejecución cuando se dispare la regla, y deben
empezar todas por el carácter <TAB>. En particular, una línea que sólo
contiene el carácter <TAB> no es una línea en blanco sino una acción vacía.
1
Pero véase el uso de la directiva .PHONY en la Sección 12.4
116
12.4. Ejemplo de ejecución de la orden make
12.3.1. Disparo de una regla
Se denomina disparo de una regla a su ejecución por make.
Definición: Se dice que un objetivo no está al día si no existe, o bien
existe pero su fecha-hora de modificación es anterior a la de alguno de sus
prerrequisitos.
Las operaciones de disparo de una regla con un objetivo igual a
<objetivo> y prerrequisitos <pre_requisito_1>, <pre_requisito_2>,
. . . , se describen en el Algoritmo 12.1:
Algoritmo 12.1 Disparo de Regla(< objetivo >)
for all < pre_requisito_i > do
if ∃Regla(< pre_requisito_i >) then
Aplicar el algoritmo de disparo a Regla(< pre_requisito_i >)
end if
end for
if < objetivo > no está al día then
Ejecutar las acciones de Regla(< objetivo >)
end if
12.4. Ejemplo de ejecución de la orden make
Supóngase que tenemos en nuestro directorio de trabajo tres archivos
fuente en lenguaje C denominados uno.c, dos.c y tres.c y dos archivos
de cabecera denominados uno.h y dos.h respectivamente. En el texto del
archivo uno.c figuran dos directivas include del preprocesador que causan
la inclusión de los archivos de cabecera uno.h y dos.h respectivamente,
mientras que en en el archivo tres.c figura una directiva include del pre-
procesador que causa la inclusión del archivo de cabecera dos.h.
Si en nuestro directorio de trabajo existe un archivo denominado
makefile cuyo contenido es el texto del Listado 12.1, podremos usar la
orden make en la forma
_

¸

make
117
12. La utilidad make de GNU
Los efectos serán los siguientes: Al no usarse la opción -f se utilizará
como makefile nuestro archivo denominado precisamente makefile.
Al no proponerse ningún objetivo en la orden make se disparará la
primera regla, que es la que aparece en la línea 12 del Listado 12.1, y que
como puede apreciarse, es una regla sin acciones asociadas.
Esa regla, una vez sustituidos los valores de las variables que figuran
en ella, tiene la forma
_

¸

al l : ej ecutabl e
El Algoritmo 12.1 causará que se dispare la regla de las líneas 17-18,
puesto que el único prerrequisito de la regla Regla(all) es ejecutable.
Una vez sustituidos los valores de las variables que figuran en ella,
Regla(ejecutable) tiene la forma
_
`
¸

ej ecutabl e : uno. o dos . o tres . o
gcc −ggdb −Wall −ansi −o ej ecutabl e uno. o dos . o tres . o
De acuerdo con el Algoritmo 12.1, deberán dispararse las reglas cuyos
objetivos son uno.o (líneas 20-21), dos.o (líneas 23-24) y tres.o (líneas 26-
27).
La primera de ellas, una vez sustituidos los valores de las variables,
tiene la forma
_
`
¸

uno. o : uno. c uno. h dos . h
gcc −c −ggdb −Wall −ansi −o uno. o uno. c
Los prerrequisitos de esa regla no son objetivos de ninguna otra, así
que se comprobará si uno.o está al día. Si no es así, se ejecutará la acción
correspondiente, que, como es evidente compilará el archivo uno.c para
generar uno.o.
118
12.4. Ejemplo de ejecución de la orden make
Las otras dos reglas dos.o y tres.o funcionan igual, y tienen como
finalidad generar los respectivos archivos objeto a partir de sus fuentes si
aquellos no están al día.
Una vez que se ha hecho esto, uno.o, dos.o y tres.o estarán al día,
bien porque ya lo estaban antes o porque el disparo de las tres reglas habrá
hecho el trabajo.
Ahora, para terminar de ejecutar el algoritmo de disparo de la regla
Regla(ejecutable), puesto que sus prerrequisitos están al día, lo que
queda por ver es si está al día su objetivo (esto es el archivo denominado
ejecutable).
Si no es así se ejecutará la acción correspondiente, que, como es evi-
dente, realiza el enlace de los tres archivos uno.o, dos.o y tres.o para
generar el archivo objetivo.
Por último, con la regla Regla(all) no queda nada por hacer, puesto
que no tiene acciones asociadas. Naturalmente, con esta regla se podrían
haber generado algunos otros ejecutables más si la variable NOMEXEC se
hubiese definido con un valor que hubiese citado más nombres de archivo
y no solamente uno.
En la Figura 12.1 puede verse la salida en el terminal de la orden make
cuando se ejecuta por primera vez para nuestro ejemplo, supuesto que no
hay errores de compilación ni enlace.
Por otra parte, se comprueba con facilidad que se habrán creado los
archivos uno.o, dos.o, tres.o y ejecutable. Ahora puede comprobarse
fácilmente también que si cambiamos el contenido de alguno de los archivos
fuente, al ejecutar de nuevo make, se recompilarán sólo los archivos necesa-
rios y se realizará de nuevo el enlace. En particular, si no hacemos ningún
cambio y ejecutamos make nuevamente, obtendremos un mensaje infor-
mándonos de que no hay nada por hacer con all, esto es, que ejecutable
está al día.
Con este archivo makefile de ejemplo también podemos ejecutar make
en la forma siguiente
_

¸

make clean
Si lo hacemos así se disparará la regla cuyo objetivo es clean, que,
una vez sustituidos los valores de las variables, tiene la forma
119
12. La utilidad make de GNU
Figura 12.1.: Salida de la orden make para el makefile del Listado 12.1
_
`
¸

clean :
rm −f ∗~ core uno. o dos . o tres . o ej ecutabl e
Esta regla no tiene prerrequisitos, y su acción se ejecutará por tanto
incondicionalmente. Es evidente que la acción consiste en borrar todos los
archivos objeto, más el ejecutable, más cualquier volcado de memoria pro-
ducido por ejecuciones erróneas previas (core) y las versiones previas de
los archivos de texto que hayan sido modificadas con el procesador de texto
(típicamente su nombre es el del archivo original terminado con el carácter
“∼”).
Ahora resulta claro por qué una regla como ésta no debe ser la primera
de un archivo makefile. Ello haría que fuera la que se ejecutara cuando
usamos make sin argumentos, y eso no es lo que habitualmente deseamos.
Por otra parte, si no se toman precauciones, la regla Regla(clean)
puede dar algunos problemas. En particular, si existiera un archivo con
nombre clean, como no hay prerrequisitos, se consideraría que está al
día, y la acción no se ejecutaría. Por si se produce esa circunstancia, es
necesario decirle a make que el objetivo clean no es un nombre de archivo,
120
12.5. Reglas implícitas
y que la acción de la regla debe ejecutarse tanto si existe un archivo con
ese nombre como si no; de hecho, la acción tampoco produce un archivo
con ese nombre. La forma de hacerlo en la versión GNU de make es usar la
directiva .PHONY tal como se hace en la línea 10 del Listado 12.1.
12.5. Reglas implícitas
En las secciones anteriores hemos descrito la utilización de reglas en
los archivos makefile. Las reglas que figuran en los archivos makefile re-
ciben el nombre de reglas explícitas. No obstante, los diferentes sistemas
operativos incorporan utilidades make que disponen de un catálogo de re-
glas que aplican implícitamente a falta de reglas explícitas que especifiquen
cómo debe realizar make su tarea. Esas reglas implícitas están asociadas a
los nombres de los objetivos y los prerrequisitos (en particular a las exten-
siones de los nombres), aplicándose así ciertas reglas para compilar archi-
vos escritos en determinados lenguajes, mientras que para otros lenguajes
se usan otras.
Para una información completa sobre el catálogo de reglas implícitas
para la versión GNU de make véase la información de manual de la utilidad
make en [3] o en la documentación en línea.
121
Bibliografía
[1] A. Afzal. Introducción a UNIX. Un enfoque práctico. Prentice Hall, 1997.
[2] Free Software Foundation. BASH reference manual, 2001. http://
www.faqs.org/docs/bashman/bashref_toc.html.
[3] Free Software Foundation. GNU Make Manual, 2006. http://www.
gnu.org/software/make/manual.
[4] Indiana University. UNIX command reference card, 1998. http://www.
indiana.edu/~{}uitspubs/b017/b017.pdf.
123

Sign up to vote on this title
UsefulNot useful