viernes, 30 de diciembre de 2016

Nombres largos de opciones

Muchos de los comandos que hemos estudiado cuentan tanto con nombres cortos como largos para las opciones. Por ejemplo, el comando ls tiene muchas opciones que pueden expresarse tanto en forma corta como larga. Por ejemplo:

[me@linuxbox ~]$ ls -ad

y:

[me@linuxbox ~]$ ls --all --directory

son comandos equivalentes. En interés de reducir la escritura, preferimos las opciones cortas cuando incluimos opciones en la línea de comandos, pero cuando escribimos scripts, las opciones largas pueden proporcionar una legibilidad mejor.

miércoles, 28 de diciembre de 2016

Más trucos de formateado

Uno de los objetivos clave de una escritura seria de scripts es que sean fáciles de mantener; es decir, la facilidad con la que un script puede ser modificado por su autor u otros para adaptarlo a las necesidades cambiantes. Hacer que un script sea fácil de leer y comprender es una forma de facilitar un mantenimiento sencillo.

lunes, 26 de diciembre de 2016

Buenas localizaciones para los scripts

El directorio ~/bin es un buen lugar para colocar scripts destinados a uso personal. Si escribimos un script que todo el mundo en el sistema está autorizado a usar, la localización tradicional es /usr/local/bin. Los scripts destinados al uso del administrador del sistema se localizan a menudo en /usr/local/sbin. En la mayoría de los casos, el software proporcionado localmente, ya sean scripts o programas compilados, debería localizarse en la jerarquía de /usr/local y no en /bin o /usr/bin. Estos directorios son especificados por el Estándar de Jerarquía del Sistema de Archivos de Linux para contener sólo archivos proporcionados y mantenidos por el distribuidor de Linux.

viernes, 23 de diciembre de 2016

Localización del archivo de script

Con los permisos establecidos, ahora podemos ejecutar nuestro script:

[me@linuxbox ~]$ ./hello_world
Hello World!

Para que el script funcione, debemos preceder el nombre del script con una ruta específica. Si no lo hacemos, obtenemos esto:

[me@linuxbox ~]$ hello_world
bash: hello_world: command not found

¿Qué es esto? ¿Qué hace que nuestro script sea diferente de otros programas? Al parecer, nada. Nuestro script está perfecto. El problema es su localización. En el capítulo 11, vimos la variable de entorno PATH y su efecto en cómo busca el sistema los programas ejecutables. Para abreviar, el sistema busca en una lista de directorios cada vez que necesita encontrar un programa ejecutable, si no se especifica una ruta concreta. Así es como el sistema sabe que tiene que ejecutar /bin/ls cuando escribimos ls en la línea de comandos. El directorio /bin es uno de los directorios donde el sistema busca automáticamente. La lista de directorios está contenida en una variable de entorno llamada PATH. La variable PATH contiene una lista de directorios separados por dos puntos donde buscar. Podemos ver el contenido de PATH:

[me@linuxbox ~]$ echo $PATH
/home/me/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

Aquí vemos nuestra lista de directorios. Si nuestro script estuviera localizado en cualquiera de los directorios de la lista, nuestro problema estaría solucionado. Fíjate en el primer directorio de la lista, /home/me/bin. La mayoría de las distribuciones Linux configuran la variable PATH para contener un directorio bin en el directorio home del usuario, para permitir a los usuarios ejecutar sus propios programas. Así que si creamos el directorio bin y colocamos nuestro script en él, debería comenzar a funcionar como otros programas:

[me@linuxbox ~]$ mkdir bin
[me@linuxbox ~]$ mv hello_world bin
[me@linuxbox ~]$ hello_world
Hello World!

Y así lo hace.

Si la variable PATH no contiene el directorio, podemos añadirlo fácilmente incluyendo esta línea en nuestro archivo .bashrc:

export PATH=~/bin:"$PATH"

Después de hacer este cambio, tendrá efecto en cada nueva sesión de terminal. Para aplicar el cambio a la sesión de terminal actual, tenemos que hacer que el shell relea el archivo .bashrc. Esto puede hacerse mediante el comando source:

[me@linuxbox ~]$ . .bashrc

El comando punto ( . ) es un sinónimo del comando source, un shell builtin que lee un archivo específico de comandos de shell y lo trata como entrada del teclado.

