Capítulo 5
Scripts

Con script (escrito) se denomina programas que se escriben en texto claro y que son interpretados directamente, o sea, que no tienen que pasar por un proceso de traducción. En otros ambientes se llaman también archivos batch. Los lenguajes script se distinguen en el sentido, que ellos mismos solo tienen la funcionalidad de aglutinar comandos “externos”, los cuales pueden ser entonces de una complejidad arbitraria, sin que el lenguaje script mismo tenga muchos comandos. Existe un sinnúmero de lenguajes script, algunos ejemplos son Tcl, perl y la gran variedad de shell’s (interpretadores de comandos) como son zsh, csh, ksh, sh. El shell estándard del proyecto GNU y de Linux es bash e incorpora un lenguaje script muy poderoso.

Podemos ver un script como una listas de comandos que el sistema linux permite ejecutar automáticamente, en vez de tener que introducirlos manualmente. Hay tres conceptos que se asocian con los lenguajes script, o la programación shell: la creación de “macros”, o sea subsumir bajo un solo nuevo nombre de comando una seríe de commandos indivduales, la realización de tareas complejas a través de la combinación de varias tareas sencillas - divide and conquer, y la realización de tareas diferentes pero parecidas con un solo comando - parámetros.

Para crear un script se anotan los comandos individuales en secuencia en un archivo ascii, el cual se puede interpretar a continuación, utilizando el nombre del archivo como comando shell. Para esto se le necesita dar permiso de ejecución al archivo, ej. (“macro”):

Escriba en un archivo de nombre ’archivos’ la línea: “ls”, ejecute el commando: “chmod +x archivos”, y a continuación “./archivos”

El comando ’ls’ se ejecutara.

Vamos a ver este proceso más detallado:

  1. Bash reconoce, que “./archivos” no es un comando interno.
  2. Normalmente busca en la via de acceso (Variable PATH) si encuentra el archivo. En este caso, el nombre del archivo está especificado absolutamente (’./’ es el directorio actual, y ’archivos’ existe) entonces lo trata de ejecutar.
  3. El atributo de ejecución está activado entonces se determina si el archivo es en uno de los formatos binarios que se pueden ejecutar directamente. En este caso es un archivo de texto, no ’binario’
  4. Si la primera línea tiene la secuencia “#!” seguido por el nombre de un comando (filtro), este comando se ejecuta, usando el archivo script como entrada en stdin.
  5. En nuestro caso no existe esta línea, por lo que se ejecuta el shell por omisión (default shell) como filtro.
  6. Este nuevo bash (sub-shell) lee la línea ’ls’, y lo interpreta como si hubieramos typeado el comando a mano: Se ejecuta ’ls’ y los archivos del directorio actual se alistan en la pantalla.
  7. El nuevo bash termina y regresamos al prompt del bash originador.

5.1 Gramatica para bash

#
inicia un comentario, el resto de la línea se ignora
$
inicia una sustitución de variable
seguido por un número 0 - 9 reproduce en su lugar el texto del ’token’ 0 - 9 respectivamente en la línea de commando
seguido por un texto reproduce el contenido de una variable shell
seguido por una expresión reproduce el texto del stdout de la expresión, ejecutandola en un sub-shell.
\
preserva el siguiente carácter si este es especial, o introduce caracteres especiales, \\ se convierte en \, se llama “escape caracter”.
\n
nueva línea (newline) ; ’\\n’ se convierte en ’\n’
\r
retorno al inicio de la línea
\t
tabulador
\g
suena el timbre del terminal
(quote, single quote), protege una secuencia de caracteres y palabras del procesamiento de línea de bash; ’au\nque no’ se mantiene intacto, inclusive ’\n’
(double-quote) igual como el single quote, pero “\” y “$” y ’ preservan su función, “\” solamente si es seguido por $, ‘, ", \, or <newline>.
.StandardVariables
bash, son nombres que pueden tener asignados cadenas de caracteres arbitrarios. Comunmente se utilizan letras mayusculas, para mas fácil diferenciarlos de los comandos.

Variables pueden ser leídos y escritos en cualquier momento. La preconfiguración de la computadora asigna un juego completo a diferentes variables tradicionalmente usados en unix. El juego de este variables permite detectar desde los programas y comandos en que estado y con que usuario etc. se encuentra el sistema en cualquier instanto. Porque definen el ambiente de ejecución las variables también se llaman “variables de ambiente” o “ambiente” (Environment variables, Environment).

