Linux Avanzado

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/ ...
268KB Größe 10 Downloads 70 vistas
Linux Avanzado Tema 4: Shell (bash) Personalización de shells 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 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,

1

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

Nombre Función

USER

Nombre del usuario conectado

UID

ID numérica de usuario del usuario conectado

HOME

Directorio principal del usuario

PWD

Directorio actual de trabajo

SHELL

Nombre del shell

$

La ID del proceso (o PID del shell Bash en ejecución (u otro proceso)

PPID

ID 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

2

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.

3

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 comandosetenv 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. )

4

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

5

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

6

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, solamenteusa 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'

7

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 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

8

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.

9

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

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

10

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 acut 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" " -f911: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.

11

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 comandosseq 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

12

[ian@pinguino developerworks]$ ls -l tools | sed -ne 's/^d\([^ ]* *\)\{8\}//p' java

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ámetro Propó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. { > 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 findcomo 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 ldirssin 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

15

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 comandols -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.

16

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 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

17

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

18

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

Operador Característica

-d

Directorio

-e

Existe (también -a)

-f

Archivo regular

-h

Vínculo simbólico (también -L)

-p

Canalización nombrada

-r

Legible por usted

-s

No vacío

-S

Socket

-w

Editable por usted

-N

Ha 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

Operador Verdadero si

-nt

Verifique si el archivo 1 es más reciente que el archivo 2. Para ello, se utiliza la fecha de modificación,

19

así como la siguiente comparación.

-ot

Verifique si el archivo 1 es más antiguo que el archivo 2.

-ef

Verifique 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

20

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.

21

Listado 35. Inclusión de pruebas aritméticas con [[ [ian@pinguino ~]$ [[ "abc def 0 [ian@pinguino ~]$ [[ "abc def 0 [ian@pinguino ~]$ [[ "abc def 0 [ian@pinguino ~]$ [[ "abc def 0 [ian@pinguino ~]$ [[ "abc def -bash: a: unbound variable

d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $? d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $? d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $? d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $? d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?

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)

22

-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 declarea 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 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.

31

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 () {

32

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

33

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 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.

34

Figura 2. Programas con permiso suid

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, si debe restringir el script para que solamente los miembros de un cierto grupo puedan ejecutarlo.

35

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. Recursos Aprender • El " Lenguaje de comandos de shell "define el lenguaje de comandos de shell según lo especificado por The Open Group e IEEE. •

En" Basic tasks for new Linux developers "(developerWorks, marzo de 2005), aprenda cómo abrir una ventana o un prompt de shell de una terminal y mucho más.

36



Lea los siguientes artículos en developerWorks para descubrir maneras de trabajar con Bash: o Bash by example [Bash en ejemplos], Parte 1 o Bash by example, Parte 2 o Bash by example, Parte 3 o System Administration Toolkit: Get the most out of bash [Kit de herramientas de administración de sistemas: Obtenga el máximo de bash] o Working in the bash shell [Cómo trabajar en el shell bash]



Los lenguajes sed y awk merecen un tutorial para cada uno, pero en estos artículos de developerWorks encontrará completos antecedentes de los mismos. Sed: o o o Awk: o o o o

Common threads: Sed by example [Semejanzas: Sed en ejemplos], Parte 1 Common threads: Sed by example, Parte 2 Common threads: Sed by example, Parte 3 Common threads: Awk by example [Semejanzas: Awk en ejemplos], Parte 1 Common threads: Awk by example, Parte 2 Common threads: Awk by example, Parte 3 Get started with GAWK: AWK language Fundamentals [Primeros pasos en GAWK: principios básicos del lenguaje AWK]

Autor:

David Mertz es Turing completo, pero probablemente no apruebe la Prueba de Turing. Para conocer más acerca de su vida, consulte su página Web personal. David escribe las columnas developerWorks Charming Python y XML Matters desde el año 2000. Consulte su libroText Processing in Python [Procesamiento de texto en Python]. Compilación y edición: Ing. Sergio Aguilera. Facultad de Tecnología Informática. Universidad de Belgrano. [email protected] Todos los Derechos Reservados a IBM Corp. – Marzo 2012.

37