Nota: Ubuntu añade automáticamente el directorio ~/bin a la variable PATH si el directorio ~/bin existe cuando el archivo .bashrc del usuario se ejecute. Así que, en sistemas Ubuntu, si creamos el directorio ~/bin y luego salimos y volvemos a entrar, todo funciona.

miércoles, 21 de diciembre de 2016

Permisos de ejecución

Lo siguiente que tenemos que hacer es hacer nuestro script ejecutable. Esto se hace fácilmente usando chmod:

[me@linuxbox ~]$ ls -l hello_world
-rw-r--r-- 1 me me 63 2009-03-07 10:10 hello_world
[me@linuxbox ~]$ chmod 755 hello_world
[me@linuxbox ~]$ ls -l hello_world
-rwxr-xr-x 1 me me 63 2009-03-07 10:10 hello_world

Hay dos configuraciones comunes de permisos para los scripts; 755 para scripts que puede ejecutar todo el mundo, y 700 para scripts que sólo puede ejecutar el propietario. Fíjate que los scripts deben ser legibles para ser ejecutados.

lunes, 19 de diciembre de 2016

Formato del archivo de script

Siguiendo la tradición de la programación, crearemos un programa "hola mundo" para demostrar un script tremendamente simple. Así que arranquemos nuestros editores de texto e introduzcamos el siguiente script:

#!/bin/bash
# This is our first script.
echo 'Hello World!'

La última línea de nuestro script es muy familiar, sólo un comando echo con un argumento de cadena. La segunda línea también es familiar. Parece un comentario que hemos visto usado en muchos de los archivos de configuración que hemos examinado y editado. Una cosa sobre los comentarios en scripts de shell es que también pueden aparecer al final de las líneas, así:

echo 'Hello World!' # This is a comment too

Todo lo que hay desde el símbolo # en adelante en la línea es ignorado.

Como muchas cosas, esto también funciona en la línea de comandos:

[me@linuxbox ~]$ echo 'Hello World!' # This is a comment too
Hello World!

Aunque los comentarios son poco útiles en la línea de comandos, también funcionan.

La primera línea de nuestro script es un poco misteriosa. Parece como si fuera un comentario, ya que comienza con #, pero parece demasiado significativa para ser sólo eso. La secuencia de caracteres #! es, de hecho, una construcción especial llamada shebang. El shebang se usa para decirle al sistema el nombre del intérprete que debería usarse para interpretar el script que sigue. Cada script de shell debería incluirlo en su primera línea.

Guardemos nuestro archivo de script como hello_world.

viernes, 16 de diciembre de 2016

Cómo escribir un script de shell

Para crear y ejecutar con éxito un script de shell, necesitamos hacer tres cosas:
  1. Escribir un script. Los scripts de shell son comunes archivos de texto. Así que necesitamos un editor de texto para escribirlos. Los mejores editores de texto proporcionarán destacado sintáctico, permitiéndonos ver una vista codificada por colores de los elementos del script. El destacado sintáctico nos ayudará a encontrar algunos tipos de errores comunes. vim, gedit, kate y muchos otros editores son buenos candidatos para escribir scripts.
  2. Hacer el script ejecutable. El sistema es un poco quisquilloso en no dejar que ningún archivo de texto antiguo sea tratado como un programa, ¡y es por una buena razón! Necesitamos configurar los permisos del archivo del script para que se permita la ejecución.
  3. Poner el script en algún lugar donde el shell pueda encontrarlo. El shell busca automáticamente archivos ejecutables en ciertos directorios cuando no se especifica una ruta concreta. Para mayor comodidad, colocaremos nuestros scripts en dichos directorios.

miércoles, 14 de diciembre de 2016

¿Qué son scripts de shell?

En términos sencillos, un script de shell es un archivo que contiene una serie de comandos. El shell lee este archivo y ejecuta los comandos como si se hubieran introducido directamente en la línea de comandos.

El shell es algo único, en cuanto a que es una interfaz poderosa de línea de comandos para el sistema y un intérprete de lenguaje de script. Como veremos, la mayoría de las cosas que pueden hacerse con la línea de comandos puede hacerse con scripts, y la mayoría de las cosas que pueden hacerse con scripts pueden hacerse en la línea de comandos.

Hemos visto muchas características del shell, pero nos hemos enfocado en aquellas más usadas directamente en la línea de comandos. El shell también proporciona una serie de características que normalmente (aunque no siempre) se usan cuando escribimos programas.

lunes, 12 de diciembre de 2016