El comando interno de bash ’set’ escribe en stdout todas las variables de ambiente y su valor. Para definir una variable simplemente se le asigna un valor:

VARIABLE=”Vamos a la playa”

Las variables solamente son válidos para su ambiente. Si se ejecuta un sub-shell ya no son “visibles”. Ej: “echo $VARIABLE” no imprime nada en stdout. Para hacer válida una variable se tiene que “exportar”; después de definir la variable se da el comando: “export nombre” con el nombre de la variable. De esta manera se pueden crear jerarquías de validéz de variables (scope). Las variables que solo los usamos temporalmente son “protegidos” de modificaciones de sub-shells.

Bash ofrece una sintaxis simplificada: “export CAJA=523” es lo mismo como: “CAJA=523; export CAJA”, sin embargo se prefiere utilizar la versión original en archivos script, para no crear incompatibilidad con ’sh’.

5.2 Decisiones

La vida real nos exige tomarlas. Bash incluye comandos, que nos permiten

  1. Bifurcar
  2. Iterar

Estos dos elementos se conocen como control de secuencias. La bifurcación se escribe de la siguiente manera:

if TEST-COMMANDS; then 
    CONSEQUENT-COMMANDS; 
[elif MORE-TEST-COMMANDS; then  
   MORE-CONSEQUENTS;] 
[else 
    ALTERNATE-CONSEQUENTS;] 
fi

Más especial:

if [ algunos comandos ]; then 
   lista de acciones 
else 
   otra lista 
fi

El código de salida (exit status) del último comando de ’algunos comandos’ significa ’no válido’ si es 0, en todos otros casos significa ’válido’. Cada comando (inclusive los script) returna un valor numérico de salida al shell que ejecuta. En el caso más simple el shell retorna 0 si el comando se ha encontrado, otro valor en caso que no. Todos los comandos y las funciones shell describen minuciosamente como se genera su exit status, de donde se puede deducir como utilizarlos para la bifurcación. El exit status del últio comando se tiene disponible en texto mediante la variable especial $?.

La iteración se escribe de la siguiente manera:

for VAR [in WORDS ...];

   do COMMANDS; 

done

donde VAR es una variable shell creada en el acto, que se asigna en torno a cada cadena de caracteres presente en WORDS. si se omita se sustituye por $@, los parametros posicionales.

5.3 Programas shell resumidos

Veamos algunos ejemplos reales1, que nos pueden servir como machotes en caso de tener que resolver una tarea parecida:

Para leer archivos “HOWTO” comprimidos:

#!/bin/sh 

if [ ”$1” =  ]; then

   ls /usr/doc/faq/howto | less 

else 

   gunzip -c /usr/doc/faq/howto/$1-HOWTO.gz | less 

fi

Remover recursivamente archivos temporales y de respaldo de diferentes aplicaciones conocidas y comprimir ciertos tipos de archivos:

#!/bin/sh 
#SQUEEZE removes unnecessary files and compresses .tex and README files 
#By Barry tolnas, tolnas@sun1.engr.utk.edu 
# echo squeezing $PWD 
find $PWD \( -name \*~ -or -name \*.o -or -name \*.log -or -name \*\#\) -exec rm -f {} \; 
find $PWD \( -name \*.tex -or -name \*README\* -or -name \*readme\* \) -exec gzip -9 {} \; 

Convertir nombres de archivos a menusculas:

for i in * ; do [ -f $i ] && mv -i $i ‘echo $i | tr ’[A-Z]’ ’[a-z]’‘; done;

Un script que hace lo mismo:

#!/bin/sh 
# lowerit 
# convert all file names in the current directory to lower case 
# only operates on plain files--does not change the name of directories 
# will ask for verification before overwriting an existing file 
for x in ‘ls‘  
   do  
   if [ ! -f $x ]; then  
      continue 
    fi  
   lc=‘echo $x | tr ’[A-Z]’ ’[a-z]’‘  
   if [ $lc != $x ]; then 
       mv -i $x $lc  
   fi  
   done

Remover archivos core:

#!/bin/sh 
 USAGE=”$0 <directory> <message-file>” 
 if [ $# != 2 ] ; then 
    echo $USAGE exit 
fi 
 
echo Deleting... 
find $1 -name core -atime 7 -print -exec rm {} \; 
 
echo e-mailing 
for name in ‘find $1 -name core -exec ls -l {} \; | cut -c16-24‘ 
do  
   echo $name cat $2 | mail $name 
done