Cap 5+Programación+en+Shell+Script
Cap 5+Programación+en+Shell+Script
RESUMEN
Shell scripting
ABSTRACT
Los idiomas o lenguajes humanos sirven para que las personas se comuniquen entre sí;
así mismo, cuando la comunicación es escrita, existe un conjunto de reglas sintácticas
que especifican la forma correcta en que deben escribirse las palabras y un conjunto de
reglas semánticas que sirven para interpretar lo que se escribe.
Los lenguajes de programación son una forma especial de comunicación entre el hom-
bre y la computadora, y asociados a ellos también existen reglas sintácticas y semánticas
que los definen formalmente.
El objetivo de los lenguajes de programación es darle órdenes adecuadas al equipo
de cómputo para que realice diversas tareas. Existen diferentes clases o tipos de lengua-
jes de programación. El más básico es el lenguaje de máquina, que utiliza el alfabeto
binario para escribir las instrucciones en el propio código de la máquina y por eso no
necesita ninguna traducción.
Hay un grupo de lenguajes que se conoce como de bajo nivel, debido a que la forma
en que se codifican las instrucciones está muy cercana al lenguaje de la máquina, un
ejemplo es el ensamblador. Este tipo de lenguaje también es específico de un solo tipo
de máquina y necesita un traductor que transforme el código escrito al código de la
máquina sobre la cual se ejecutará el programa.
Otro grupo de lenguajes se considera de alto nivel, debido a que son independientes
de la máquina y se acercan más al lenguaje humano. Este tipo de lenguaje necesita un
intérprete56 o compilador que traduzca las instrucciones para que puedan ejecutarse en
la máquina. Existen muchos lenguajes de programación que se pueden agrupar en esta
categoría.
A su vez, pueden encontrarse muchas otras formas de agrupar los lenguajes de pro-
gramación, pero no es el objetivo de este libro discutir el tema y solo se han mencionado
estas clasificaciones generales para introducir el lenguaje Shell script.
Como ya se vio en el capítulo anterior, los Shell de los sistemas Unix permiten inter-
pretar y ejecutar diversas órdenes. La orden más “compleja” que se analizó en ese capítulo
fue enviar un conjunto de comandos al Shell para que lo ejecutara como un proceso de fon-
do, la orden mencionada tiene la forma (comando1; comando2; comando3; comando4)&.
56 Intérprete. Programa informático que analiza y ejecuta, una a una, las instrucciones de un programa escritos en un lenguaje
de alto nivel. No produce código de máquina como los compiladores.
174 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
Esas mismas órdenes (no necesariamente para que se ejecuten de fondo) se pueden
introducir dentro de un archivo de texto y pasarle el nombre del archivo al Shell para
que las interprete y las ejecute (el Shell es un intérprete).
Entonces, en su forma más sencilla, un programa en Shell script no es más que un
conjunto de comandos ordinarios contenidos dentro de un archivo de texto plano. Los
Shell de los SO de la familia Unix son más complejos que eso y pueden, entre otras
cosas: interpretar comandos compuestos, evaluar expresiones que contienen diferentes
operadores, ejecutar subcomandos que se asemejan a sentencias de cualquier lenguaje
de programación, trabajar con funciones definidas, contener comentarios, trabajar con
parámetros, manejar arreglos, sustituir comandos, etc. En fin, toda una gama de facili-
dades que están presentes en los lenguajes de alto nivel.
V.2. GENERALIDADES
En general, para que un archivo se pueda ejecutar deberá tener permiso de ejecución.
Observe la figura V.1, que muestra el listado de un directorio obtenido con el comando ls.
En los SO Unix, los permisos sobre los archivos se establecen de acuerdo con tres
tipos de usuarios: el propietario (owner), los miembros del grupo (group) y los demás
usuarios (others).
La segunda columna de la figura V.1 muestra una cadena o tira de nueve caracteres
(internamente son nueve bits), que define los permisos asociados a cada archivo: los
primeros tres campos corresponden a los permisos del propietario (owner), los tres
campos que siguen definen los permisos que tienen los miembros del grupo (group) y los
tres últimos campos son los permisos de los demás usuarios (others), es decir, de aquellos
que no son dueños del archivo, ni miembros del grupo asociado al proceso (en Unix es
obligatorio que los usuarios pertenezcan al menos a un grupo).
Cuando se lista un directorio, los tres campos de cada terna, dentro de la tira de
nueve bits, pueden mostrar:
El comando chmod permite cambiar esos permisos. La sintaxis de este y otros co-
mandos se debe ver usando la ayuda (man chmod), debido a que pueden diferir de una
versión a otra del SO. En este capítulo, se usa la forma sintáctica de la versión GNU57.
57 GNU, acrónimo recursivo de GNU is not Unix, una forma que tiene el movimiento de software libre de desligarse de los produc-
tos Unix que son propietarios. Los SO GNU/Linux tienen un núcleo, llamado Linux, más una colección de programas GNU
(aunque es habitual que se le llame Linux a todo).
176 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
Existen otros permisos que se pueden fijar utilizando el comando chmod, que son:
SUID, SGID y el sticky bit.
* Permiso SUID (Set User ID). La facilidad SUID define un permiso especial para que
un usuario que no es el dueño de un archivo lo pueda ejecutar con los mismos permisos
de su propietario. A fin de establecer este permiso, se usan las formas u+s y u-s para
agregar y quitar el permiso, respectivamente; obsérvese la referencia al propietario (u).
* SGID (Set Group ID). SGID es similar a SUID, pero en este caso se refiere a los
permisos de ejecución del grupo, que se pueden cambiar para que el usuario ejecute el
archivo con los permisos del grupo (como si fuera miembro de él). Si el permiso SGID
se le aplica a un directorio, los archivos creados en ese directorio pertenecen al grupo
Programación en Shell script / 177
del cual el directorio es miembro y no al grupo del usuario, esta facilidad puede ser útil
para compartir directorios. A fin de establecer el permiso, se usan las formas g+s y g-s
para agregar y quitar el permiso, respectivamente; obsérvese la referencia al grupo (g).
* Sticky bit. Principalmente, se usa sobre directorios compartidos y permite que cual-
quiera pueda escribir o modificar un archivo o directorio, pero solo su propietario o
el root pueden eliminarlo.
La conclusión es la siguiente:
* Si se fija el permiso SUID sobre un archivo que no tiene permiso de ejecución para
el propietario (como es el caso del archivo file en el paso 2), no se le está cambiando
el permiso de ejecución, sino que se le está agregando el permiso SUID, y por eso el
listado lo distingue poniendo la letra S en el bit de ejecución del propietario (rwS), lo
cual significa que a pesar de tenerse ese permiso no se podrá ejecutar el archivo, dado
que ni siquiera el propietario lo puede hacer.
* De otra parte, si se fija el SUID sobre un archivo que tiene permiso de ejecución
para el propietario, el SO le otorga ese permiso al usuario que lo cambió (sin ser el
propietario) y lo distingue en el listado con la letra s; es lo que sucede en el paso 3.
propietario, por eso se usa la forma chmod g+s o g-s. Debido a esto, si se da la orden
chmod g+s file; ls -l file, después de haber hecho el paso 3 del ejemplo anterior el lis-
tado obtenido mostrará los permisos en la forma rwsr-Sr-- file, lo que significa que se
ha establecido el permiso SGID sobre el archivo file, pero no tendrá efecto porque ni
siquiera los miembros del grupo pueden ejecutar ese archivo. Si después se da la orden
chmod g+x file; ls -l file, el listado obtenido será: -rwsr-sr--file, donde se aprecia que
ahora el usuario puede ejecutar el archivo como si fuera un miembro del grupo al cual
pertenece el archivo file.
Para fijar o quitar el sticky bit, se usan los signos + y -, respectivamente, seguido por
la letra t, observe el siguiente ejemplo:
1. Se comienza con un archivo, nombrado file, sobre el cual no se tiene ningún per-
miso, es decir que un listado ls –l file mostraría: --------- file.
2. Ahora se le fija el sticky bit: chmod +t file; ls -l y el listado queda en la forma:
---------T file, donde se puede observar que la tira de bits de permiso finaliza con
una T, lo que significa que se ha fijado el sticky bit, pero no tendrá efecto debido a
que ningún usuario tiene permiso de ejecución.
3. Ahora se ejecuta la siguiente secuencia: chmod +x file; ls –l, el listado obtenido
muestra la forma: --x--x--xt file, donde todos los usuarios tienen el permiso de
ejecución y por eso la letra T ha pasado a ser t, porque se ha activado el sticky bit.
* chmod 7 file es equivalente a chmod 0007 file. En este caso, sobre el archivo file:
• No se establecen los permisos SUID, SGID ni el sticky bit que se corresponden con
el primer dígito (el cero en la posición 1 es 000).
* chmod 754 file es equivalente a chmod 0754. En este caso, sobre el archivo file no
se establecen los permisos SUID, SGID ni el sticky bit que se corresponden con el
Programación en Shell script / 179
* chmod 7111 file; el primer dígito (7: 111) fija los bits SUID, SGID y el sticky bit;
los tres dígitos restantes (todos 1: 001) fijan el permiso de ejecución para todos los
usuarios.
* shell <nombre del script>. En este caso, se cargará el Shell que se especifique: bash,
sh, ksh, etc., al cual se le pasará, como argumento, el nombre del programa que se
desea ejecutar, por ejemplo:
• bash Example. En esta orden, se le pide al Bash que ejecute el archivo Example.
* ./<nombre del script>. El símbolo ./ se refiere al directorio actual, es decir, se le dice
al Shell que el archivo que tiene que ejecutar está en el directorio actual, por ejemplo:
El primer programa que se presenta (V.1) es bien sencillo y lo único que contendrá
son tres comandos: clear para limpiar la pantalla, pwd (print working directory) para co-
nocer el nombre del directorio de trabajo actual y echo para imprimir algo. La opción -n
del comando echo inhibe el cambio de línea que escribe el comando por defecto.
El programa V.1 también contiene un comentario: el símbolo # hace que el intérprete
ignore todos los caracteres que le siguen hasta el cambio de línea.
La salida del programa será (el directorio variará de acuerdo con el directorio actual):
Este es un programa en Shell script
El directorio actual o de trabajo es: /home/mlezcano
Obsérvese que el último comando echo y el comando pwd imprimen sobre la misma
línea, debido a que el segundo echo usa la opción -n para inhibir el cambio de línea que
siempre da por defecto este comando.
180 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
Los programas en Shell script pueden contener una primera línea que especifica el
Shell que ejecutará el archivo script; si no se especifica nada, como en el programa V.1,
el Shell es el que esté usando el usuario que ordenó la ejecución.
La variable de ambiente SHELL contiene el nombre del Shell que se está usando. Para
ver su contenido, se puede usar el comando echo $shell que imprimirá /bin/bash si se
está usando el Bash (no tiene que ser ese directorio específico). Recuerde que el signo $ no
forma parte del nombre de la variable y se usa cuando se quiere acceder a su contenido.
La segunda versión del programa V.1 muestra una primera línea especial que co-
mienza con #!58, a fin de especificar que el programa lo debe ejecutar el Bash que está
localizado en el camino o ruta /bin/, es decir, en el directorio bin que es hijo del direc-
torio raíz (/).
Es una práctica muy buena poner siempre, en los programas Shell script, esa primera
línea; si no se hace así, se usará el intérprete por defecto y las órdenes contenidas en el
programa pueden ser incompatibles con él. Este tipo de línea es común en los lenguajes
interpretados, tales como Perl, Phyton, etc.
El programa V.2 amplía el programa V.1 poniendo las cadenas que se asocian a los co-
mandos echo entre comillas ( “) y agregándoles otras funcionalidades. Cuando el coman-
do echo se encuentra una cadena entre comillas, interpreta los caracteres especiales59.
También se usan tres nuevos comandos: date, que permite escribir la fecha; set, que
fija valores del Shell y de los parámetros posicionales; y exit60, que termina el proceso.
58 La combinación de símbolos #! al inicio del programa se conoce como sha-bang, y es una marca especial que se usa en los
sistemas tipo Unix para indicar el camino absoluto donde se encuentra el programa que debe interpretar al script contenido en
el archivo.
59 Un carácter especial es aquel que debe interpretarse de manera diferente a su significado literal.
60 Todos los comandos con comportamiento correcto, en sistemas tipo Unix, devuelven un estado de salida (representado por un
número entero): si tiene éxito, el retorno es 0 y si no tiene éxito, es distinto de cero.
Programación en Shell script / 181
Este pequeño programa, aunque simple, tiene muchos detalles que están presentes
en la programación en Shell script, debe tratar de entenderlo en su totalidad.
Los ejemplos presentados en la sección anterior ofrecen una idea general acerca de la pro-
gramación en Shell script. Esta sección también comienza con un ejemplo (el programa
V.3), que tiene la intención de insistir acerca de las variables dentro de un programa Shell
script.
El programa V.3 comienza imprimiendo algo, para lo cual usa el comando:
Debe observarse que es obligatorio que la cadena que le sigue al comando echo esté
entre comillas, debido a que así el Shell interpreta algunos símbolos de forma especial.
En este caso particular, interpretará que debe imprimir el contenido de las variables: 0,
USER y HOSTNAME. Si la cadena estuviera entre apóstrofos, por ejemplo, ‘USER’ o
solo USER, echo la imprimiría literalmente, es decir, sin hacer ninguna interpretación;
en este caso imprimiría la palabra USER.
La variable posicional 0 contiene el nombre del programa, y las variables de am-
biente USER y HOSTNAME contienen el identificador del usuario y el nombre de la
máquina, respectivamente; así que ante la línea que se está analizando (con el comenta-
rio #1 en el programa), el Shell imprimirá lo siguiente (suponiendo que el programa se
llama progv3, el usuario es mlezcano y la máquina es miDebian):
El programa progv3, lo ejecuta mlezcano en la computadora miDebian.
Los siguientes comandos echo (observe los números puestos como comentarios para
facilitar la explicación) imprimen los contenidos de otras variables:
V.3.1 Operadores
* test <expression>
* [ <expression> ]
* man test
* man [
Las expresiones dentro de test se pueden combinar usando diversos operadores, las
tablas que se presentan a continuación los agrupan según diferentes criterios.
La tabla V.1 muestra un grupo de operadores que se usan para realizar compara-
ciones dentro de la sentencia test. Están agrupados de acuerdo con el contenido de las
variables.
* La forma test $a=$b no reporta error, pero el resultado no será el esperado y por eso
debe escribirse como test $a = $b.
Tabla V.1.
Operadores básicos de test
Tabla V.2.
Operadores relacionales numéricos de test. Los operandos son números enteros
Ejercicio:
Desde una terminal, teclee las expresiones que se muestran en la columna “Ejemplo”
de las tablas V.1 y V.2, después ejecute el comando echo $?. Observe que el valor que se im-
primirá dependerá del valor de retorno de cada expresión evaluada por la sentencia test.
Por ejemplo, teclee: a=3; b=4; [ $a -eq $b ]; echo $?, ¿qué valor se imprime?, ¿por qué?
61 Operadores infijos, son aquellos que están dentro de dos operandos: <operando1>operador<operando2>.
62 Operadores binarios, son aquellos que tienen dos operandos.
Programación en Shell script / 185
La tabla V.3 muestra que los operadores para el trabajo con cadenas alfanuméricas
son de dos tipos: unarios63 y binarios. La tabla V.4 muestra los operadores lógicos que se
pueden usar con la sentencia test.
Después de analizar las tablas V.3 y V.4, realice los ejercicios propuestos. Debe ase-
gurarse de comprenderlos. La idea que usted siempre debe seguir, en esta etapa del
aprendizaje, es comprobar los comandos desde el prompt del intérprete de comandos
para que después pueda incluirlos en los programas Shell script que haga.
Tabla V.3.
Operadores para comparar cadenas alfanuméricas con test
Tabla V.4.
Operadores lógicos que usa la sentencia test
Ejercicios:
1. Use los operadores de las tablas V.3 y V.4 desde una terminal. Proceda de la si-
guiente manera:
a. Asigne valores a algunas variables de usuario. Observe cómo hacerlo en la
segunda fila de la tabla V.3.
b. Pruebe cada uno de los ejemplos mostrados en la tabla V.3, haciendo las com-
paraciones que se muestran en la columna correspondiente.
i. Para cada comparación que haga, averigüe el valor de la variable especial ?
(contiene el valor devuelto por el último comando).
2. Pruebe los ejemplos mostrados en la tabla V.4.
3. Proponga ejercicios de este tipo usted mismo y pruebe que funcionen en la termi-
nal, después intente usar esos resultados dentro de un programa en Shell script.
Sugerencia: Use comandos compuestos, por ejemplo: s1=Daniel; test -n $1; echo $?
La tabla V.5 muestra los operadores que se usan junto a la sentencia test para obtener
diversas informaciones acerca de los archivos.
Estos operadores son muy importantes cuando se programa en Shell script, debido a
que muchas veces los programas se hacen para interaccionar con archivos y es necesario
conocer algunas de sus características, tales como:
* ¿Qué tipo de archivo es? (Un directorio, un archivo especial, un archivo ordinario, un
socket, un descriptor de archivo, una tubería nombrada, etc.).
Es habitual usar la sentencia test para probar diversas condiciones acerca de los
archivos y después tomar alguna alternativa basada en las características del archivo.
Analice la tabla V.5 de manera detallada y asegúrese de comprender todos los aspec-
tos que se presentan en ella. Observe que son diversos y tendrá que cerciorarse de que
los entiende. Después, puede proceder a realizar los ejercicios que se proponen. Hágalo
en las dos formas propuestas, es decir, interactivamente y dentro de un programa en
Shell script.
Programación en Shell script / 187
Tabla V.5.
Operadores que actúan sobre archivos
OPERADORES UNARIOS
OPERADOR SIGNIFICADO EJEMPLO SIGNIFICADO
Archivo de
-b (block) -b file1 Verdadero si existe el archivo especial de bloques file1
bloques
Archivo de
-c (character) -c file2 Verdadero si existe el archivo especial de caracteres file2
caracteres
-d (directory) Es un directorio -d dir Verdadero si existe el directorio dir
-e (exist) El archivo existe -e file3 Verdadero si existe el archivo file3 (aunque sea un directorio)
-f (file) Archivo regular -f file4 Verdadero si existe el archivo regular file4
Efectivo ID de Verdadero si existe el archivo file5 y pertenece al effective
-G -G file5
grupo group ID
Tiene fijado el
-g (group) -g file6 Verdadero si existe el archivo file6 y tiene fijado su bit SGID
SGID
Enlace
-h -h file7 Verdadero si el archivo file7 es un enlace simbólico
simbólico
-k Sticky bit -k file8 Verdadero si existe el archivo file8 y tiene fijado su sticky bit
Es un enlace
-L -L file9 Verdadero si el archivo file9 es un enlace simbólico
simbólico
Efectivo ID de Verdadero si existe el archivo file10 y pertenece al effective
-o -o file10
usuario user ID
Tubería con
-p -p file11 Verdadero si el archivo file11 es una tubería con nombre
nombre
-r Lectura (read) -r file12 Verdadero si el archivo file12 tiene permiso de lectura
Longitud mayor
-s -s file13 Verdadero si la longitud del archivo file13 es mayor que cero
que cero
-S Socket -S file14 Verdadero si existe el archivo file14 y es un socket
Verdadero si fileD es un descriptor de archivo que refiere a
-t Terminal -t fileD
una terminal
-u Bit SUID -u file15 Verdadero si el archivo file15 tiene fijado su bit SUID
-w Escritura (write) -w file16 Verdadero si el archivo file16 tiene permiso de escritura
Ejecución
-x -x file17 Verdadero si el archivo file17 tiene permiso de ejecución
(execute)
OPERADORES BINARIOS
-ef (equal Son archivos ¿Los archivos file1 y file2 tienen el mismo i-node y están en
file1 -ef file2
files) iguales el mismo equipo (apuntado desde lugares distintos)?
-nt (newer Más reciente
file1 -nt file2 ¿El archivo file1 es más reciente que el file2?
than) que
-ot (older
Más viejo que file1 -ot file2 ¿El archivo file1 es más viejo que el archivo file2?
than)
188 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
Ejercicios:
1. Compruebe, de forma interactiva, algunas de las características de los archivos
que están en su directorio de trabajo. Recuerde combinar test con echo $? para
conocer el resultado de la evaluación que ha hecho test; por ejemplo, compruebe
lo siguiente: [ -e file ]; echo $?
2. Si el archivo file existe, test fijará la variable ? con el valor 0 y si no existe, le
asignará el valor 1. Con base en eso, echo $? imprimirá un valor o el otro.
Ahora intente hacer ese mismo tipo de prueba con los ejemplos mostrados en
la tabla V.5. Recuerde que puede usar el comando chmod para cambiar algunos
permisos de los archivos.
3. Vaya más adelante y lleve estas mismas ideas a un programa.
Tabla V.6.
Operadores aritméticos básicos
En el Shell, no existen tipos de variables y por eso una misma variable en un momen-
to puede contener una palabra o un número. Es responsabilidad del programador pasar
valores numéricos a estas expresiones.
Existen cuatro formas de evaluar una expresión aritmética:
La primera forma es usando el comando externo expr. A continuación, se muestran
algunos ejemplos. El comando expr imprime el valor calculado:
El comando expr, heredado del Bourne shell, es un comando externo y por eso la
operación se hace más lenta.
Programación en Shell script / 189
* La segunda forma es usando el comando interno let, el cual evalúa la expresión pero
no imprime el resultado, como sucede con expr cuando se usa de forma independien-
te, como se hizo en expr $a + $b y en expr $a % $b.
Ejemplos:
El comando let informa error sintáctico si se usan espacios antes o después de los
operadores; al contrario de expr, en el que es necesario dejar espacios a ambos lados de
los operadores.
Cuando se usa el comando let, no es necesario que las variables estén antecedidas por
el signo $ para referirse a su contenido (aunque se puede usar).
Tanto el comando let, como la forma de doble paréntesis admiten tres operadores
adicionales: incremento (++), decremento (--) y exponenciación (**). Los dos primeros
operadores son unarios y el último es binario. Observe los ejemplos siguientes en los
cuales se mezclan ambas formas:
190 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
Los operadores ++ y -- pueden ser prefijos (van antes del operando) o posfijos (van
después del operando).
Todas las operaciones aritméticas se realizan con números enteros. Si se desea hacer
una operación con números en punto flotante, es necesario usar la calculadora bc, que
incluye un lenguaje de programación.
Sentencia if
A continuación, se formaliza la definición de la sentencia condicional if, que se toma
como ejemplo para establecer las normas sintácticas generales que se usan en este
capítulo.
Sintaxis de if:
if clist1; then clist2; [[ elif1 celist1; then celist2; ] ... [ elifn celistn; then celistn; ]][ else list; ] fi
En las definiciones sintácticas usadas en este libro, cualquier expresión entre corche-
tes ([]) es opcional, de modo que la forma más simple de la sentencia if es la que se usa
en el programa V.4, es decir: if list1; then list2; fi.
El programa V.4 comienza con una sentencia if que debe evaluar una expresión usan-
do la sentencia test en su forma []. Observe que en el programa V.4 hay un espacio en-
tre los corchetes (abiertos y cerrados), los cuales deberán estar siempre; de lo contrario
la forma [] no se reconocerá sintácticamente por el Shell.
En este caso, no fue necesario el uso de punto y comas para separar los lexemas,
debido a que los cambios de línea hacen la función de separadores de lexemas y el Shell
puede discriminar entre cada uno de ellos. Se recomienda escribir las sentencias de
los programas de esta manera, es decir, separando los lexemas por cambios de líneas;
también deben identarse66 las sentencias debido a que resulta más fácil leer el código.
Observe los comentarios del segmento de código anterior.
* En la primera forma: if ls; then pwd; fi, se ponen los punto y coma para separar las
partes (lexemas) que conforman la sentencia if.
* En la segunda forma, se escriben los lexemas en líneas separadas. En este caso, cada
vez que se oprime la tecla enter (cambio de línea), el Shell se percata de que el coman-
do no está completo y por eso lanza su segundo prompt (por defecto el signo >, que
está contenido en la variable de ambiente PS2), con lo que indica que faltan elemen-
tos. Cuando el Shell se encuentra con el lexema fi, identifica el final de la sentencia
que le indica que ya puede intentar reconocerla.
Obsérvese que la lista de comandos del if, en la figura V.3, está compuesta por el co-
mando ls que nunca falla (su código de retorno es 0), y por eso se pasa a ejecutar el coman-
do que está en la parte then (pwd).
Se retoma la sintaxis de la sentencia if para interpretar su significado (su semántica):
if clist1; then clist2; [[ elif1 celist1; then celist2; ] ... [ elifn celistn; then celistn; ]][ else list; ] fi
El programa V.5 muestra un ejemplo del uso de la sentencia if con todas sus partes.
Debe observarse que se utiliza una sentencia if que contiene dos elif y el else, es decir
que este programa solo contiene una sentencia. En la parte if y en todas las partes elif,
se tiene que probar alguna condición, no así en la parte else a la cual se llega si fallan
todas las pruebas anteriores.
Primero se verifica que se ha pasado la cantidad de parámetros correcta (2), lo cual
se hace evaluando la expresión ! [ $# -eq 2 ]; obsérvese que se niega (!) la expresión
de la sentencia test ([]), es decir que si la cantidad de parámetros no es 2, entonces la
evaluación de la sentencia [ $# -eq 2 ] es falsa y por tanto su negación es verdadera, lo
que significa que hay un error (los espacios que se dejan son de carácter obligatorio). En
ese caso (hubo error), se ejecuta la parte then, donde se imprimen las cadenas asociadas
a la sentencias echo y el programa termina con código de error (exit 1).
Si la cantidad de parámetros es correcta, es decir, si la evaluación de la expresión
que acompaña al if es falsa, se pasa a evaluar el primer elif, para comprobar si el primer
parámetro (contenido en la variable posicional 1) es el nombre de un directorio, de
nuevo se usa la expresión negada; si es verdadera, el programa ejecuta la parte then
correspondiente a ese elif y termina informando un error (exit 2).
Programación en Shell script / 193
Sentencia case
La sentencia case también es una condicional que permite dirigir el flujo de un progra-
ma en ejecución hacia diferentes partes del código. Cuando existen muchas alternativas
de ejecución es mejor usar esta sentencia y no un if, con muchas partes elif, debido a que
el programa es más fácil de comprender y queda más compacto.
La sintaxis de la sentencia case es la siguiente:
case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
Debe recordar que en la forma sintáctica adoptada, las expresiones entre corchetes
son opcionales, por eso la forma más sencilla de case es: case word in esac.
El programa V.6 consta de un menú con tres opciones que se muestran usando el
comando echo, después de lo cual se usa el comando read A, que lee la selección del
usuario y la almacena en la variable A.
194 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
Observe que hay una etiqueta por cada uno de los valores válidos que puede tomar la
variable (A, en este caso). En este ejemplo, las etiquetas son: 1), 2) y 3); además, hay una
etiqueta especial, *), para la acción por defecto.
Programación en Shell script / 195
En el caso del programa V.6, la acción por defecto es emitir un mensaje de error que
le informa al usuario que ha elegido una opción que no es correcta (algo distinto a los
valores: 1, 2 o 3), para después salir con el código de error 1 (exit 1).
Cuando un programa se encuentra ante una sentencia con la forma case word in, que
en el ejemplo es case $A in, compara el valor de la variable con cada una de las etiquetas
y salta a la etiqueta que coincide con el valor de la variable (A, en este caso) para ejecu-
tar todas las instrucciones desde ese lugar hasta el fin del alcance de la etiqueta que va
hasta los dos símbolos de punto y coma (;;). Si la selección del usuario no coincide con
ninguna de las etiquetas, es decir: 1), 2), 3), el salto se produce a la etiqueta *).
En el ejemplo V.6, el programa, según la opción escogida, mostrará información
acerca de:
Sentencia for
La sentencia for tiene dos formas sintácticas.
De esta manera, la variable Files contiene una lista con nombres de archivos. Obsér-
vese que en el segundo caso no se verifica si son en realidad nombres de archivos.
Después, se hace un ciclo for tantas veces como nombres haya en la variable Files;
en cada iteración, se verifica si la variable file contiene el nombre de un archivo y se
imprime su contenido (usando el comando cat).
El ciclo for comienza evaluando la expresión aritmética expr1 (solo cuando se inicia
el ciclo, es decir, una vez). Después, se repite el ciclo hasta que la evaluación de la expre-
sión aritmética expr2 sea cero. Para cada evaluación de expr2 que sea distinta de cero, se
evalúa la expresión aritmética expr3 y se ejecuta command.
Se puede omitir cualquier expresión aritmética (expr1, expr2, expr2) y, en ese caso, for
se comportará como si la evaluación fuera 1.
El valor retornado es el valor de salida del último comando que se ejecute, o es falso
si cualquiera de las expresiones no es válida.
El programa V.8 es una variante del programa V.7, donde se usa la segunda forma del
for. En este caso, el ciclo se basa en la comprobación del valor numérico de la variable
count (debe contener la cantidad de archivos que se van a listar). Los nombres de los
archivos quedan almacenados en la variable Files, al igual que en el programa V.7.
Programación en Shell script / 197
Sentencia while
Sintaxis:
while list1; do command; done
El programa V.9 muestra un ejemplo del uso de la sentencia while. El programa hace
un reporte de todos los usuarios que tienen cuenta en una máquina dada. El reporte in-
cluye los siguientes datos para cada usuario: el nombre con que se identifica dentro del
sistema (el login), su nombre completo, el directorio donde lo sitúa el SO cuando inicia
sesión (el directorio de inicio) y el intérprete de comando que usa (el Shell).
El programa V.9 toma los datos del archivo passwd que reside en el directorio /etc.
Ese archivo contiene información de todos los usuarios e incluye los siguientes datos:
nombre de usuario, nombre real, directorio de inicio (home), una referencia a la palabra
clave (password), el Shell que se usa, etc. El formato del archivo passwd es el siguiente
(son siete campos separados por dos puntos): login:clave:uid:gid:info:homeDir:Shell:
El primer campo (login) contiene el nombre con el que se identifica el usuario en el
sistema; el segundo campo (clave) contiene una letra x, que indica que la palabra clave
está almacenada en el archivo /etc/shadow en forma encriptada; el tercer campo (UID)
contiene la identificación del usuario (user identification)67; el cuarto campo (gid) contie-
ne la identificación de grupo primario (se almacena en /etc/group); el quinto campo
(info) contiene información adicional de cada usuario (nombre completo, teléfono, etc.);
el sexto campo (homeDir) contiene el camino absoluto hacia el directorio de inicio del
usuario (si el directorio no existe, entonces el directorio raíz se convierte en el directo-
rio de inicio); el séptimo campo (Shell) contiene el camino absoluto hacia el intérprete
de comandos.
67 El UID cero (0) es del usuario root, los UID del 1 al 99 se reservan para otras cuentas predefinidas y los UID del 100 al 999
se reservan para cuentas de administración y de sistema.
Programación en Shell script / 199
Ahora se puede analizar el programa V.9. En la primera línea, el comando echo redirige
su salida (>) hacia un archivo denominado Users, con el objetivo de ponerle un encabe-
zado al listado que se guardará en ese archivo: echo “Login Name Home Shell ” > Users
En la instrucción siguiente, la salida del comando cat passwd se entuba (|) hacia la
sentencia while (que en realidad es un comando); después, cada iteración de while hace
las acciones siguientes: lee una línea del archivo passwd (read Data), se extraen los cam-
pos 1, 5, 6 y 7, y se asignan a las variables login, name, home y Shell, respectivamente,
para después redirigir esos valores hacia el final del archivo Users (>>). Obsérvese que
la orden name=$(echo “$Data ” | cut -d : -f 5 | cut -d , -f 1) es diferente a las demás,
debido a que el campo 5 de cada entrada del archivo passwd contiene una información
variada, separada por comas y solo se desea su primer campo (cut -d , -f 1), es decir, el
nombre del usuario (no es lo mismo que el login).
Sentencia until
Sintaxis:
until list1; do list2; command; done
La sentencia until es idéntica a la sentencia while, pero en este caso se ejecuta command
hasta que el código de salida de list1 devuelva un valor distinto de cero.
quedará con una cadena que contiene el primer comando, con todas sus opciones, y la
variable cmd1 contendrá el segundo comando; ambos se ejecutan con la orden $cmd y
$cmd1 respectivamente; por ejemplo, si los argumentos que se le pasan al programa
son: ls -l -a ps, la variable cmd contendrá la cadena ls -l -a y la variable cmd1 contendrá
la cadena ps.
El estado de salida de las sentencias while y until es el estado de salida del último
comando que se ejecute o es cero si no se ejecuta ningún comando.
Ejercicio
Transforme los programas V.7 y V.8 para usar las sentencias while y until en susti-
tución de la sentencia for.
Sentencia select
Sintaxis:
select name [ in word1…wordn ] ; do command ; done
La sentencia select expande la lista de palabras (word1…wordn) que siguen a in, ge-
nerando una lista de ítems que se imprimen, en líneas separadas, por la salida de error
estándar; las líneas se numeran a partir de 1. Si se omite in word, se imprimen los pará-
metros posicionales. Debajo de la lista se imprime el símbolo del prompt PS3. Después
de esas acciones, select queda en espera de que el usuario teclee su selección (seguida de
cambio de línea), y después de que se haga una selección, se ejecuta command.
Si la línea leída:
* Contiene uno de los números, la variable name recibe la palabra asociada a esa
etiqueta.
Lo primero que hace el programa es fijar un valor para la variable PS3 y después se
usa la sentencia select, a la cual se asocia una lista de tres valores (separados por espa-
cios: Cuba, Colombia, “Reino Unido ”). La ejecución de select provoca que se impriman,
por la terminal, cada uno de los elementos de la lista, precedidos por un número y un
paréntesis cerrado, tras lo cual se imprime el contenido de la variable PS3. Observe la
salida del programa V.10 a continuación:
1. Cuba
2. Colombia
3. Reino Unido
¿A cuál país le gustaría viajar?
* El usuario teclea fin de archivo (ctrl d), select finaliza y, como no hay ninguna otra
acción en el código, el programa también finaliza.
En este caso, la variable name quedó con valor NULL, de ahí que el mensaje no
contenga el nombre del país.
202 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
El programa V.12 es un poco “más complejo” y hace algo útil. Su propósito es mos-
trar alguna información acerca del sistema, para lo cual ofrece varias opciones que se
listan por medio de la sentencia select.
El usuario debe elegir una de esas opciones, y luego la variable Var se instancia con
el valor de la palabra asociada a la opción elegida; por ejemplo, si el usuario teclea 1,
la variable Var tomará el valor ‘Datos de conexión ’. Después de que el usuario elige su
respuesta, se ejecuta la sentencia case, la cual contiene una etiqueta por cada una de las
palabras que le siguen a la palabra in en la sentencia select, de modo que si el usuario te-
cleó 1, la sentencia case ejecuta todas las instrucciones que están asociadas a la etiqueta
‘Datos de conexión’.
A continuación, se analizan todas las acciones asociadas a la etiqueta ‘Datos de cone-
xión ’, de la sentencia case del programa V.12.
En el comando Data= “$(ifconfig | grep ‘inet addr:’ | grep -v 127) ”, se le asigna a la
variable de usuario Data el resultado de la ejecución del comando ifconfig. Este coman-
do brinda varias líneas de información, pero solo interesa en este caso conocer datos
relacionados con el direccionamiento ip, por eso su salida se entuba dos veces (con el
propósito de filtrarla):
* El primer entubamiento es hacia el comando grep (grep ‘inet addr: ’ ), el cual hace
un filtraje para quedarse solo con las líneas que contengan la cadena ‘inet addr: ’; son
dos, por ejemplo (no tienen que coincidir con las que obtenga el lector):
inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
inet addr:127.0.0.1 Bcast:255.0.0.0
Programación en Shell script / 203
* Después, el nuevo resultado se entuba hacia grep -v 127 para desechar las líneas
(opción -v) que contengan 127. De modo que la variable Data solo recibe la cadena
asociada a la dirección IP básica, es decir, la primera.
El segundo comando de esta etiqueta hace un filtraje para dejar en la variable IP la
dirección ip de la máquina. Para esto, se usa el comando echo que imprime el contenido
de la variable Data y lo entuba hacia el comando cut, el cual toma como delimitador al
signo de dos puntos (-d :) para elegir el segundo campo (-f 2) de la cadena (10.0.2.15
Bcast). Ese resultado se entuba de nuevo hacia el comando sed (sed s/Bcast//), el cual
sustituye la cadena Bcast por nada (s/Bcast//), obteniendo la dirección ip (10.0.2.15).
En los dos comandos siguientes, se obtienen (de forma similar) la dirección de men-
sajes de difusión (broadcast) y la máscara. Por último, se imprime la información conte-
nida en las variables (IP, Bcast y Mask).
* Sintaxis de break:
break [n]
El programa V.13 no tiene mucha utilidad, el único objetivo es percatarse del efecto
que produce la sentencia break. Obsérvese que aunque el ciclo más exterior se debe re-
petir hasta el valor 12, el uso de break en el ciclo interior provoca que solo se impriman
los valores del 1 al 4.
204 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
* Sintaxis de continue:
continue [n]
La sentencia continue salta al inicio del ciclo o salta al inicio del ciclo n veces para
hacer una nueva iteración, abandonando la iteración actual.
El programa V.14 es una modificación del programa V.13, pero se ha sustituido la sen-
tencia break por continue y es necesario poner la orden ((ctrl++)) dentro de la sección
then de la sentencia if o se producirá un ciclo infinito que constantemente imprimirá 4.
V.3.2 Arreglos
Un arreglo en Bash es una variable que contiene múltiples valores, y a diferencia de
otros lenguajes, pueden contener valores de diferentes “tipos” y sus elementos no tienen
que estar contiguos.
El índice inferior de los arreglos es cero y no existe un límite para el índice superior.
Los arreglos en Shell script solo pueden tener una dimensión.
* Sintaxis de declaración:
name[expr]=value
name=(lista de valores separados por espacios)
declare -a name
Donde name es el nombre del arreglo y expr es una expresión que tiene que dar
como resultado un valor entero.
Ejemplo:
Array[2]=12; Array[4]=10
10. Debe observarse que en el arreglo Array se han dejado elementos sin inicializar y
por tal motivo esos elementos tienen el valor NULL.
Para referirse al contenido del arreglo, se usa la notación de llaves; por ejemplo, el
siguiente fragmento de código imprime el arreglo Array, inicializado antes:
for((i=0; i <=5; i++))
do
echo -n “Array[$i] es ${Array[i]}, “
done
A diferencia de muchos otros lenguajes, los arreglos en Bash pueden contener dis-
tintos “tipos de datos”. Observe el programa V.15 en el que se usan dos ciclos for: el
primero imprime los elementos del arreglo B y el segundo multiplica cada elemento
por tres, B[$i]*3.
Debe observarse que los nuevos valores de los elementos B[1] y B[2] son cero,
debido a que no es posible multiplicar una cadena por un número.
Para determinar la cantidad de elementos de un arreglo, se usan las expresiones
${#name[@]} y ${#name[*]}, donde name es el nombre del arreglo. Debe señalarse
que esa longitud está determinada por la cantidad de elementos que se han inicializado
y no por el índice mayor. Observe el siguiente segmento de código:
B[0]=12; B[10]=23
echo “La longitud del arreglo B calculada usando la forma \${#b[@]} es ${#b[@]}”
echo “La longitud del arreglo B calculada usando la forma \${#b[*]} es ${#b[*]}”
Es decir que el arreglo B tiene solo dos elementos, pero no están contiguos. Este no
es el comportamiento usual de los arreglos en otros lenguajes y por eso no se podría
hacer un ciclo for en la forma for((i=0; i<=2; i++)) para referirse a sus elementos, to-
mando en cuenta el valor de la variable de control de ciclo i.
Esta sección se termina con el programa V.17, el cual usa otras facilidades para el tra-
bajo con arreglos. El programa tiene una buena cantidad de comentarios68; no obstante,
a continuación se detallan algunas cosas:
1. Cada línea de salida del comando who produce algo como lo siguiente:
usuario terminal año-mes-día.
2. Esa salida se entuba hacia el comando cut con la orden: who | cut -d ‘-’ -f 1. Esto
hace que cut filtre la entrada escogiendo su primer campo (field), -f 1; los campos
estarán separados por el delimitador (delimiter) guion (-), -d ‘-’. Lo anterior hace
que en la orden: login=(`who | cut -d ‘-’ -f 1`) se construya un arreglo (observe los
paréntesis) que tendrá los elementos en la forma: usuario terminal año, es decir,
los nombres de los usuarios, la terminal desde la cual están conectados al sistema
y el año (primer campo de la fecha en que se conectaron).
3. Después de haber obtenido este primer arreglo, se ejecuta el comando:
login=(`echo “${login[*]/2016/} ”`), el cual reconstruye el arreglo login elimi-
nando todos los campos que contienen el año (en este caso, 2016), de modo que los
elementos del arreglo toman ahora la forma: usuario terminal.
4. El ciclo for, for((i=0; i<size; i=$i+2)), recorre el nuevo arreglo login, de dos en
dos, para imprimir en cada iteración: el identificador del usuario ${login[$i]} y la
terminal desde la que está conectado ${login[$i+1]}.
68 Aunque es importante comentar los programas, es imprescindible programar lo más claro posible, de manera que el código se
pueda leer fácilmente.
208 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
V.3.3 Funciones
El lenguaje Bash también permite definir funciones de usuarios. La definición de una
función debe preceder a su llamada para que el intérprete conozca, antes de la llamada,
el lugar al que deberá saltar.
* Sintaxis de definición:
function name { ... }
name () { ... }
Los parámetros pasados a una función se refieren dentro de ella en forma de pa-
rámetros posicionales y solo se pasan por valores (aunque hay algunos “trucos” para
pasarlos por referencia). El programa V.18 muestra un ejemplo elemental para trabajar
con funciones que reciben parámetros.
Programación en Shell script / 209
Las funciones siempre retornan un valor que se denomina “estado de salida”. El valor
implícito que se retorna es el estado de salida del último comando que se ejecute, pero
se puede retornar un valor explícitamente si se usa la sentencia return.
El valor del estado de salida de una función se puede usar dentro de un programa
script a través de la variable especial ?. Debe recordarse que esa variable también de-
vuelve el estado de salida del programa script.
Los lenguajes Shell script que acompañan a los SO tipo Unix se caracterizan por la
diversidad de herramientas que ofrecen.
Otros SO proporcionan herramientas similares a los lenguajes Shell script de los
SO tipo Unix, pero la potencia de estas herramientas en Unix se considera de las más
poderosas.
Existen varios Shell que se pueden usar en los SO tipo Unix, por ejemplo: Bash shell,
Bourne shell, C-shell, Korn shell, etc. Asociado a cada uno de ellos, existen un lenguaje
Shell script que posee características similares a los demás Shell, pero en esencia son
distintos. De ahí que es muy recomendable iniciar todo programa Shell script con la
línea especial #!, que especifica el Shell que deberá interpretar el programa.
El Bash es el lenguaje Shell script que se ha usado en este capítulo debido a que, es
quizás el más popular y versátil de esos lenguajes entre los SO tipo Unix (es una opinión
personal del autor con la cual se puede estar en desacuerdo).
210 \ FUNDAMENTOS DE SISTEMAS OPERATIVOS
* Identar todo el programa de una manera homogénea y clara, como cuando se escribe
un texto cualquiera.
* Usar convenios estándares para los nombres de las variables, de modo que para el
caso del Shell script se pueda distinguir entre las variables: de usuario, de ambiente
y posicionales.
En el lenguaje Shell script, se pueden usar diversas facilidades; cabe destacar las
siguientes:
En este capítulo, se ha hecho un estudio sintetizado del lenguaje Shell script. Exis-
ten diversos libros que estudian el lenguaje más ampliamente, aunque algunos de ellos
se enfocan en la descripción del lenguaje en sí y los ejemplos que se citan no son muy
prácticos.
Aunque el capítulo no es muy amplio, se ha tratado de que los ejemplos sean aplica-
bles en entornos de SO, debido a que el libro es para enseñar precisamente esa materia.
No se finaliza este capítulo con una sección de ejercicios propios, como se hizo en los
restantes, dado que el planteamiento de ejercicios de programación en entornos de SO
puede hacerse complejo, y se ha preferido emitir las siguientes recomendaciones:
1. Busque los programas Shell script que acompañan a su SO tipo Unix. Estúdielos
profundamente, de modo que esté consciente de que los ha entendido.
Nota. En su directorio de inicio encontrará algunos. Si está usando el Bash, podrá
ver los archivos ocultos .bash_history, .bash_logout, bashrc y en el directorio /etc
encontrará algunos otros, tales como bash.bashrc, que son complementos de los
anteriores.
Programación en Shell script / 211