Preparación para el examen 102 de LPI, Tema 109: Shells, scripting, programación y compilación

Tema 109 del examen de Administración Nivel Junior (LPIC-1)

En este tutorial, Ian Shields continúa preparándolo para rendir el® Examen 102 de la Administración Nivel Junior de Linux Professional Institute (LPIC-1). En éste, el quinto de una serie de nueve tutoriales, Ian lo introduce en el mundo del shell Bash, y le presenta los scripts y la programación del shell Bash. Al finalizar este tutorial, usted sabrá cómo personalizar el entorno de su shell, cómo usar estructuras de programación de shell para crear funciones y scripts, cómo activar y desactivar variables de entorno, y cómo usar los diversos scripts de inicio de sesión.

Ian Shields, Senior Programmer, IBM

Ian ShieldsIan Shields trabaja en múltiples proyectos Linux para la zona Linux dev developerWorks. Es Senior Programmer de IBM en el Research Triangle Park (RTP), Carolina del Norte. Ingresó a IBM en Canberra, Australia, como Systems Engineer en 1973, y desde entonces se dedica a sistemas de comunicaciones y computación ubicua en Montreal, Canadá, y en el RTP de Carolina del Norte. Es propietario de numerosas patentes y publicó diversos trabajos. Tiene una diplomatura en Matemática Pura y Filosofía de la Universidad Nacional de Australia. Es Máster y Doctor en Ciencias de la Computación de la Universidad Estatal de Carolina del Norte.



23-11-2009

Antes de comenzar

Sepa qué pueden enseñarle estos tutoriales y cómo obtener el máximo provecho de los mismos.

Acerca de esta serie

El Linux Professional Institute (LPI) brinda certificaciones a administradores de sistemas Linux en dos niveles: nivel junior (también denominado "nivel de certificación 1") y nivel intermedio (también denominado "nivel de certificación 2"). Para alcanzar el nivel de certificación 1, usted deberá aprobar los exámenes 101 y 102; para alcanzar el nivel de certificación 2, deberá aprobar los exámenes 201 y 202.

developerWorks ofrece tutoriales que lo ayudarán a prepararse para cada uno de estos cuatro exámenes. Cada examen se ocupa de diversos temas, y cada tema cuenta con su correspondiente tutorial de autoestudio en developerWorks. Para el examen 102 de LPI, los nueve temas y sus correspondientes tutoriales de developerWorks son:

Tabla 1. Examen 102 de LPI: Tutoriales y temas
Tema del examen 102 de LPITutorial de developerWorksResumen del tutorial
Tema 105Preparación para el examen 102 de LPI:
Kernel
Aprenda a instalar y realizar el mantenimiento de los kernels y los módulos de los kernels de Linux.
Tema 106Preparación para el examen 102 de LPI:
Arranque, inicialización, interrupción y niveles de ejecución
Aprenda a arrancar un sistema, a establecer parámetros de kernel, y a detener o reiniciar un sistema.
Tema 107Preparación para el examen 102 de LPI:
Impresión
Aprenda a gestionar impresoras, a imprimir colas y trabajos de impresión de los usuarios en un sistema Linux.
Tema 108Preparación para el examen 102 de LPI:
Documentación
Aprenda a usar y administrar la documentación local, a encontrar documentos en Internet y a usar mensajes de inicio de sesión automáticos para notificar a los usuarios sobre los eventos del sistema.
Tema 109Preparación para el examen 102 de LPI:
Shells, scripting, programación y compilación
(Este tutorial.) Aprenda cómo personalizar los entornos de shell para cumplir con las necesidades del usuario, a escribir funciones Bash para secuencias de comandos frecuentemente utilizadas, a escribir scripts simples nuevos usando la sintaxis del shell para realizar bucles y verificaciones y a personalizar los scripts existentes. Vea los objetivos detallados más adelante.
Tema 111Preparación para el examen 102 de LPI:
Tareas administrativas
Próximamente.
Tema 112Preparación para el examen 102 de LPI:
Principios básicos de las conexiones de red
Próximamente.
Tema 113Preparación para el examen 102 de LPI:
Servicios de red
Próximamente.
Tema 114Preparación para el examen 102 de LPI:
Seguridad
Próximamente.

Para aprobar los exámenes 101 y 102 (y obtener el nivel de certificación 1), usted deberá:

  • Trabajar en la línea de comandos Linux
  • Realizar tareas sencillas de mantenimiento: ayudar a los usuarios, agregar usuarios a un sistema de mayor tamaño, realizar tareas de backup y restauración, e interrumpir y reiniciar el sistema
  • Instalar y configurar una estación de trabajo (que incluya X) y conectarla a una LAN, o conectar una PC independiente a Internet por medio de un módem

Para seguir preparándose para el nivel de certificación 1, vea los tutoriales de developerWorks para los exámenes 101 y 102 de LPI, así como el conjunto completo de tutoriales de LPI en developerWorks.

El Linux Professional Institute no avala ninguna técnica o material de preparación de exámenes en particular. Para obtener más detalles, por favor escriba a info@lpi.org.

Acerca de este tutorial

Bienvenido a "Shells, scripting, programación y compilación," el quinto de una serie de nueve tutoriales diseñados para prepararlo para el examen 102 de LPI. En este tutorial, usted aprenderá a usar el shell Bash, a utilizar las estructuras de programación de shell para crear funciones y scripts, a personalizar su entorno de shell, a establecer y deshabilitar variables de entorno y a usar los diversos scripts de inicio de sesión.

El título de este tutorial es idéntico al tema correspondiente en el examen 102 de LPI, y por lo tanto, incluye "programación y compilación," si bien los objetivos de LPI limitan la "programación" a lo necesario para escribir funciones y scripts de shells. Por otro lado, el tema no incluye objetivos para compilación de programas.

Este tutorial se encuentra organizado en función de los objetivos que LPI establece para este tema. A grandes rasgos, usted deberá esperar que en el examen haya más preguntas para los temas de mayor valor.

Tabla 2. Shells, scripting, programación y compilación: Objetivos del examen que se tratan en este tutorial
Objetivo del examen de LPIValor del objetivoResumen del objetivo
1.109.1
Personalización y uso del entorno del shell
Valor 5Personalice los entornos de los shells para cumplir con las necesidades de los usuarios. Establezca las variables del entorno (al inicio de la sesión o al generar un shell nuevo). Escriba funciones Bash para secuencias de comandos usadas con frecuencia.
1.109.2
Personalización o programación de scripts simples
Valor 3Escriba scripts Bash simples y personalice los existentes.

Requisitos previos

Para aprovechar al máximo este tutorial, usted deberá contar con un conocimiento básico de Linux y con un sistema Linux en funcionamiento donde pueda practicar los comandos tratados en este tutorial.

Este tutorial se basa en los contenidos de los tutoriales previos de esta serie de LPI, de manera que será conveniente que revise en primer lugar los tutoriales para el examen 101. En particular, usted deberá estar familiarizado con el material del tutorial denominado " Preparación para el examen 101 de LPI (tema 103): Comandos GNU y UNIX ", ya que muchos de los bloques sobre los cuales se basa este tutorial se tratan en el anteriormente mencionado, especialmente en la sección "Uso de la línea de comandos."

Las diferentes versiones de un programa pueden dar formato a los datos de salida de manera diferente, de manera que es posible que los resultados que usted obtenga difieran levemente de los listados y las figuras que aparecen en este tutorial.


Personalización de shells

Esta sección se ocupa del material del tema 1.109.1 del examen 102 de la Administración Nivel Junior (LPIC-1). El tema tiene un valor de 5.

En esta sección, aprenda a:

  • Activar y desactivar variables de entorno
  • Usar perfiles para configurar variables de entorno al inicio de la sesión o cuando se genera un nuevo shell
  • Escribir l funciones de shell para las secuencias de comandos frecuentemente utilizadas
  • Uso de listas de comandos

Shells y entornos

Antes del nacimiento de las interfaces gráficas, los programadores usaban una terminal de impresión o una terminal de visualización para ASCII para conectarse a un sistema UNIX® . La terminal de impresión les permitía escribir los comandos, y los datos de salida generalmente se imprimían en papel continuo. La mayoría de las terminals de visualización para ASCII contenían 80 caracteres por línea y alrededor de 25 líneas en pantalla, si bien existían terminales de mayor y menor tamaño. Cuando los programadores escribían un comando y presionaban Enter, el sistema interpretaba y luego ejecutaba dicho comando.

Si bien esto puede parecer algo primitivo en nuestra era de interfaces geográficas de arrastrar y soltar, fue un enorme adelanto respecto de tener que escribir un programa, perforar las tarjetas, compilar el conjunto de tarjetas y ejecutar el programa. Con el nacimiento de los editores, los programadores pudieron incluso crear programas como imágenes de tarjeta y compilarlas en una sesión de terminal.

El flujo de caracteres escritos en una terminal brindaba un flujo de datos estándar de entrada al shell, y el flujo de caracteres que devolvía el shell, ya sea en papel o en el monitor, representaba los datos de salida estándar.

El programa que acepta y ejecuta los comandos se denomina shell. Brinda una capa entre usted y las complejidades de un sistema operativo. Los shells de UNIX y Linux son extremadamente poderosos en el sentido que usted puede elaborar operaciones bastante complejas al combinar funciones básicas. Con el uso de construcciones de programación, usted podrá entonces elaborar funciones para su ejecución directa en el shell o guardar funciones como scripts de shell para poder reutilizarlas una y otra vez.

En ocasiones, usted deberá ejecutar comandos antes de que el sistema haya arrancado lo suficiente como para permitir las conexiones de la terminal, y en otras, deberá ejecutar los comandos de manera periódica, esté o no conectado. Cierto tipo de shell también puede realizar estas tareas por usted. Los datos de entrada y salida estándar no deben necesariamente provenir o dirigirse a un usuario real de la terminal.

En esta sección, usted aprenderá más acerca de los shells. En particular, aprenderá sobre el shell bash o Bourne-again, el cual es una versión mejorada del shell Bourne original, junto con algunas funciones de otros shells y algunos cambios respecto del shell Bourne que lo hacen más adaptable a POSIX.

POSIX significa Portable Operating System Interface for uniX (Interfaz portátil de sistema operativo para Unix), y es una serie de estándares IEEE conocidos colectivamente como IEEE 1003. El primero de ellos fue el Estándar IEEE 1003.1-1988, emitido en 1988. Otros shells famosos incluyen el shell Korn (ksh), el shell C (csh) y su derivado tcsh, el shell Almquist (ash) y su derivado para Debian (dash). Usted deberá tener cierto conocimiento sobre muchos de estos shells, aunque más no sea para reconocer cuando un script determinado requiere características de alguno de ellos.

Muchos aspectos de su interacción con la computadora serán iguales de una sesión a otra. Recuerde que en el tutorial " Preparación para el examen 101 de LPI (tema 103): Comandos GNU y UNIX " decíamos que cuando usted se encuentra funcionando en un shell Bash, tiene un entorno de shell, que define cosas como la forma de su prompt, su directorio principal, su directorio en funcionamiento, el nombre de su shell, los archivos que ha abierto, las funciones que ha definido, etc. El entorno se vuelve disponible a todos los procesos del shell. Los shells, incluyendo el bash, le permiten crear y modificar shellvariables (variables de shell), que usted podrá exportar a su entorno para que sean utilizadas por otros procesos que se ejecutan en el shell o por otros shells que usted haya generado a partir del shell actual.