Escribiendo tu primer script

En los capítulos anteriores, hemos reunido un arsenal de herramientas de línea de comandos. Aunque estas herramientas pueden resolver muchos tipos de problemas de computación, todavía estamos limitados a usarlas manualmente una a una en la línea de comandos. ¿No sería genial si pudiéramos usar el shell para hacer la mayoría del trabajo? Podemos. Poniendo nuestras herramientas juntas en programas diseñados por nosotros, el shell puede ejecutar secuencias completas de tareas por sí mismo. Podemos habilitarlo para hacerlo escribiendo scripts de shell.

miércoles, 7 de diciembre de 2016

Resumiendo

En este capítulo, hemos visto cómo tres simples comandos:

./configure
make
make install

pueden usarse para construir muchos paquetes de código fuente. También hemos visto el importante rol que make juega en el mantenimiento de programas. El programa make puede usarse para cualquier tarea que necesite mantener una relación objetivo/dependencia, no sólo para compilar código fuente.

lunes, 5 de diciembre de 2016

Instalando el programa

El código fuente bien empaquetado a menudo incluirá un objetivo especial de make llamado install. Este objetivo instalará el producto final en un directorio del sistema para su uso. Generalmente, este directorio es /usr/local/bin, la localización tradicional para el software construido localmente. Sin embargo, este directorio normalmente no es modificable por los usuarios normales, así que tenemos que ser superusuario para realizar la instalación:

[me@linuxbox diction-1.11]$ sudo make install

Después de realizar la instalación, podemos comprobar que el programa está listo:

[me@linuxbox diction-1.11]$ which diction
/usr/local/bin/diction
[me@linuxbox diction-1.11]$ man diction

¡Y ahí lo tenemos!

viernes, 2 de diciembre de 2016

Construyendo el programa

La mayoría de los programas se construyen con una secuencia simple de dos comandos:

./configure
make

El programa configure es un script de shell que es proporcionado por el árbol de código fuente. Su trabajo es analizar el entorno de construcción. La mayoría del código fuente se diseña para ser portable. Es decir, se diseña para construirse en más de un tipo de sistema tipo Unix. Pero para hacer eso, el código fuente puede necesitar someterse a leves ajustes durante la construcción para adaptarse a las diferencias entre sistemas. configure también comprueba que se instalen las herramientas externas y los componentes necesarios. Ejecutemos configure. Como configure no está localizado donde el shell espera normalmente que estén almacenados los programas, tenemos que decirle al shell explícitamente su localización precediendo el comando con ./ para indicar que el programa se localiza en el directorio de trabajo actual:

[me@linuxbox diction-1.11]$ ./configure

configure producirá un montón de mensajes a medida que prueba y configura la construcción. Cuando termina, tendrá un aspecto como este:

checking libintl.h presence... yes
checking for libintl.h... yes
checking for library containing gettext... none required
configure: creating ./config.status
config.status: creating Makefile
config.status: creating diction.1
config.status: creating diction.texi
config.status: creating diction.spec
config.status: creating style.1
config.status: creating test/rundiction
config.status: creating config.h
[me@linuxbox diction-1.11]$

Lo importante aquí es que no hay mensajes de error. Si los hubo, la configuración falló, y el programa no se construirá hasta que se corrijan los errores.

Vemos que configure ha creado varios archivos nuevos en nuestro directorio fuente. El más importante es Makefile. Makefile es un archivo de configuración que indica al programa make cómo construir exactamente el programa. Sin él, make no funcionará. Makefile es un archivo de texto ordinario, así que podemos verlo:

[me@linuxbox diction-1.11]$ less Makefile

El programa make toma como entrada un makefile (que normalmente se llama Makefile), que describe las relaciones y dependencias entre los componentes que componen el programa finalizado.

La primera parte de makefile define variables que son sustituidas en secciones posteriores del makefile. Por ejemplo vemos la línea:

CC=           gcc

que define que el compilador C será gcc. Más adelante en el makefile, vemos una instancia donde se usa:

diction: diction.o sentence.o misc.o getopt.o getopt1.o $(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o \ getopt.o getopt1.o $(LIBS)

Aquí se realiza una sustitución, y el valor $(CC) se reemplaza por gcc en el momento de la ejecución.

La mayoría del makefile consiste en líneas, que definen un objetivo, en este caso el archivo ejecutable diction, y los archivos de los que depende. Las líneas restantes describen el/los comando/s necesarios para crear el objetivo desde sus componentes. Vemos en este ejemplo que el archivo ejecutable diction (uno de los productos finales) depende de la existencia de diction.o, sentence.o, misc.o, getop.o y gestopt1.o. Más adelante aún, en el makefile, vemos las definiciones de cada uno de estos objetivos:

diction.o:  diction.c config.h getopt.h misc.h sentence.h
getopt.o:   getopt.c getopt.h getopt_int.h
getopt1.o:  getopt1.c getopt.h getopt_int.h
misc.o:     misc.c config.h misc.h
sentence.o: sentence.c config.h misc.h sentence.h
style.o:    style.c config.h getopt.h misc.h sentence.h

Sin embargo, no vemos ningún comando especificado para ellos. Esto es gestionado por un objetivo general, anteriormente en el archivo, que describe el comando usado para compilar cualquier archivo .c en un archivo .o:

.c.o:
            $(CC) -c $(CPPFLAGS) $(CFLAGS) $<

Todo esto parece muy complicado. ¿Por qué no listamos simplemente todos los pasos para compilar las partes y terminamos? La respuesta a esto se aclarará en un momento. Mientras tanto, ejecutemos make y construyamos nuestros programas:

[me@linuxbox diction-1.11]$ make

El programa make se ejecutará, usando los contenidos de Makefile para guiar sus acciones. Producirá un montón de mensajes.

Cuando termine, veremos que todos los objetivos están presentes ahora en nuestro directorio:

[me@linuxbox diction-1.11]$ ls
config.guess  de.po           en           install-sh  sentence.c
config.h      diction         en_GB        Makefile    sentence.h
config.h.in   diction.1       en_GB.mo     Makefile.in sentence.o
config.log    diction.1.in    en_GB.po     misc.c      style
config.status diction.c       getopt1.c    misc.h      style.1
config.sub    diction.o       getopt1.o    misc.o      style.1.in
configure     diction.pot     getopt.c     NEWS        style.c
configure.in  diction.spec    getopt.h     nl          style.o
COPYING       diction.spec.in getopt_int.h nl.mo       test
de            diction.texi    getopt.o     nl.po
de.mo         diction.texi.in INSTALL      README

Entre los archivos, vemos diction y style, los programas que elegimos construir. ¡Tengo que darte la enhorabuena! ¡Acabamos de compilar nuestros primeros programas desde código fuente!

Pero sólo por curiosidad, ejecutemos make de nuevo:

[me@linuxbox diction-1.11]$ make
make: Nothing to be done for `all'.

Sólo produce un extraño mensaje. ¿Qué está pasando? ¿Por qué no ha construido el programa de nuevo? Ah, esta es la magia de make. En lugar de simplemente construirlo todo de nuevo, make sólo construye lo que necesita construirse. Con todos los objetivos presentes, make ha determinado que no hay nada que hacer. Podemos demostrar esto eliminando uno de los objetivos y ejecutando make de nuevo para ver qué hace. Deshagámonos de uno de los objetivos intermedios:

[me@linuxbox diction-1.11]$ rm getopt.o
[me@linuxbox diction-1.11]$ make

Vemos que make reconstruye y reenlaza los programas diction y style, ya que dependen del módulo perdido. Este comportamiento también indica otra característica importante de make: mantiene los objetivos actualizados. make insiste en que los objetivos sean más nuevos que sus dependencias. Esto tiene mucho sentido, como programador a menudo actualizarás algo de código fuente y luego usarás make para construir una nueva versión del producto finalizado. make se asegura que se construya todo lo que se necesita construir basándose en el código actualizado. Si usamos el programa touch para "actualizar" uno de los archivos de código fuente, podemos ver lo que ocurre:

[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2007-03-30 17:45 getopt.c
[me@linuxbox diction-1.11]$ touch getopt.c
[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c
[me@linuxbox diction-1.11]$ make

Después de que make se ejecute, vemos que ha restaurado el objetivo para que sea más nuevo que la dependencia:

[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:24 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c

La capacidad de make de construir inteligentemente sólo lo que necesita ser construido es un gran beneficio para los programadores. Aunque el ahorro de tiempo no es muy evidente en nuestro pequeño proyecto, es muy significativo para proyectos más grandes. Recuerda, el kernel Linux (un programa sometido a modificaciones y mejoras constantes) contiene varios millones de líneas de código.