Tanto las variables de entorno como las variables de shell tienen un nombre. Usted hace referencia al valor de una variable colocando un prefijo a su nombre con el signo '$'. Algunas de las variables de entorno bash más comunes que se encuentran establecidas para usted se muestran en la Tabla 3.

Tabla 3. Variables comunes de entorno bash
NombreFunción
USERNombre del usuario conectado
UIDID numérica de usuario del usuario conectado
HOMEDirectorio principal del usuario
PWDDirectorio actual de trabajo
SHELLNombre del shell
$La ID del proceso (o PID del shell Bash en ejecución (u otro proceso)
PPIDID de proceso del proceso que inició este proceso (es decir, id del proceso principal)
?Código de salida del último comando

Definición de variables

En el shell Bash, usted deberá crear o definir una variable de shell escribiendo un nombre inmediatamente seguido de un signo igual (=). Los nombres de variables (o identificadores) son palabras formadas solamente por caracteres alfanumérico y guiones bajos, que comienzan con un carácter alfabético o un guión bajo. Las variables distinguen entre mayúsculas y minúsculas, por lo tanto var1 y VAR1 son variables diferentes. Por convención, las variables, especialmente las variables exportadas, se escriben en mayúscula, aunque esto no representa un requerimiento. Técnicamente, $$ y $? son parámetros de shell más que variables. Sólo es posible hacer referencia a los mismos; no se les puede asignar un valor.

Cuando usted crea una variable de shell, a menudo será conveniente exportarla al entorno para que quede disponible a otros procesos que usted pueda iniciar desde este shell. Las variables que usted exporta no están disponibles para un shell primario. Usted deberá usar el comando exportar para exportar un nombre de variable. Bash permite el atajo de asignar y exportar en un solo paso.

Para ilustrar la asignación y la exportación, ejecutemos el comando bash mientras estamos en el shell Bash, y luego ejecutemos el shell Korn (ksh) del nuevo shell Bash. Usaremos el comando ps para visualizar la información sobre el comando que se está ejecutando.

Listado 1. Definición y exportación de variables de shell
[ian@echidna ian]$ ps -p $$ -o "pid ppid cmd"
  PID  PPID CMD
30576 30575 -bash
[ian@echidna ian]$ bash
[ian@echidna ian]$ ps -p $$ -o "pid ppid cmd"
  PID  PPID CMD
16353 30576 bash
[ian@echidna ian]$ VAR1=var1
[ian@echidna ian]$ VAR2=var2
[ian@echidna ian]$ export VAR2
[ian@echidna ian]$ export VAR3=var3
[ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3
var1 var2 var3
[ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 $SHELL
var1 var2 var3 /bin/bash
[ian@echidna ian]$ ksh
$ ps -p $$ -o "pid ppid cmd"
  PID  PPID CMD
16448 16353 ksh
$ export VAR4=var4
$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
var2 var3 var4 /bin/bash
$ exit
$ [ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
var1 var2 var3 /bin/bash
[ian@echidna ian]$ ps -p $$ -o "pid ppid cmd"
  PID  PPID CMD
16353 30576 bash
[ian@echidna ian]$ exit
[ian@echidna ian]$ ps -p $$ -o "pid ppid cmd"
  PID  PPID CMD
30576 30575 -bash
[ian@echidna ian]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
/bin/bash

Notas:

  1. En el inicio de la secuencia, el shell Bash tenía la PID 30576.
  2. El segundo shell Bash tiene la PID 16353, y su shell primario es PID 30576, el shell Bash original.
  3. Creamos VAR1, VAR2, y VAR3 en el segundo shell Bash, pero sólo exportamos VAR2 y VAR3.
  4. En el shell Korn, creamos VAR4. El comando echo mostró valores sólo para VAR2, VAR3, y VAR4, confirmando que VAR1 no fue exportada. ¿Le sorprendió descubrir que el valor de la variable SHELL no se había modificado, incluso cuando había cambiado el prompt? Usted no siempre podrá confiar en que SHELL le diga en qué shell usted está funcionando, pero el comando ps sí le dice el comando real. Observe que ps coloca un guión (-) delante del primer shell Bash para indicar que éste es el shell de inicio de sesión.
  5. Nuevamente, en el segundo shell Bash, podemos ver a VAR1, VAR2 yVAR3.
  6. Finalmente, cuando volvemos al shell original, veremos que ya no existe ninguna de nuestras variables.

El Listado 2 muestra lo que probablemente usted vea en algunas de estas variables bash.

Listado 2. Entorno y variables de shell
[ian@echidna ian]$ echo $USER $UID
ian 500
[ian@echidna ian]$ echo $SHELL $HOME $PWD
/bin/bash /home/ian /home/ian
[ian@echidna ian]$ (exit 0);echo $?;(exit 4);echo $?
0
4
[ian@echidna ian]$ echo $$ $PPID
30576 30575

Entornos y el shell C

En los shells tales como C y tcsh, usted deberá usar el comando set para definir las variables de su shell, y el comando setenv para definir y exportar variables. La sintaxis difiere levemente de las del comando export como se muestra en el Listado 3. Observe los signos igual (=) cuando se usa set.

Listado 3. Definición de variables de entorno en el shell C
ian@attic4:~$ echo $VAR1 $VAR2

ian@attic4:~$ csh
% set VAR1=var1
% setenv VAR2 var2
% echo $VAR1 $VAR2
var1 var2
% bash
ian@attic4:~$ echo $VAR1 $VAR2
var2

Cómo desactivar variables

Usted puede quitar una variable del shell Bash mediante el comando unset. Puede usar la opción -v para asegurarse de que está eliminando una definición de la variable. Es posible que las funciones tengan el mismo nombre que las variables, de manera que usted deberá usar -f si desea eliminar una definición de función. Sin -f o -v, el comando unset de bash elimina una definición de variable existente; de otro modo, elimina una definición de función existente. (Las funciones serán tratadas en más detalle en la sección Shell functions. )

Listado 4. Comando unset de bash
ian@attic4:~$ VAR1=var1
ian@attic4:~$ VAR2=var2
ian@attic4:~$ echo $VAR1 $VAR2
var1 var2
ian@attic4:~$ unset VAR1
ian@attic4:~$ echo $VAR1 $VAR2
var2
ian@attic4:~$ unset -v VAR2
ian@attic4:~$ echo $VAR1 $VAR2

Bash está predeterminado para tratar las variables desactivadas como si tuvieran un valor vacío, por lo tanto es posible que usted se pregunte por qué debería desactivar una variable en lugar de simplemente asignarle un valor vacío. Bash y muchos otros shells le permiten generar un error si se hace referencia a una variable no definida. Use el comando set -u para generar un error para variables no definidas, y set +u para desactivar la advertencia. El Listado 5 ilustra estos puntos.

Listado 5. Generación de errores con variables desactivadas
ian@attic4:~$ set -u
ian@attic4:~$ VAR1=var1
ian@attic4:~$ echo $VAR1
var1
ian@attic4:~$ unset VAR1
ian@attic4:~$ echo $VAR1
-bash: VAR1: unbound variable
ian@attic4:~$ VAR1=
ian@attic4:~$ echo $VAR1

ian@attic4:~$ unset VAR1
ian@attic4:~$ echo $VAR1
-bash: VAR1: unbound variable
ian@attic4:~$ unset -v VAR1
ian@attic4:~$ set +u
ian@attic4:~$ echo $VAR1

ian@attic4:~$

Observe que no es un error desactivar una variable que no existe, incluso cuando se ha especificado set -u.

Perfiles

Cuando usted inicia una sesión en un sistema Linux, su id cuenta con un shell predeterminado, que es su shell de inicio de sesión. Si este shell es bash, ejecutará muchos scripts de perfil antes de brindarle el control. Si existe /etc/profile, se ejecutará en primer lugar. Según la distribución que usted tenga, se podrán ejecutar otros scripts del árbol /etc, por ejemplo, /etc/bash.bashrc o /etc/bashrc. Una vez que se han ejecutado los scripts del sistema, se ejecutará un script de su directorio principal si existiese. Bash busca los archivos ~/.bash_profile, ~/.bash_login, y ~/.profile en ese orden. Se ejecuta el primero que se encuentra.

Cuando usted termina la sesión, bash ejecuta el script ~/.bash_logout de su directorio principal si el mismo existiese.

Una vez que usted ha iniciado sesión y se encuentra usando bash, podrá iniciar otro shell, denominado shell interactivo para ejecutar un comando, por ejemplo, para ejecutar un comando en el segundo plano. En este caso, bash ejecuta solamente el script the ~/.bashrc, suponiendo que el mismo exista. Es común buscar este script en su ~/.bash_profile, de manera que usted sólo podrá ejecutarlo al inicio de la sesión y cuando se inicia un shell interactivo, con comandos tales como los que se muestran en el Listado 6.

Listado 6. Búsqueda de ~/.bashrc
# include .bashrc if it exists if [ -f ~/.bashrc ]; then
                    . ~/.bashrc fi

Usted puede obligar a bash a leer perfiles como si fuera un shell de inicio de sesión con la opción --login. Si no desea ejecutar los perfiles para un shell de inicio de sesión, deberá especificar la opción --noprofile. De manera similar, si desea desactivar la ejecución del archivo ~/.bashrc para un shell interactivo, inicie bash con la opción --norc. También puede obligar a bash a usar un archivo que no sea ~/.bashrc si especifica la opción --rcfile con el nombre del archivo que desea usar. El Listado 8 ilustra la creación de un archivo simple denominado testrc y cómo se usa con la opción --rcfile. Observe que la variable VAR1 no no está definida en el shell exterior, sino que ha sido definida para el shell interior por el testrc.

Listado 7. Uso de la opción --rcfile
ian@attic4:~$ echo VAR1=var1>testrc ian@attic4:~$
                    echo $VAR1 ian@attic4:~$ bash --rcfile testrc ian@attic4:~$ echo $VAR1
                    var1

Inicio de bash de otras maneras

Además de los modos estándar que existen para ejecutar bash desde una terminal como se mencionó anteriormente, también se puede usar bash de otras maneras.

A menos que usted origine un script para que se ejecute en el shell actual, bash se ejecutará en su propio shell no interactivo, y no se leerán los perfiles anteriormente mencionados. No obstante, si se define la variable BASH_ENV, bash expande el valor y lo toma como nombre de un archivo. Si el archivo existe, entonces bash ejecuta el archivo antes independientemente de cualquier script o comando que se esté ejecutando en el shell no interactivo. El Listado 8 usa dos archivos simples para ilustrar este caso.

Listado 8. Uso de BASH_ENV
ian@attic4:~$ cat testenv.sh
#!/bin/bash
echo "Testing the environment"
ian@attic4:~$ cat somescript.sh
#!/bin/bash
echo "Doing nothing"
ian@attic4:~$ export BASH_ENV="~/testenv.sh"
ian@attic4:~$ ./somescript.sh
Testing the environment
Doing nothing

Los shells no interactivos pueden también iniciarse con la opción --login para forzar la ejecución de los archivos de perfil.

Bash también puede iniciarse en modo POSIX con la opción --posix. Este modo es similar al shell no interactivo, excepto en que el archivo a ejecutar está determinado en la variable de entorno ENV.

En los sistemas Linux, es común ejecutar bash como /bin/sh usando un vínculo simbólico. Cuando bash detecta que está siendo ejecutado con el nombre sh, intenta seguir el comportamiento de inicio del antiguo shell Bourne al tiempo que se adapta a los estándares POSIX. Cuando se ejecuta como un shell de inicio de sesión, bash intenta leer y ejecutar /etc/profile y ~/.profile. Cuando se ejecuta como un shell interactivo con el comando sh, bash intenta ejecutar el archivo especificado por la variable ENV como lo hace cuando es invocado en modo POSIX. Cuando se ejecuta de manera interactiva como sh, solamente usa un perfil especificado por la variable ENV; siempre se ignorará la opción --rcfile.

Si bash es invocado por el daemon remoto del shell, se comportará como un shell interactivo, y usará el archivo ~/.bashrc si existiera.

Alias de los shell

El shell Bash le permite definir alias para los comandos. Los motivos más comunes para la creación de alias son brindar un nombre alternativo para el comando, o proporcionar algunos parámetros predeterminados para el comando. El editor vi ha sido durante años un elemento básico de UNIX y los sistemas Linux. El editor vim (Vi IMproved) es similar a vi, pero presenta muchas mejoras. De manera que si usted está escribiendo "vi" cuando desea un editor, pero en realidad prefiere usar vim, entonces necesitará un alias. El Listado 9 muestra cómo usar el comando alias para lograr esta tarea.

Listado 9. Uso de vi como alias de vim
[ian@pinguino ~]$ alias vi='vim'
[ian@pinguino ~]$ which vi
alias vi='vim'
   /usr/bin/vim
[ian@pinguino ~]$ /usr/bin/which vi
/bin/vi

Observe en este ejemplo que si usted usa el comando which para ver dónde reside el programa vi, obtendrá dos líneas de datos de salida: la primera le muestra el alias, y la segunda, la ubicación de vim ( /usr/bin/vim). Sin embargo, si usa el comando which con su ruta completa ( /usr/bin/which), obtendrá la ubicación del comando vi. Si usted dedujo que esto significa que el comando which propiamente dicho posee un alias en este sistema, estará en lo correcto.

También puede usar el comando alias para mostrar todos los alias si lo usa sin opciones o sólo con la opción -p, y podrá mostrar todos los alias para uno o más nombres proporcionando los nombres como argumentos sin asignaciones. El Listado 10 muestra los alias para which y vi.

Listado 10. Alias para which y vi
[ian@pinguino ~]$ alias which vi alias which='alias |
                    /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' alias
                    vi='vim'

El alias para el comando which resulta bastante curioso. ¿Por qué canalizar los datos de salida del comando del alias (sin argumentos) a /usr/bin/which ? Si usted consulta las páginas man sobre el comando which, descubrirá que la opción --read-alias le indica a which que lea una lista de alias desde stdin y que informe las correspondencias en stdout. Esto permite que el comando which informe los alias además de los comandos de su PATH, por lo cual es probable que su distribución lo haya configurado como predeterminado para usted. Esto es bueno, debido a que el shell ejecutará un alias antes de un comando con el mismo nombre. Entonces, ahora que usted sabe esto, podrá verificarlo usando alias which. Además, podrá saber si este tipo de alias ha sido configurado para which ejecutando which which.

Otro uso común para los alias es agregar parámetros a los comandos de manera automática, como vio anteriormente para --read-alias y muchos otros parámetros del comando which. Esta técnica se aplica usualmente para el usuario root con los comandos cp, mv, y rm de manera que se emita un prompt antes de que se detecten o sobrescriban los archivos. Esto puede verse en el Listado 11.

Listado 11. Agregado de parámetros de seguridad
[root@pinguino ~]# alias cp mv rm alias
                    cp='cp -i' alias mv='mv -i' alias rm='rm -i'

Listas de comandos

En el anterior tutorial " Preparación para el examen 101 de LPI (tema 103): Comandos GNU y UNIX," usted aprendió sobre las secuencias o listas de los comandos. Acaba de ver al operador de canalización (|) usado con un alias, y puede también usar listas de comandos. Imagine, a modo de ejemplo, que usted desea que un comando elabore una lista de los contenidos del directorio actual y además, la cantidad de espacio usado por él y todos sus subdirectorios. Lo llamaremos comando lsdu. Entonces, usted simplemente asignará una secuencia de comandos ls y du al lsdu del alias . El Listado 12 muestra un modo erróneo de hacerlo y también el modo correcto. Obsérvelo cuidadosamente antes de leer, y piense en por qué fracasó el primer intento.

Listado 12. Alias para secuencias de comandos
[ian@pinguino developerworks]$ alias lsdu=ls;du -sh # Wrong way
2.9M    .
[ian@pinguino developerworks]$ lsdu
a tutorial  new-article.sh   new-tutorial.sh   readme  tools  xsl
my-article  new-article.vbs  new-tutorial.vbs  schema  web
[ian@pinguino developerworks]$ alias 'lsdu=ls;du -sh' # Right way way
[ian@pinguino developerworks]$ lsdu
a tutorial  new-article.sh   new-tutorial.sh   readme  tools  xsl
my-article  new-article.vbs  new-tutorial.vbs  schema  web
2.9M    .

Usted deberá prestar mucha atención al citar la secuencia completa que conformará el alias. También deberá ser cuidadoso acerca del uso de comillas simples o dobles si cuenta con variables de shell como parte del alias. ¿Desea que el shell expanda las variables cuando se defina o cuando se ejecute el alias? El Listado 13 muestra el modo erróneo de crear un comando personalizado denominado mywd cuya intención es imprimir el nombre de su directorio en funcionamiento

Listado 13. pwd personalizado: intento 1
[ian@pinguino developerworks]$ alias mywd="echo \"My working directory is $PWD\""
[ian@pinguino developerworks]$ mywd
My working directory is /home/ian/developerworks
[ian@pinguino developerworks]$ cd ..
[ian@pinguino ~]$ mywd
My working directory is /home/ian/developerworks

Recuerde que las comillas dobles hacen que bash expanda las variables antes de ejecutar un comando. El Listado 14 usa el comando alias para mostrar cuál es en realidad el alias resultante, por lo cual nuestro error es evidente. El Listado 14 muestra además un modo correcto de definir este alias.

Listado 14. pwd personalizado: intento 2
[ian@pinguino developerworks]$ alias mywd
alias mywd='echo \"My working directory is $PWD\"'
[ian@pinguino developerworks]$ mywd
"My working directory is /home/ian/developerworks"
[ian@pinguino developerworks]$ cd ..
[ian@pinguino ~]$ mywd
"My working directory is /home/ian"

Por fin el éxito.

Funciones de los shells

Los alias le permiten usar una abreviatura o un nombre alternativo para un comando o una lista de comandos. Es posible que haya notado que puede agregar otros elementos, como por ejemplo el nombre del programa que está buscando con el comando which. Cuando se ejecutan los datos que usted ha ingresado, el alias se expande, y cualquier otra cosa que usted escriba posteriormente se agrega a la expansión antes de que se ejecute el comando o la lista final. Esto significa que usted sólo podrá agregar parámetros al final del comando o la lista, y que podrá usarlos sólo con el comando final. Las funciones brindan capacidad adicional, incluyendo la capacidad de procesar los parámetros. Las funciones forman parte de la definición del shell por parte de POSIX. Se encuentran disponibles en shells tales como bash, dash, y ksh, pero no se encuentran en csh o tcsh.

En los próximos párrafos, usted elaborará un comando complejo pieza por pieza, a partir de bloques de construcción menores, y lo refinará en cada paso para convertirlo en una función que más adelante volverá a refinar.

Problema hipotético

Usted puede usar el comando ls para hacer una lista de una variedad de información sobre los directorios y los archivos de su sistema de archivos. Imagine que desea un comando, llamémoslo ldirs, que enumere los nombres de los directorios con datos de salida como los del Listado 15.

Listado 15. Datos de salida del comando ldirs
[ian@pinguino developerworks]$ ldirs *[st]* tools/*a*
                    my dw article schema tools tools/java xsl

Para hacer que las cosas sigan siendo relativamente simples, los ejemplos de esta sección usan los directorios y los archivos del paquete de autor de developerWorks (ver Recursos), el cual usted puede usar, si lo desea, para escribir artículos o tutoriales para developerWorks. En estos ejemplos, usamos el script new-article.sh del paquete para crear una plantilla para un nuevo artículo que denominamos "my dw article".

Al momento de escribir este artículo, la versión del paquete de autor de developerWorks es la 5.6, de manera que si usted usa una versión posterior, es posible que encuentre diferencias. También puede usar sus propios archivos y directorios. El comando ldirs también los manejará. Usted podrá encontrar otros ejemplos de funciones de bash en las herramientas incluidas en el paquete de autor de developerWorks.

Cómo encontrar las entradas de directorios

Ignorando por el momento *[st]* tools/*a*, si usted usa el comando ls con las opciones de color que se muestran en los alias anteriores, verá datos de salida similares a los que muestran en la Figura 1.

Figura 1. Cómo distinguir los archivos y directorios con el comando ls
Distinguishing files and directories with the ls command

En este ejemplo, los directorios se muestran en azul oscuro, si bien esto es un poco difícil de descodificar con las aptitudes que usted ha desarrollado en esta serie de tutoriales. El uso de la opción -l, sin embargo, le dará una pauta de cómo proceder: las listas de los directorios tienen una 'd' al final. De manera que su primer paso puede ser simplemente filtrarlas de las listas largas con grep como se muestra en el Listado 16.

Listado 16. Uso de grep para buscar entradas de directorios
[ian@pinguino developerworks]$ ls -l | grep "^d"
drwxrwxr-x 2 ian ian 4096 Jan 24 17:06 my dw article
drwxrwxr-x 2 ian ian 4096 Jan 18 16:23 readme
drwxrwxr-x 3 ian ian 4096 Jan 19 07:41 schema
drwxrwxr-x 3 ian ian 4096 Jan 19 15:08 tools
drwxrwxr-x 3 ian ian 4096 Jan 17 16:03 web
drwxrwxr-x 3 ian ian 4096 Jan 19 10:59 xsl

Recorte de las entradas de directorios

Quizás le convenga usar awk en lugar de grep para poder en una pasada, filtrar la lista y quitar la última parte de cada línea, que es el nombre del directorio, como se muestra en el Listado 17.

Listado 17. Uso de awk em lugar de grep
[ian@pinguino developerworks]$ ls -l  | awk '/^d/ { print $NF } '
article
readme
schema
tools
web
xsl

El problema que surge con el enfoque del Listado 17 es que no se ocupa del directorio con espacios en su nombre, como por ejemplo "my dw article". Como casi siempre sucede en Linux y en la vida, a menudo existen diversas maneras de resolver un problema. Pero como el objetivo aquí consiste en aprender acerca de las funciones, volveremos a usar grep. Otra de las herramientas que usted vio anteriormente en esta serie es cut, que corta campos de un archivo, incluyendo stdin. Si volvemos a ver el Listado 16, observará que hay ocho campos delimitados vacíos antes del nombre del archivo. El agregado de cut al comando anterior generará datos de salida como los que se muestran en el Listado 18. Observe que la opción -f9- le dice a cut que imprima los campos 9 y superiores.

Listado 18. Uso de cut para recortar nombres
[ian@pinguino developerworks]$ ls -l | grep "^d" |
                    cut -d" " -f9- my dw article readme schema tools web xsl

Hay un pequeño problema en este enfoque que se plantea de manera obvia cuando probamos el comando en el directorio de herramientas en lugar de en el directorio actual como se muestra en el Listado 19.

Listado 19. Problema con cut
[ian@pinguino developerworks]$ ls -l tools | grep "^d" | cut -d" " -f9-
11:25 java
[ian@pinguino developerworks]$ ls -ld tools/[fjt]*
-rw-rw-r-- 1 ian ian  4798 Jan  8 14:38 tools/figure1.gif
drwxrwxr-x 2 ian ian  4096 Oct 31 11:25 tools/java
-rw-rw-r-- 1 ian ian 39431 Jan 18 23:31 tools/template-dw-article-5.6.xml
-rw-rw-r-- 1 ian ian 39407 Jan 18 23:32 tools/template-dw-tutorial-5.6.xml

¿Cómo llegó aquí la marca de tiempo? Los dos archivos de plantillas tienen tamaños de 5 dígitos, mientras el directorio java sólo posee un tamaño de 4 dígitos. Por ende, cut interpretó que el espacio adicional era otro separador de campo.

Use seq para encontrar un punto de corte

El comando cut puede también realizar cortes usando posiciones de caracteres en lugar de campos. En lugar de contar caracteres, el shell Bash posee muchísimos utilitarios que usted podrá emplear. Entonces, podrá probar con los comandos seq y printf para imprimir una regla por encima de su extensa lista de directorios, para encontrar fácilmente dónde debe cortar las líneas de datos de salida. El comando seq toma hasta tres argumentos, que le permiten imprimir todos los números hasta un valor dado, todos los números entre un valor y otro, o todos los números desde un valor, siguiendo la ejecución del comando en función de un valor dado, hasta un tercer valor. En las páginas man encontrará todas las demás maravillas que puede hacer con seq, incluyendo la impresión de números octales y hexadecimales. Por ahora, usaremos seq y printf para imprimir una regla con posiciones marcadas cada 10 caracteres, como se muestra en el Listado 20.

Listado 20. Impresión de una regla con seq y printf
[ian@pinguino developerworks]$ printf "....+...%2.d" `seq 10 10 60`;printf "\n";ls -l
....+...10....+...20....+...30....+...40....+...50....+...60
total 88
drwxrwxr-x 2 ian ian 4096 Jan 24 17:06 my dw article
-rwxr--r-- 1 ian ian  215 Sep 27 16:34 new-article.sh
-rwxr--r-- 1 ian ian 1078 Sep 27 16:34 new-article.vbs
-rwxr--r-- 1 ian ian  216 Sep 27 16:34 new-tutorial.sh
-rwxr--r-- 1 ian ian 1079 Sep 27 16:34 new-tutorial.vbs
drwxrwxr-x 2 ian ian 4096 Jan 18 16:23 readme
drwxrwxr-x 3 ian ian 4096 Jan 19 07:41 schema
drwxrwxr-x 3 ian ian 4096 Jan 19 15:08 tools
drwxrwxr-x 3 ian ian 4096 Jan 17 16:03 web
drwxrwxr-x 3 ian ian 4096 Jan 19 10:59 xsl

¡Aha! Ahora usted puede usar el comando ls -l | grep "^d" | cut -c40- para cortar líneas desde la posición 40. Si reflexionamos un momento, veremos que esto en realidad tampoco resuelve el problema, debido a que los archivos más extensos moverán la posición de corte correcta as la derecha. Compruébelo usted mismo.

Sed al rescate

A veces denominado "la navaja suiza" del kit de herramientas de UNIX y Linux, sed es un extremadamente poderosos filtro de edición que utiliza expresiones regulares. Resulta claro ahora que el desafío consiste en quitar las primeras 8 palabras y los espacios en blanco que siguen de cada línea de datos de salida que comience con 'd'. Usted puede hacer todo esto con sed : seleccione sólo aquellas líneas que le interesan usando la expresión de correspondencia de patrones /^d/, sustituyendo una cadena nula con las primeras ocho palabras con el comando sustituto s/^d\([^ ]* *\)\(8\}//. Use la opción -n para imprimir sólo las líneas que usted especifica con el comando p como se muestra en el Listado 21.

Listado 21. Recorte de nombres de directorios con sed
[ian@pinguino developerworks]$ ls -l | sed -ne 's/^d\([^ ]* *\)\{8\}//p' 
my dw article
readme
schema
tools
web
xsl
[ian@pinguino developerworks]$ ls -l tools | sed -ne 's/^d\([^ ]* *\)\{8\}//p'
java

Para conocer más acerca de sed, consulte la sección Recursos.

Por fin una función

Ahora que usted tiene el comando complejo que necesita para su función ldirs, es hora de aprender acerca de cómo se lo convierte en una función. Una función consiste en un nombre seguido por () y luego un comando compuesto. Por ahora, consideraremos que un comando compuesto es cualquier comando o lista de comandos, finalizada con un punto y coma y encerrada por llaves (que deben estar separadas de otros tokens mediante un espacio en blanco). Usted aprenderá acerca de otros comandos compuestos en la sección Scripts del shell.

Nota: En el shell Bash, el nombre de una función puede estar precedido por la palabra 'function', pero esto no forma parte de la especificación POSIX y no se encuentra soportado por shells más minimalistas como por ejemplo dash. En la sección Scripts del shell, usted aprenderá a asegurarse que un script se encuentra interpretado por un shell determinado, incluso si usted normalmente utiliza un shell diferente.

Dentro de la función, usted puede referirse a los parámetros usando las variables especiales de bash que aparecen en la Tabla 4. Debe colocarles un símbolo $ como prefijo para referirse a ellos, al igual que con las demás variables del shell.

Tabla 4. Parámetros del shell para las funciones
ParámetroPropósito
0, 1, 2, ...Parámetros posicionales que comienzan desde el parámetro 0. El parámetro 0 se refiere al nombre del programa que inició bash, o al nombre del script del shell si la función se ejecuta dentro de un script del shell. Consulte las páginas man para obtener información sobre otras posibilidades, como por ejemplo cuando bash se inicia con el parámetro -c. Una cadena encerrada dentro de comillas simples o dobles pasará como un parámetro único, sin las comillas. En el caso de las comillas dobles, toda variable del shell, como por ejemplo $HOME, se expandirá antes de que la función sea llamada. <usted deberá usar comillas simples o dobles para pasar parámetros que contengan espacios en blanco incrustados u otros caracteres que puedan tener un significado especial para el shell.
*Parámetros posicionales que comienzan desde el parámetro 1. Si la expansión se realiza con comillas dobles, entonces la expansión será una única palabra en donde el primer carácter de la variable especial del separador de campos (IFS) separa los parámetros o no interviene espacios si el IFS es nulo. El valor predeterminado es un espacio en blanco, una pestaña y una nueva línea. Si el IFS no está definido, entonces el separador utilizado es un espacio en blanco, al igual que para el IFS predeterminado.
@Parámetros posicionales que comienzan desde el parámetro 1. Si la expansión se realiza con comillas dobles, entonces cada parámetro se convierte en una única palabra, de manera que "$@" es equivalente a "$1" "$2" .... Si es probable que sus parámetros contengan espacios en blanco incrustados, será conveniente que use esta forma.
#Cantidad de parámetros, sin incluir el parámetro 0.

Nota: Si usted tiene más de 9 parámetros, no puede usar $10 para referirse al décimo. Usted deberá en primer lugar, o bien procesar o guardar el primer parámetro ($1), y luego usar el comando shift para dejar el parámetro 1 y mover los parámetros restantes 1 lugar hacia abajo, de manera que $10 se convierta en $9 y así sucesivamente. El valor de $# quedará actualizado para reflejar la cantidad restante de parámetros.

Ahora usted puede definir una función simple para que haga nada menos que decirle cuántos parámetros posee y se los muestre como se observa en el Listado 22.

Listado 22. Parámetros de función
[ian@pinguino developerworks]$ testfunc () { echo "$# parameters"; echo "$@"; }
[ian@pinguino developerworks]$ testfunc
0 parameters

[ian@pinguino developerworks]$ testfunc a b c
3 parameters
a b c
[ian@pinguino developerworks]$ testfunc a "b c"
2 parameters
a b c

Ya sea que usted use $*, "$*", $@, o "$@", no verá demasiada diferencia en los datos de salida de la función anterior; sin embargo, esté seguro de que cuando las cosas se vuelvan más complejas, las distinciones que existen tendrán mucha importancia.

Ahora tome el comando complejo que probamos hasta ahora y cree una función ldirs con él, usando "$@" para representar los parámetros. Usted puede ingresar la totalidad de la función en una única línea como lo hizo en el ejemplo anterior; también, bash le permitirá ingresar comandos en múltiples líneas, en cuyo caso se agregará automáticamente un punto y coma como se muestra en el Listado 23. El Listado 23 también ilustra el uso del comando type para visualizar la definición de la función. Observe a partir de los datos de salida de type que el comando ls ha sido reemplazado por el valor expandido de su alias. Usted podría usar /bin/ls en lugar del simple ls si fuera necesario evitarlo.

Listado 23. Su primera función ldirs
[ian@pinguino developerworks]$ # Enter the function on a single line            
[ian@pinguino developerworks]$ ldirs () { ls -l "$@"|sed -ne 's/^d\([^ ]* *\)\{8\}//p'; }
[ian@pinguino developerworks]$ # Enter the function on multiple lines           
[ian@pinguino developerworks]$ ldirs ()
> {
> ls -l "$@"|sed -ne 's/^d\([^ ]* *\)\{8\}//p'
> }
[ian@pinguino developerworks]$ type ldirs
ldirs is a function
ldirs ()
{
    ls --color=tty -l "$@" | sed -ne 's/^d\([^ ]* *\)\{8\}//p'
}
[ian@pinguino developerworks]$ ldirs
my dw article
readme
schema
tools
web
xsl
[ian@pinguino developerworks]$  ldirs tools
java

De manera que ahora su función parece funcionar. Pero ¿Qué sucede si usted ejecuta ldirs * como se muestra en el Listado 24?

Listado 24. Ejecución de ldirs *
[ian@pinguino developerworks]$ ldirs * 5.6 java www.ibm.com
                    5.6

¿Sorprendido? En el directorio actual, usted no encontró directorios, sino subdirectorios de segundo nivel. Revise la página man sobre el comando ls o nuestros tutoriales anteriores y comprenderá por qué. De lo contrario, ejecute el comando find como se muestra en el Listado 25 para imprimir los nombres de los subdirectorios de segundo nivel.

Listado 25. Cómo encontrar subdirectorios de segundo nivel
[ian@pinguino developerworks]$ find . -mindepth 2 -maxdepth 2 -type d
./tools/java
./web/www.ibm.com
./xsl/5.6
./schema/5.6

Agregado de algunas pruebas

El uso de comodines ha expuesto un problema con la lógica de este enfoque. Alegremente ignoramos el hecho de que ldirs sin parámetros mostró los subdirectorios en el directorio actual, mientras que ldirs tools mostró el subdirectorio java del directorio de herramientas más que el directorio de herramientas propiamente dicho como se esperaría usando ls con archivos más que con directorios. Lo ideal sería usar ls -l si no se proveen parámetros y ls -ld si se proveen algunos parámetros. Usted puede usar el comando test para comprobar la cantidad de parámetros, y luego usar && y || para construir una lista de comandos que ejecute el comando adecuado. Al usar la forma test expression ] de test, su expresión será algo similar a { [ $# -gt 0 ] &&/bin/ls -ld "$@" || /bin/ls -l } | sed -ne ....

No obstante, hay un pequeño problema con este código, en el sentido de que si el comando ls -ld no encuentra archivos o directorios correspondientes, emitirá un mensaje de error y devolverá un código de salida sin cero, haciendo que el comando ls -l también se ejecute. Quizás esto no sea lo que usted desea. Una respuesta consiste en construir un comando compuesto para el primer comando ls de manera que el número de parámetros se verifique nuevamente si falla el comando. Expanda la función para incluir esto, con lo cual su función se verá como la que aparece en el Listado 26. Intente usarla con algunos de los parámetros del Listado 26, o experimente con sus propios parámetros para ver su comportamiento.

Listado 26. Manejo de comodines con ldirs
[ian@pinguino ~]$ type ldirs
ldirs is a function
ldirs ()
{
    {
        [ $# -gt 0 ] && {
            /bin/ls -ld "$@" || [ $# -gt 0 ]
        } || /bin/ls -l
    } | sed -ne 's/^d\([^ ]* *\)\{8\}//p'
}
[ian@pinguino developerworks]$ ldirs *
my dw article
readme
schema
tools
web
xsl
[ian@pinguino developerworks]$ ldirs tools/*
tools/java
[ian@pinguino developerworks]$ ldirs *xxx*
/bin/ls: *xxx*: No such file or directory
[ian@pinguino developerworks]$ ldirs *a* *s*
my dw article
readme
schema
schema
tools
xsl

Retoque final

En este momento, usted podrá obtener un directorio que aparezca dos veces como en el último ejemplo del Listado 26. Usted podría ampliar la canalización canalizando los datos de salida de sed mediante sort | uniq si lo desea.

A partir de algunos bloques de construcción pequeños, usted ha construido una función de shell bastante compleja.

Personalización de teclas

Las teclas que usted presiona en una sesión de terminal, y aquellas usadas en programas tales como FTP, son procesadas por la biblioteca de líneas de lectura y pueden ser configuradas. El archivo de personalización predeterminado es .inputrc y se encuentra en su directorio principal. El mismo será leído durante el inicio de bash, si existiera. Usted puede configurar un archivo diferente estableciendo la variable INPUTRC. Si no se la define, se usará .inputrc en su directorio principal. Muchos sistemas cuentan con un mapeo de teclas predeterminado en /etc/inputrc, por lo cual será usualmente conveniente que las incluya usando la directiva $include.

El Listado 27 ilustra de qué manera usted puede enlazar su función ldirs a la combinación de teclas Ctrl-t (presionar y mantener Ctrl, luego presionar t). Si desea que el comando se ejecute sin parámetros, agregue \n al final de la línea de configuración.

Listado 27. Ejemplo de archivo .inputrc
# My custom key mappings $include /etc/inputrc

Usted puede obligar al archivo INPUTRC a leer nuevamente si presiona Ctrl-x y luego Ctrl-r. Observe que algunas distribuciones fijarán INPUTRC=/etc/inputrc si usted no posee su propio .inputrc, por lo tanto si usted crea uno en un sistema así, deberá desconectarse y volver a iniciar sesión para levantar sus nuevas definiciones. Si sólo resetea INPUTRC a un valor nulo o para que se dirija a su nuevo archivo, el sistema volverá a leer el archivo original y no la nueva especificación.

El archivo INPUTRC puede incluir especificaciones condicionales. Por ejemplo, el comportamiento de su teclado deberá ser distinto según usted use el modo de edición emacs (predeterminado por bash) o el modo de edición vi. Consulte las páginas man para ver más detalles sobre cómo personalizar su teclado.

Cómo guardar alias y funciones

Usted probablemente agregará sus propios alias y funciones al archivo ~/.bashrc, si bien puede guardarlos en cualquier archivo que desee. No importa lo que haga, recuerde, recuerde originar el/los archivo/s con el comando source o . de manera que los contenidos de su archivo se lean y ejecuten en el entorno actual. Si usted crea un script y solamente lo ejecuta, el mismo será ejecutado en un subshell y se perderá la valiosa personalización que usted ha hecho cuando se produzca la salida del subshell y el mismo le devuelva el control.

En la próxima sección, usted aprenderá cómo ir más allá de las funciones simples. Aprenderá a agregar construcciones de programación tales como las verificaciones condicionales y construcciones de bucle, y a combinarlas con múltiples funciones para crear o modificar scripts en el shell Bash.


Scripts del shell

Esta sección se ocupa del material para el tema 1.109.2 del examen 102 de la Administración Nivel Junior (LPIC-1). El tema tiene un valor de 3.

En esta sección, usted aprenderá a:

  • Usar la sintaxis estándar de shell, como por ejemplo bucles y verificaciones
  • Usar sustitución de comandos
  • Verificar los valores de devolución para ver si la ejecución fue correcta o no y para ver otra información provista por un comando
  • Realizar un correo condicional al superusuario
  • Seleccionar el intérprete de scripts correcto mediante la línea shebang (#!)
  • Gestionar la ubicación, la propiedad, la ejecución y los derechos de suid de los scripts

Esta sección se basa en lo que usted aprendió sobre funciones simples en la sección anterior y muestra algunas de las técnicas y herramientas que agregan capacidad de programación al shell. Usted ya ha visto la lógica simple usando los operadores && y ||, lo cual le permite ejecutar un comando en base a la existencia normal o con error del comando previo. En la función ldirs, usted usó esto para alterar la llamada a ls según si se pasaron o no parámetros a su función ldirs. Ahora aprenderá a ampliar estas técnicas básicas para una programación de shell más compleja.

Pruebas

Lo primero que debe hacer en cualquier tipo de lenguaje de programación después de aprender a asignar valores a las variables y a pasar los parámetros es verificar esos valores y parámetros. En los shells las pruebas que usted realiza determinan el estado de la devolución, que es lo mismo que hacen otros comandos. De hecho, test es un comando builtin.

Test y [

El comando builtin test devuelve 0 (Verdadero) o 1 (Falso) según la evaluación de una expresión expr. Usted puede también usar corchetes para que test expr y [ expr ] sean equivalentes. Puede analizar el valor de devolución mostrando $?; puede usar el valor de devolución como lo ha hecho antes con && y ||; o puede verificarlo usando las diversas construcciones condicionales que se tratan posteriormente en esta sección.

Listado 28. Algunas verificaciones simples
[ian@pinguino ~]$ test 3 -gt 4
                    && echo True || echo false false [ian@pinguino ~]$ [ "abc" !=
                    "def" ];echo $? 0 [ian@pinguino ~]$ test -d "$HOME" ;echo $? 0

En el primero de estos ejemplos, el operador -gt se usó para realizar una comparación aritmética entre dos valores literales. En el segundo, se usó la forma alternativa [ ] para comparar dos cadenas en busca de desigualdad. En el ejemplo final, se verifica el valor de la variable HOME para ver si es un directorio que usa el operador unario -d.

Los valores aritméticos pueden compararse usando -eq, -ne, -lt, -le, -gt, o -ge, que significan igual, no igual, menos que, menos o igual que, más que y más o igual que, respectivamente.

Se pueden comparar cadenas para ver igualdad, desigualdad, o si la primera cadena ordena antes o después de la segunda usando los operadores =, !=, < y >, respectivamente. El operador unario -z verifica si hay una cadena nula, mientras que el operador -n o ningún operador devuelve Verdadero si una cadena no está vacía.

Nota: Los operadores < y > también son usados por el shell para el redireccionamiento, de manera que usted debe salir de ellos usando \< o \>. El Listado 29 muestra algunos otros ejemplos de las verificaciones realizadas en cadenas. Compruebe que resultan de la manera que usted espera.

Listado 29. Algunas verificaciones de cadenas
[ian@pinguino ~]$ test "abc" = "def" ;echo $?
1
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \< "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \> "def" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \<"abc" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \> "abc" ];echo $?
1

La Tabla 5 muestra algunas de las verificaciones de archivo más comunes. El resultado es verdadero si el archivo verificado es un archivo que existe y que tiene las características especificadas.

Tabla 5. Algunas verificaciones de archivos
OperadorCaracterística
-dDirectorio
-eExiste (también -a)
-fArchivo regular
-hVínculo simbólico (también -L)
-pCanalización nombrada
-rLegible por usted
-sNo vacío
-SSocket
-wEditable por usted
-NHa sido modificado desde su última lectura

Además de las anteriores verificaciones unarias, pueden compararse dos archivos con los operadores binarios que se muestran en la Tabla 6.

Tabla 6. Verificación de pares de archivos
OperadorVerdadero si
-ntVerifique si el archivo 1 es más reciente que el archivo 2. Para ello, se utiliza la fecha de modificación, así como la siguiente comparación.
-otVerifique si el archivo 1 es más antiguo que el archivo 2.
-efVerifique si el archivo 1 es un vínculo físico al archivo 2.

Existen numerosas otras verificaciones que le permiten comprobar cosas tales como los permisos del archivo. Consulte las páginas man sobre bash para ver más detalles o use help test para obtener un resumen de la información del builtin de la prueba. Además, usted podrá usar el comando help para otros builtins.

El operador -o le permite verificar las diferentes opciones del shell que puedan estar configuradas con set -o option, la cual da como resultado Verdadero (0) si la opción está configurada y Falso (1) si no lo está, como se muestra en el Listado 30.

Listado 30. Verificación de las opciones de shell
[ian@pinguino ~]$ set +o nounset
[ian@pinguino ~]$ [ -o nounset ];echo $?
1
[ian@pinguino ~]$ set -u
[ian@pinguino ~]$ test  -o nounset; echo $?
0

Por último, las opciones -a y -o le permiten combinar expresiones con las expresiones lógicas AND y OR, respectivamente, mientras que el operador unario. ! invierte el sentido de la prueba. Usted puede usar paréntesis para agrupar estas expresiones y anular la preferencia anterior. Recuerde que el shell normalmente ejecutará una expresión que se encentre entre paréntesis dentro de un subshell, de manera que deberá salir del paréntesis usando \( y \) o colocando a estos operadores entre comillas simples o dobles. El Listado 31 ilustra la aplicación de las leyes de De Morgan en una expresión .

Listado 31. Combinación y agrupación de pruebas
[ian@pinguino ~]$ test "a" != "$HOME" -a 3 -ge 4 ; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o 3 -lt 4 \) ]; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o '(' 3 -lt 4 ')' ")" ]; echo $?
1

(( y [[

Si bien el comando test es muy poderoso, es algo difícil de manejar debido a su requerimiento de salida y a la diferencia entre la cadena y las comparaciones aritméticas. Afortunadamente, bash cuenta con otras dos modos de verificación que son en algún sentido más naturales para las personas que están familiarizadas con la sintaxis C, C++, o Java. El comando (( ))compuesto evalúa la expresión aritmética y define un estado de salida en 1 si la evaluación de la expresión es 0, o en 0 si la evaluación de la expresión es un valor distinto de 0. Usted no necesita salir de los operadores entre (( y )). La aritmética se realiza en números enteros. La división por 0 genera un error, pero el desbordamiento no lo hace. Usted puede realizar las operaciones aritméticas, lógicas y de bits usuales del lenguaje C. El comando let puede además ejecutar una o más expresiones aritméticas. Por lo general se lo utiliza para asignar valores a las variables aritméticas.

Listado 32. Asignación y verificación de expresiones aritméticas
[ian@pinguino ~]$ let x=2 y=2**3 z=y*3;echo $? $x $y $z
0 2 8 24
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 3 8 16
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 4 8 13

Al igual que con (( )), el comando compuesto [[ ]] le permite usar una sintaxis más natural en las pruebas de nombre de archivo y cadena. Usted puede combinar las pruebas que se permiten en el comando test usando paréntesis y operadores lógicos.

Listado 33. Uso del comando compuesto [[
[ian@pinguino ~]$ [[ ( -d "$HOME" ) &&
                    ( -w "$HOME" ) ]] && > echo "home is a writable
                    directory" home is a writable directory

El comando compuesto [[ puede también realizar una correspondencia de patrones sobre las cadenas cuando se están utilizando los operadores = o !=. La correspondencia se comporta igual que con un globbing comodín como se ilustra en el Listado 34.

Listado 34. Pruebas de comodín con [[
[ian@pinguino ~]$ [[ "abc def .d,x--" == a[abc]*\ ?d* ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def c" == a[abc]*\ ?d* ]]; echo $?
1
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* ]]; echo $?
1

Incluso podrá realizar pruebas aritméticas dentro de los comandos compuestos [[, pero deberá tener cuidado al hacerlo. A menos que se encuentre dentro de un comando compuesto ((, los operadores < y > compararán los operandos como cadenas, verificando su orden en la secuencia de intercalación actual. El Listado 35 ilustra esta cuestión con algunos ejemplos.

Listado 35. Inclusión de pruebas aritméticas con [[
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable

Condicionales

Si bien usted podría lograr una gran cantidad de programación con las pruebas que se mencionan anteriormente y los operadores de control && y ||, bash incluye enunciados más conocidos con "if, then, else" y estructuras de control. Una vez que las conozca, aprenderá acerca de las construcciones de bucle y podrá realmente expander su caja de herramientas.

Enunciados con If, then, else

El comando bash if es un comando compuesto que verifica el valor que devuelve una prueba o un comando ( $? y lo ramifica según el valor resultante sea Verdadero (0) o Falso (valor distinto de 0). Si bien las pruebas anteriores sólo devolvieron valores 0 o 1, los comandos pueden dar como resultado otros valores. En una sección posterior de este tutorial, usted aprenderá a verificarlos. El comando if de bash posee una cláusula then que contiene una lista de comandos que se deberán ejecutar si la prueba o el comando devuelven 0. Además, el comando posee una o más cláusulas elif opcionales. Cada una de estas cláusulas elif opcionales incluye una verificación y una cláusula then adicional con una lista de comandos asociada, una cláusula opcional else final, y una lista de comandos que se ejecutarán si ninguno de los resultados de la prueba original, o de cualquiera de las demás verificaciones usadas en las cláusulas elif dio verdadero. Una terminal fi señala el fin de la construcción.

Usando lo que ha aprendido hasta el momento, usted podría ahora construir una simple calculadora para evaluar expresiones aritméticas, como se muestra en el Listado 36.

Listado 36. Evaluación de expresiones con if, then, else
[ian@pinguino ~]$ function mycalc ()
> {
>   local x
>   if [ $# -lt 1 ]; then
>     echo "This function evaluates arithmetic for you if you give it some"
>   elif (( $* )); then
>     let x="$*"
>     echo "$* = $x"
>   else
>     echo "$* = 0 or is not an arithmetic expression"
>   fi
> }
[ian@pinguino ~]$ mycalc 3 + 4
3 + 4 = 7
[ian@pinguino ~]$ mycalc 3 + 4**3
3 + 4**3 = 67
[ian@pinguino ~]$ mycalc 3 + (4**3 /2)
-bash: syntax error near unexpected token `('
[ian@pinguino ~]$ mycalc 3 + "(4**3 /2)"
3 + (4**3 /2) = 35
[ian@pinguino ~]$ mycalc xyz
xyz = 0 or is not an arithmetic expression
[ian@pinguino ~]$ mycalc xyz + 3 + "(4**3 /2)" + abc
xyz + 3 + (4**3 /2) + abc = 35

La calculadora hace uso del enunciado local para declarar a x como una variable local que se encuentra disponible sólo dentro del alcance de la función mycalc. La función let posee numerosas opciones posibles, al igual que la función declare a la cual está muy ligada. Consulte las páginas man sobre bash, o use help let para obtener más información.

Como puede ver en el Listado 36, usted debe asegurarse de salir adecuadamente de sus expresiones si las mismas usan metacaracteres shell como por ejemplo (, ), *, >, y <. No obstante, usted cuenta con una pequeña calculadora a mano para evaluar la aritmética a medida que el shell la realiza.

Posiblemente haya observado la cláusula else y los dos últimos ejemplos. Como puede ver, no es erróneo pasar xyz a mycalc, pero la evaluación que realiza arroja 0. Esta función no es lo suficientemente inteligente para identificar los valores del carácter en el ejemplo de uso final y, por lo tanto, para poder alertar al usuario. Usted podría usar una prueba de correspondencia con el patrón de cadenas como por ejemplo
[[ ! ("$*" == *[a-zA-Z]* ]]
(o la forma adecuada en su localidad) con el fin de eliminar cualquier expresión que contenga caracteres alfabéticos, pero esto evitaría el uso de notación hexadecimal en sus datos de entrada, debido a que usted podría usar 0x0f para representar 15 con una notación hexadecimal. De hecho, el shell permite bases hasta 64 (con notación base # value), por lo cual usted legítimamente podría usar cualquier carácter alfabético, más _ y @ en sus datos de entrada. Las notaciones octal y hexadecimal usan la notación usual de un 0 inicial para la octal y de un 0x o 0X inicial para la hexadecimal. Estos ejemplos se muestran en el Listado 37.

Listado 37. Cálculo con diferentes bases
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574

La purificación adicional de los datos de entrada se encuentra fuera del alcance de este tutorial, de manera que usted deberá usar la calculadora con el cuidado adecuado.

El enunciado elif es en verdad conveniente. Lo ayudará con la escritura de sus scripts al permitirle simplificar la indentación. Es posible que se sorprenda al ver los datos de salida del comando type para la función mycalc como se muestra en el Listado 38.

Listado 38. Type mycalc
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
{
    local x;
    if [ $# -lt 1 ]; then
        echo "This function evaluates arithmetic for you if you give it some";
    else
        if (( $* )); then
            let x="$*";
            echo "$* = $x";
        else
            echo "$* = 0 or is not an arithmetic expression";
        fi;
    fi
}

Enunciados con mayúsculas y minúsculas

El comando compuesto case simplifica las pruebas cuando usted cuenta con una lista de posibilidades y desea actuar de acuerdo a si un valor corresponde a una posibilidad en particular. El comando compuesto case se inicia con case WORD in y termina con esac ("case" deletreado de manera inversa). Cada caso de mayúscula o minúscula consiste en un patrón único, o en múltiples patrones separados por |, seguidos por ), una lista de enunciados, y finalmente un par de signos punto y coma. (;;).

A modo de ejemplo, imagine un comercio que sirve café, café descafeinado (decaf), té o gaseosas. Se puede usar la función del Listado 39 para determinar la respuesta a una orden.

Listado 39. Uso de comandos con mayúsculas y minúsculas
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    case "$*" in
        "coffee" | "decaf")
            echo "Hot coffee coming right up"
        ;;
        "tea")
            echo "Hot tea on its way"
        ;;
        "soda")
            echo "Your ice-cold soda will be ready in a moment"
        ;;
        *)
            echo "Sorry, we don't serve that here"
        ;;
    esac
}
[ian@pinguino ~]$ myorder decaf
Hot coffee coming right up
[ian@pinguino ~]$ myorder tea
Hot tea on its way
[ian@pinguino ~]$ myorder milk
Sorry, we don't serve that here

Observe el uso de '*' para encontrar la correspondencia entre cualquier cosa que no hubiera hecho coincidir anteriormente.

Bash posee otra construcción similar a case que se puede usar para imprimir los datos de salida a una terminal y hacer que un usuario seleccione los artículos. Es el comando select, el cual no se tratará en este documento. Consulte las páginas man sobre bash, o escriba help select para aprender más sobre este tema.

Por supuesto, existen muchos problemas con un enfoque tan simple para resolver el problema; usted no puede pedir dos bebidas al mismo tiempo, y la función no maneja nada que no sean datos de entrada en minúsculas. ¿Puede hacer usted una correspondencia que no distinga entre mayúsculas y minúsculas? La respuesta es "si". Veamos cómo se hace.

Valores de devolución

El shell Bash cuenta con un builtin shopt que le permite activar o desactivar numerosas opciones del shell. Una de ellas es nocasematch, que, si está activado, indica al shell que ignore la diferencia entre mayúsculas y minúsculas al realizar la correspondencia entre cadenas. Es probable que lo primero que usted piense sea en usar el operando -o que aprendió con el comando test. Desafortunadamente, nocasematch no es una de las opciones que usted puede verificar con -o, de manera que deberá recurrir a otra cosa.

El comando shopt, al igual que la mayoría de los comandos de UNIX y Linux, determina un valor de devolución que usted puede examinar usando $?. Las pruebas que usted aprendió a realizar anteriormente no son elementos son valores de devolución. Si usted analiza las pruebas que realiza en un enunciado if, notará que en verdad verifican el valor de devolución del comando de verificación subyacente para obtener un resultado Verdadero (0) o Falso (1 o cualquier valor superior a 0). Esto funciona incluso si usted no aplica una prueba, pero usa algún otro comando. El éxito se indica con un valor de devolución de 0, y la falla se indica con un valor de devolución distinto de 0.

Con estos conocimientos, ahora usted puede verificar la opción nocasematch, configurarla si aún no lo está, y luego volverla a la forma preferida por el usuario cuando termina la función. El comando shopt cuenta con cuatro opciones convenientes, -pqsu para imprimir el valor actual, no imprimir nada, establecer la opción y desactivar la opción. Las opciones -p y -q establecen un valor de devolución de 0 que indica que la opción del shell está configurada, y de 1 que indica que está desactivada. La opción -p imprime el comando requerido para fijar la opción en su valor actual, mientras que la opción -q o simplemente fija un valor de devolución de 0 o 1.

La función que usted ha modificado usará el valor de devolución desde shopt para fijar una variable local que represente el estado actual de la opción nocasematch, establecer la opción, ejecutar el comando de mayúscula-minúscula, y luego restablecer la opción. nocasematch a su valor original. El Listado 40 muestra una manera de hacerlo.

Listado 40. Verificación de los valores de devolución de los comandos
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    local restorecase;
    if shopt -q nocasematch; then
        restorecase="-s";
    else
        restorecase="-u";
        shopt -s nocasematch;
    fi;
    case "$*" in
        "coffee" | "decaf")
            echo "Hot coffee coming right up"
        ;;
        "tea")
            echo "Hot tea on its way"
        ;;
        "soda")
            echo "Your ice-cold soda will be ready in a moment"
        ;;
        *)
            echo "Sorry, we don't serve that here"
        ;;
    esac;
    shopt $restorecase nocasematch
}
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
[ian@pinguino ~]$ # nocasematch is currently unset
[ian@pinguino ~]$ myorder DECAF
Hot coffee coming right up
[ian@pinguino ~]$ myorder Soda
Your ice-cold soda will be ready in a moment
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
[ian@pinguino ~]$ # nocasematch is unset again after running the myorder function

Si usted quiere que su función (o script) devuelva un valor que pueda ser verificado por otras funciones o comandos, use el enunciado return de su función. El Listado 41 le muestra cómo lograr un valor de devolución 0 para una bebida que usted puede servir y uno de 1 si el cliente pide otra cosa.

Listado 41. Configuración de sus propios valores de devolución a partir de las funciones
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    local restorecase=$(shopt -p nocasematch) rc=0;
    shopt -s nocasematch;
    case "$*" in
        "coffee" | "decaf")
            echo "Hot coffee coming right up"
        ;;
        "tea")
            echo "Hot tea on its way"
        ;;
        "soda")
            echo "Your ice-cold soda will be ready in a moment"
        ;;
        *)
            echo "Sorry, we don't serve that here";
            rc=1
        ;;
    esac;
    $restorecase;
    return $rc
}
[ian@pinguino ~]$ myorder coffee;echo $?
Hot coffee coming right up
0
[ian@pinguino ~]$ myorder milk;echo $?
Sorry, we don't serve that here
1

Si usted no especifica su propio valor de devolución, el valor de devolución será el del último comando ejecutado. Las funciones suelen ser reutilizadas en situaciones que usted nunca previó, de manera que establecer su propio valor será una buena práctica.

Los comandos pueden devolver valores que no sean 0 y 1, y en ocasiones será conveniente distinguirlos. Por ejemplo, el comando grep devuelve 0 si el patrón tiene correspondencia, y 1 si no la tiene, pero también devuelve 2 si el patrón es inválido o la especificación del archivo no se corresponde con ningún archivo. Si usted debe distinguir además otros valores de devolución que no sean simplemente éxito (0) o falla (valores distintos a 0), probablemente deberá usar un comando case o quizás un comando if con muchas partes elif.

Sustitución de comandos

Usted conoció la sustitución de comandos en el tutorial " Preparación para el examen 101 de LPI (tema 103): Comandos GNU y UNIX ", pero hagamos una breve revisión.

La sustitución de comandos le permite usar los datos de salida de un comando como datos de entrada para otro comando simplemente si encierra el comando ente $( y ) o con un par de acentos invertidos - `. La forma $() presenta algunas ventajas si desea anidar los datos de salida de un comando como parte del comando que generará los datos de salida finales. Por otro lado, será más fácil descubrir qué es lo que sucede realmente ya que el paréntesis tiene una forma derecha y otra izquierda, contrariamente al acento invertido. Sin embargo, la opción es suya, y los acentos invertidos son una opción muy común.

Con frecuencia usará la sustitución de comandos con bucles (que se tratan más adelante en Bucles). No obstante, también la puede usar para simplificar la función myorder que acaba de crear. Debido a que shopt -p nocasematch en realidad imprime el comando que usted necesita para establecer la opción nocasematch en su valor actual, sólo deberá guardar esos datos de salida y luego ejecutarlos al finalizar el enunciado case. Con esto se restaurará la opción nocasematch independientemente de si usted la modificó o no. Su función revisada será similar al Listado 42. Pruébela usted mismo.

Listado 42. Sustitución de comandos en lugar de pruebas de valor de devolución
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    local restorecase=$(shopt -p nocasematch) rc=0;
    shopt -s nocasematch;
    case "$*" in
        "coffee" | "decaf")
            echo "Hot coffee coming right up"
        ;;
        "tea")
            echo "Hot tea on its way"
        ;;
        "soda")
            echo "Your ice-cold soda will be ready in a moment"
        ;;
        *)
            echo "Sorry, we don't serve that here"
            rc=1
        ;;
    esac;
    $restorecase
    return $rc
}
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
[ian@pinguino ~]$ myorder DECAF
Hot coffee coming right up
[ian@pinguino ~]$ myorder TeA
Hot tea on its way
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch

Depuración

Si usted ha escrito funciones por sí mismo y ha cometido errores de tipeo que lo dejaron pensando en qué estaba mal, es probable que también se pregunte cómo se depuran las funciones. Afortunadamente el shell le permite establecer la opción -x para rastrear los comandos y sus argumentos a medida que el shell los ejecuta. El Listado 43 muestra cómo funciona para la función myorder del Listado 42.

Listado 43. Rastreo de la ejecución
[ian@pinguino ~]$ set -x
++ echo -ne '\033]0;ian@pinguino:~'

[ian@pinguino ~]$ myorder tea
+ myorder tea
++ shopt -p nocasematch
+ local 'restorecase=shopt -u nocasematch' rc=0
+ shopt -s nocasematch
+ case "$*" in
+ echo 'Hot tea on its way'
Hot tea on its way
+ shopt -u nocasematch
+ return 0
++ echo -ne '\033]0;ian@pinguino:~'

[ian@pinguino ~]$ set +x
+ set +x

Usted puede aplicar esta técnica para sus alias, funciones o scripts. Si necesita más información, agregue la opción -v para obtener datos de salida detallados.

Bucles

El Bash y otros shells cuentan con tres construcciones de bucle que son de alguna manera similares a las del lenguaje C. Cada una ejecutará una lista de comandos entre cero y varias veces. La lista de comandos se encuentra rodeada por las palabras do y done, cada uno de ellos precedida por punto y coma.

for
vienen en dos sabores. La forma más común de scripting de los shells itera sobre un conjunto de valores, ejecutando la lista de comandos una vez para cada valor. El conjunto puede estar vacío, en cuyo caso no se ejecuta la lista de comandos. La otra manera es más un lenguaje C tradicional para bucles, que usa tres expresiones aritméticas para controlar una condición inicial, una función de escalón y una condición final.
while
evalúan una condición cada vez que el bucle seinicia y ejecuta la lista de comandos si la condición es verdadera. Si la condición no es inicialmente verdadera, los comandos nunca se ejecutan.
until
ejecutan la lista de comandos y evalúan una condición cada vez que el bucle finaliza. Si la condición es verdadera, el bucle vuelve a ejecutarse. Incluso si la condición no es inicialmente verdadera, los comandos se ejecutan por lo menos una vez.

Si las condiciones que se verifican corresponden a una lista de comandos, entonces se usará el valor de devolución del último ejecutado. El Listado 44 muestra los comandos de bucle.

Listado 44. Bucles for, while, y until
[ian@pinguino ~]$ for x in abd 2 "my stuff"; do echo $x; done
abd
2
my stuff
[ian@pinguino ~]$ for (( x=2; x<5; x++ )); do echo $x; done
2
3
4
[ian@pinguino ~]$ let x=3; while [ $x -ge 0 ] ; do echo $x ;let x--;done
3
2
1
0
[ian@pinguino ~]$ let x=3; until echo -e "x=\c"; (( x-- == 0 )) ; do echo $x ; done
x=2
x=1
x=0

Estos ejemplos son en cierta medida artificiales, pero sirven para ilustrar los conceptos. La mayoría de las veces, será conveniente iterar sobre los parámetros a una función o script del shell, o a una lista creada por sustitución de comandos. Anteriormente, usted descubrió que los shells pueden referirse a la lista de parámetros pasados como $* o $@ y que el hecho de que usted haya encerrado estas expresiones entre comillas o no afectó la manera en que las mismas fueron interpretadas. El Listado 45 muestra una función que imprime la cantidad de parámetros y luego imprime los parámetros de acuerdo con las cuatro alternativas. El Listado 46 muestra la función en acción, con un carácter adicional agregado delante de la variable IFS para la ejecución de la función.

Listado 45. Función para imprimir información sobre los parámetros
[ian@pinguino ~]$ type testfunc
testfunc is a function
testfunc ()
{
    echo "$# parameters";
    echo Using '$*';
    for p in $*;
    do
        echo "[$p]";
    done;
    echo Using '"$*"';
    for p in "$*";
    do
        echo "[$p]";
    done;
    echo Using '$@';
    for p in $@;
    do
        echo "[$p]";
    done;
    echo Using '"$@"';
    for p in "$@";
    do
        echo "[$p]";
    done
}
Listado 46. Impresión de la información de los parámetros con testfunc
[ian@pinguino ~]$ IFS="|${IFS}" testfunc abc "a bc" "1 2
> 3"
3 parameters
Using $*
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$*"
[abc|a bc|1 2
3]
Using $@
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$@"
[abc]
[a bc]
[1 2
3]

Analice cuidadosamente las diferencias, en especial para las formas entre comillas y los parámetros que incluyen espacios en blanco como espacios o caracteres de nuevas líneas.

Break y continue

El comando break le permite salir de un bucle de manera inmediata. Como alternativa, usted puede especificar una cantidad de niveles de los cuales salir si posee bucles anidados. De manera que si usted tenía un bucle until dentro de un bucle for dentro de un bucle for y todos ellos dentro de un bucle while, entonces break 3 inmediatamente finalizaría el bucle until y ambos bucles for, para devolver el control al código que se encuentra dentro del bucle while.

El enunciado continue le permite bordear los enunciados restantes en la lista de comandos y pasar inmediatamente a la siguiente iteración del loop.

Listado 47. Uso de break y continue
[ian@pinguino ~]$ for word in red blue green yellow violet; do
> if [ "$word" = blue ]; then continue; fi
>  if [ "$word" = yellow ]; then break; fi
>  echo "$word"
> done
red
green

Nueva visita a ldirs

¿Recuerda todo el trabajo que tuvo para hacer que la función ldirs extrajera el nombre del archivo de una lista larga y descubriera si se trataba de un directorio o no? La función final que usted desarrollara no estaba del todo mal, pero imagine que contaba con toda la información que posee en este momento. ¿Hubiera creado la misma función? Quizás no. Usted sabe cómo verificar su nombre corresponde a un directorio o no con [ -d $name ], y tiene conocimientos sobre el comando compuesto for. El Listado 48 muestra otra manera en que usted podría haber codificado la función ldirs.

Listado 48. Otro enfoque para ldirs
[ian@pinguino developerworks]$ type ldirs
ldirs is a function
ldirs ()
{
    if [ $# -gt 0 ]; then
        for file in "$@";
        do
            [ -d "$file" ] && echo "$file";
        done;
    else
        for file in *;
        do
            [ -d "$file" ] && echo "$file";
        done;
    fi;
    return 0
}
[ian@pinguino developerworks]$ ldirs
my dw article
my-tutorial
readme
schema
tools
web
xsl
[ian@pinguino developerworks]$ ldirs *s* tools/*
schema
tools
xsl
tools/java
[ian@pinguino developerworks]$ ldirs *www*
[ian@pinguino developerworks]$

Usted observará que la función simplemente brinda una devolución si no existen directorios que correspondan a sus criterios. Esto puede o no ser lo que buscaba, pero de serlo, quizás esta forma de la función resulte más sencilla de entender que la versión que utilizaba sed para analizar los datos de salida de ls. Por lo menos, usted cuenta ahora con otra herramienta en su caja de herramientas.

Creación de scripts

¿Recuerda que myorder sólo podía manejar una bebida a la vez? Ahora podrá combinar esta simple función de bebida única con un comando compuesto for para iterar por los parámetros y manejar varias bebidas. Esto se realiza simplemente colocando su función en un archivo y agregando la instrucción for. El Listado 49 ilustra el nuevo script myorder.sh.

Listado 49. Pedidos de varias bebidas
[ian@pinguino ~]$ cat myorder.sh
function myorder ()
{
    local restorecase=$(shopt -p nocasematch) rc=0;
    shopt -s nocasematch;
    case "$*" in
        "coffee" | "decaf")
            echo "Hot coffee coming right up"
        ;;
        "tea")
            echo "Hot tea on its way"
        ;;
        "soda")
            echo "Your ice-cold soda will be ready in a moment"
        ;;
        *)
            echo "Sorry, we don't serve that here";
            rc=1
        ;;
    esac;
    $restorecase;
    return $rc
}

for file in "$@"; do myorder "$file"; done

[ian@pinguino ~]$ . myorder.sh coffee tea "milk shake"
Hot coffee coming right up
Hot tea on its way
Sorry, we don't serve that here

Observe que el script fue originado para que se ejecute en el entorno del shell actual más que en su propio shell usando el comando .. Para poder ejecutar un script, usted tendrá que originarlo, o deberá marcar el script como ejecutable con el comando chmod -x como se ilustra en el Listado 50.

Listado 50. Cómo transformar un script en ejecutable
[ian@pinguino ~]$ chmod +x myorder.sh
[ian@pinguino ~]$ ./myorder.sh coffee tea "milk shake"
Hot coffee coming right up
Hot tea on its way
Sorry, we don't serve that here

Especificación de un shell

Ahora que cuenta con un flamante script de shell para jugar con él, quizás se pregunte si el mismo funciona en todos los shells. El Listado 51 muestra qué sucede si usted ejecuta exactamente el mismo script de shell en un sistema Ubuntu que usa primero el shell Bash, y luego el shell dash.

Listado 51. Diferencias entre shells
ian@attic4:~$ ./myorder tea soda
-bash: ./myorder: No such file or directory
ian@attic4:~$ ./myorder.sh tea soda
Hot tea on its way
Your ice-cold soda will be ready in a moment
ian@attic4:~$ dash
$ ./myorder.sh tea soda
./myorder.sh: 1: Syntax error: "(" unexpected

Esto no es bueno.

¿Recuerda cuando anteriormente mencionamos que la palabra 'function' era opcional en una definición de función bash, pero que no formaba parte de la especificación de shell de POSIX? Bien, dash es un shell más pequeño y liviano que bash y no soporta la característica opcional. Debido a que usted no puede garantizar cuál es el shell que preferirán sus potenciales usuarios, siempre deberá asegurarse de que su script sea transportable a los entornos de todos los shells, lo cual puede ser algo difícil, o usar el denominado shebang (#!) para indicar al shell que ejecute su script en un shell determinado. La línea shebang debe ser la primera línea de su script, y el resto de la línea debe contener la ruta al shell en el cual se debe ejecutar su programa, por lo cual el script sería #!/bin/bash the myorder.sh.

Listado 52. Uso de shebang
$ head -n3 myorder.sh
#!/bin/bash
function myorder ()
{
$ ./myorder.sh Tea Coffee
Hot tea on its way
Hot coffee coming right up

Usted puede usar el comando cat para visualizar /etc/shells, que es la lista de los shells de su sistema. Algunos sistemas incluyen en sus listas de shells a shells que no se encuentran instalados, y es posible que algunos shells (possibly /dev/null) estén para asegurar que los usuarios de FTP no salgan accidentalmente de sus limitados entornos. Si usted debe modificar su shell predeterminado, podrá hacerlo con el comando chsh, que actualice la entrada de su id de usuario en /etc/passwd.

Ubicación de derechos de suid y scripts

En el tutorial anterior, Preparación para el examen 101 de LPI: Dispositivos, sistemas de archivos Linux, y el Estándar de Jerarquía del Sistema de Archivos, usted aprendió a cambiar el dueño y el grupo de un archivo y a configurar los permisos suid y sgid. Un archivo ejecutable que cuente con algunos de estos permisos configurados se ejecutará en un shell con permisos vigentes del dueño del archivo (para suid) o del grupo (para suid). Por lo tanto, el programa podrá hacer cualquier cosa que el dueño o el grupo pueda hacer, según cuál es el bit de permiso establecido. Existen buenas razones para que los programas deban hacer esto. Por ejemplo, el programa passwd debe actualizar /etc/shadow, y el comando chsh, que usted usa para cambiar su shell predeterminado, debe actualizar /etc/passwd. Si usted usa un alias para ls, el listado de estos programas probablemente originará un listado en rojo, resaltado, que le servirá de advertencia, como se muestra en la Figura 2. Observe que ambos programas tiene activado suid big (s) y por lo tanto operan como si el usuario root los estuviera ejecutando.

Figura 2. Programas con permiso suid
Programs with suid permission

El Listado 53 muestra que un usuario común puede ejecutar estos archivos y actualizar archivos propiedad del usuario root.

Listado 53. Uso de programas suid
ian@attic4:~$ passwd
Changing password for ian
(current) UNIX password:
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
ian@attic4:~$ chsh
Password:
Changing the login shell for ian
Enter the new value, or press ENTER for the default
        Login Shell [/bin/bash]: /bin/dash
ian@attic4:~$ find /etc -mmin -2 -ls
308865    4 drwxr-xr-x 108 root     root         4096 Jan 29 22:52 /etc
find: /etc/cups/ssl: Permission denied
find: /etc/lvm/archive: Permission denied
find: /etc/lvm/backup: Permission denied
find: /etc/ssl/private: Permission denied
311170    4 -rw-r--r--   1 root     root         1215 Jan 29 22:52 /etc/passwd
309744    4 -rw-r-----   1 root     shadow        782 Jan 29 22:52 /etc/shadow
ian@attic4:~$ grep ian /etc/passwd
ian:x:1000:1000:Ian Shields,,,:/home/ian:/bin/dash

Usted puede establecer permisos suid y sgid para los scripts de los shells, pero la mayoría de los shells modernos ignoran estos bits para los scripts. Como habrá observado, el shell posee un poderoso lenguaje de scripts, y existen todavía más características que no se tratan en este tutorial, como por ejemplo la capacidad de interpretar y ejecutar expresiones arbitrarias. Estas características lo vuelven un entorno muy inseguro para permitir un permiso tan amplio. De manera que si usted establece un permiso suid o sgid para un script de shell, no espere que se cumpla cuando se ejecuta el script.

Anteriormente, usted cambió los permisos de myorder.sh para marcarlo como ejecutable (x). A pesar de ello, todavía debía calificar el nombre con el prefijo ./ para realmente poder ejecutarlo, a menos que lo originara en el shell actual. Para poder ejecutar un shell solamente con su nombre, se debe encontrar en su ruta, según lo representado por la variable PATH. Normalmente, usted no desea que el directorio actual esté en su ruta, ya que esto constituye una potencial falta de seguridad. Una vez que haya verificado su script y lo haya encontrado satisfactorio, deberá colocarlo en ~/nom si se trata de un scripts personal, o en /usr/local/bin si estará disponible a otros usuarios del sistema. Si usted simplemente usó chmod -x para marcarlo como ejecutable, será ejecutable para todos (propietario, grupo y el mundo). Esto es generalmente lo que usted desea, pero consulte el tutorial anterior, Preparación para el examen 101 de LPI: Dispositivos, sistemas de archivos Linux, y el Estándar de Jerarquía del Sistema de Archivos, si debe restringir el script para que solamente los miembros de un cierto grupo puedan ejecutarlo.

Es probable que haya notado que los shell usualmente se ubican en /bin más que en /usr/bin. Según el Estándar de Jerarquía del Sistema de Archivos, /usr/bin puede estar ubicado en un sistema de archivos compartido por los sistemas, por lo cual puede no estar disponible el momento de la inicialización. En consecuencia, ciertas funciones, como por ejemplo los shells, deberán ubicarse en /bin para que se encuentren disponibles incluso de aún no se ha montado /urs/bin. Los scripts creados por los usuarios no suelen tener que estar en /bin (or /sbin), ya que los programas que están en estos directorios deberían brindarle suficientes herramientas para que su sistema funcionara hasta el momento en que usted pueda montar el /usr filesystem.

Mail a root

Si su script ejecuta alguna tarea administrativa de su sistema en la quietud de la noche cuando usted duerme profundamente, ¿qué sucederá si algo funciona mal? Por suerte, es fácil enviar archivos de registro o información de errores a usted mismo, a otro administrador o al usuario root. Simplemente canalice el mensaje al comando mail, y use la opción -s para agregar una línea de asunto como se muestra en el Listado 54.

Listado 54. Envío de un correo con mensaje de error a un usuario
ian@attic4:~$ echo "Midnight error message" | mail -s "Admin error" ian
ian@attic4:~$ mail
Mail version 8.1.2 01/15/2001.  Type ? for help.
"/var/mail/ian": 1 message 1 new
>N  1 ian@localhost      Mon Jan 29 23:58   14/420   Admin error
&
Message 1:
From ian@localhost  Mon Jan 29 23:58:27 2007
X-Original-To: ian
To: ian@localhost
Subject: Admin error
Date: Mon, 29 Jan 2007 23:58:27 -0500 (EST)
From: ian@localhost (Ian Shields)

Midnight error message

& d
& q

Si usted debe enviar un archive de registro, use la función de redireccionamiento < para redirigirlo como datos de entrada al comando mail. Si debe enviar varios archivos, puede usar cat para combinarlos y canalizar los datos de salida que se van a enviar por correo. En el Listado 54, se envió un correo al usuario ian que resultó ser también el que ejecutaba el comando, aunque es más probable que los scripts admin dirijan el correo al usuario root o a otro administrador. Como es usual, consulte las páginas man sobre mail para aprender las otras opciones que usted puede especificar.

Esto nos trae al final de este tutorial. Nos hemos ocupado de una gran cantidad de material sobre shells y scripting. No se olvide de calificar este tutorial y darnos su opinión.

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Linux
ArticleID=449222
ArticleTitle=Preparación para el examen 102 de LPI, Tema 109: Shells, scripting, programación y compilación
publish-date=11232009