PARA SHELL DE UNIX[/size]
¿Qué es un guión (script)?Un guión o script es un fichero de texto que contiene una serie de instrucciones que se pueden ejecutar en la línea de órdenes, y que se ejecutarán seguidas. En ese sentido son iguales que los ficheros con extensión BAT de MS-DOS, aunque, por supuesto, como en los sistemas UNIX no existe el concepto de extensión, pueden tener cualquier nombre. El único requisito es que ese fichero de texto tenga permiso de ejecución para la persona que intenta ejecutarlo. Alternativamente, puedes llamar al intérprete y darle como parámetro el nombre del guión, pero es más cómodo dar permiso de ejecución al guión para ahorrarnos escribir el nombre del intérprete.
Esas instrucciones se procesarán por orden, como si alguien las escribiera en el mismo orden en la línea de órdenes (no es exactamente así, pero casi). Por ello, van una por línea (los saltos de línea se interpretan igual que si hubieras pulsado INTRO), o separadas por caracteres ``punto y coma'' (
si están en la misma línea. Cuando se ejecuta un guión para el intérprete de órdenes, se intenta ejecutar como un binario normal, pero al darse cuenta el sistema operativo de que no es un binario válido, pero que es un archivo de texto, ejecuta un intérprete hijo y empieza a interpretar el fichero de texto. Si fuera un archivo con un texto cualquiera, tarde o temprano (probablemente en la primera línea) habría un error de sintaxis y se abortaría la ejecución del programa.
ComentariosComo el resto de los lenguajes de programación (al menos todos los que he visto yo), hay un carácter o unos caracteres especiales que denotan comentario. Ese texto es ignorado por el intérprete al leer el guión, y sólo sirven para que una persona, cuando edite el guión, pueda entender cómo funciona. Este carácter especial es la almohadilla `#', y denota que desde ahí hasta el final de la línea, el resto de los caracteres son un comentario del programador (como el `//' de C++).
RedireccionesAl igual que si fuera un ejecutable cualquiera, un guión permite redireccionamientos de la entrada y de las salidas. No ocurre lo mismo con los ficheros BAT de MS-DOS, por razones obvias (el MS-DOS está mal hecho). Las redirecciones de la entrada, salida estándar, errores estándar, y todas las salidas se realizan con los caracteres <, >, 2> y &> (depende del intérprete, así son las del bash), como estoy suponiendo que ya sabes. Algo que puede que no sepas es que existe un fichero-dispositivo en UNIX, estándar a todos los clones (en Linux está), que es el /dev/null. Cualquier cosa que es escriba en ese dispositivo virtual va a parar a un agujero negro, es decir, se ignora completamente. ¿Y para qué sirve esto?, puede que te preguntes. Pues si quieres comprobar si un programa devuelve un error, pero no quieres que la salida estándar aparezca en pantalla (porque al usuario no le interesa, por ejemplo), entonces redireccionas la salida total (con &>) a /dev/null.
El truco del #!Por lo general, en los guiones de los sistemas UNIX se suele incluir una línea especial, que empieza por #! (deben ser los dos primeros caracteres del fichero). La historia de esto es larga, y no la sé exactamente, pero el caso es que sirve para ejecutar un fichero con un intérprete determinado (no sólo intérpretes de órdenes como bash o el intérprete C, sino también como Perl, Tcl o Python).
Lo único que debes saber es que todos tus guiones deben empezar con una línea como:
#!/bin/bash
En general, debe empezar con los caracteres #!, seguidos de la ruta completa al intérprete que desees utilizar para ese guión. Si no utilizas características específicas de bash, deberías poner #!/bin/sh.
¿Y cómo sabes qué intérprete quieres utilizar? Por lo general, bastará con /bin/sh. Si utilizas características específicas del intérprete Korn o del bash, entonces deberías utilizar /bin/ksh o /bin/bash respectivamente. Si el intérprete fuera del lenguaje Perl, por ejemplo (nada que ver con este tutorial), querrías que se ejecutara el programa perl y que recibiera como parámetro el fichero en el que has escrito esa línea mágica. Entonces, para hallar la ruta completa al programa perl, escribes which perl, y el sistema te responderá algo como /usr/bin/perl o /usr/local/bin/perl. Tú pones la línea que te devuelva y listo.
VariablesComo en los lenguajes de programación corrientes, en la programación en shell existen las variables, que son nombres simbólicos para zonas de memoria que almacenan datos que nos interesan. Pero al contrario que los lenguajes de alto nivel normales, las variables de los guiones no tienen tipo, o quizás sería más apropiado decir que tienen un tipo único y permanente: a todos los efectos se tratan como ristras de caracteres.
Para poder hacer sumas y restas hay una orden especial, la orden expr. Esta orden tiene que recibir cada número u operando como un parámetro independiente, así que devolverá un error si intentas hacer algo como expr 5+7. No vale la pena extenderse aquí, aunque si quieres más información sobre esta instrucción o cualquier otra característica de la programación en shell, ya sabes adonde acudir.
Referenciar variablesLas variables de los guiones no se declaran, y siempre están inicializadas con una ristra vacía o nula. Nótese que esto no es lo mismo que contener un espacio. Una ristra vacía o nula es una ristra que no contiene ningún caracter.
Hay que tener cuidado al asignar valores a las variables, ya que no se debe dejar ningún espacio entre el signo de asignación (el `=') y la variable o el valor asignado (algo que, por otra parte, es una muy buena costumbre cuando da igual).
Para referirse a las variables, hay que utilizar el signo dólar ($) antes del nombre de ésta, siempre que nos refiramos a ellas para consultar su valor (si asignamos un valor a la variable, o utilizamos la orden read, que escribe en ella, NO hay que poner el signo de dólar). Si nos olvidamos del signo dólar, y hacemos algo parecido a:
y=hola
x=y
Nos encontraremos con la desagradable sorpresa de que el valor de x es el carácter y, y no los caracteres hola, como quizás pretendiéramos. Para hacer la asignación correctamente, tendríamos que haber escrito:
y=hola
x=$y
Como en todas o al menos la mayoría de las cosas en UNIX, los nombres son case sensitive, es decir, que no es lo mismo y que Y (¡aviso para los programadores acostumbrados al MS-DOS!).
¿Cómo funciona el analizador del intérprete?Es útil saber el funcionamiento del analizador sintáctico (parser) del intérprete de órdenes a la hora de programar. Al fin y al cabo, significa saber cómo va a interpretar nuestras órdenes.
Como la mayoría de los analizadores de los lenguajes interpretados, primero realiza algunas sustituciones, al leer la orden, y luego ejecuta la orden que le queda después de las sustituciones.
Las sustituciones hechas por el intérprete de órdenes de UNIX son sustituciones de comillas y sustituciones de variables (lo que vea con un signo dólar que no esté ``escapado''). Según el intérprete en particular, hará algunas sustituciones más, como sustituciones del caracter ~ (que indica el directorio raíz del usuario), los caracteres {}, los caracteres [], etc.
Las sustituciones de variables son lo que más nos interesa. Cuando el analizador se encuentra un singo de dólar, que no tenga antes una barra invertida (\), lo interpreta como una variable. Lo que hace, entonces, es avanzar hasta que tiene el nombre completo de la variable, y cuando lo tiene, sustituye el dólar y el nombre por el valor de la variable. Si no existe ninguna variable que tenga ese nombre, no es un error. Simplemente lo sustituye por nada. Este comportamiento puede acarrear algunos fallos difíciles de descubrir. Por ejemplo, al procesar la línea
mensaje="$foo y alguna otra cosa"
El analizador dectecta el signo $, que indica variable, avanza hasta que tiene completo el nombre de la variable (el nombre es, por supuesto foo), y sustituye la ristra $foo por el contenido de la variable en cuestión. Es importante tener esto en cuenta, porque si quisiésemos poner bar después del contenido de la variable $foo, no podríamos escribir $foobar, porque el analizador creería que estaríamos consultando la variable foobar. Lo que tenemos que hacer es ``delimitar'' el nombre de la variable con llaves, quedando ${foo}bar.
Dos órdenes útiles: echo y readDos órdenes bastante útiles para probar algunas estupideces con variables son echo y read. La primera es como su homónimo de MS-DOS (o mejor dicho: el de MS-DOS es una copia, probablemente peor, de su homónimo UNIX), para mostrar texto por pantalla, y la segunda, como su nombre indica, es para leer del teclado y poner el resultado en una o varias variables. El funcionamiento de read es el siguiente: lee del teclado una ristra, y va asignando las palabras en orden a cada una de las variables que se le pasen como parámetros, asignando el resto de la ristra a la última variable. Es decir, que si se le pasan tres variables como parámetro, se asigna la primera palabra a la primera variable, la segunda a la segunda variable, y el resto de la ristra leída a la tercera variable. Por ejemplo:
$ read foo bar
Escribo demasiadas palabras
$ echo $foo
Escribo
$ echo $bar
demasiadas palabras
ParámetrosLos parámetros son variables normales, que tienen los nombres $1, $2 ... $9. Aunque se pueden dar más de nueve parámetros a un guión para el intérprete de órdenes, sólo se puede acceder de forma directa a los nueve primeros. La orden shift permite desplazar los parámetros de sitio, de tal forma que sean accesibles los que estén más allá del noveno, con el inconveniente de no poder acceder a los primeros. En realidad, en el Korn shell (y en bash) se puede acceder al resto de los parámetros directamente con la construcción ${número}. El funcionamiento de shift es el siguiente:
Supongamos que tenemos como parámetros $1=-o, $2=foo y bar, por llamar al guión (suponiendo que el nombre del guión es compila) así:
compila -o foo bar
Lo que queremos es quitarnos de enmedio las opciones, después de haberlas procesado, de tal forma que el tercer parámetro (bar) se quede como primero. Lo que haremos, entonces, es llamar dos veces a shift, o llamar a shift con el parámetro 2. Teniendo este código:
shift 2
echo $1
Y suponiendo la llamada anterior, el resultado por pantalla sería bar.
Las variables $#, $*, $0 nos permiten saber el número de parámetros pasados al guión, la ristra entera de todos los parámetros pasados, y el nombre del programa que se ha llamado. Esto último puede parecer estúpido, pero piensa que en UNIX pueden hacerse enlaces a ficheros. Si utilizas Linux, cuando llamas al vi en realidad estás ejecutando el fichero ejecutable vim. Fíjate cuando hagas un ls -l en tu directorio /usr/bin. Tendrá que salir un fichero parecido a este:
rwxr-xr-x root root vi->vim
Eso significa que el fichero vi no es un ejecutable, sino un enlace que apunta al fichero vim del mismo directorio. Así, por ejemplo, hay un caso parecido con awk, que apunta a gawk. Cuando llamas a cualquiera de los dos programas sin parámetros, te da la ayuda, diciéndote cada uno de ellos que se llama con el nombre con el que tú le hayas llamado (con esto no quiero decir que el awk/gawk sea un guión, por supuesto, pero los lenguajes de alto nivel tienen una opción parecida).
Valores devueltos. Orden exitLos valores devueltos por los programas, por convenio, son 0 si el programa ha ido perfectamente (cero porque es el código de error), y un número distinto de cero, normalmente 1 o -1 para indicar algún tipo de error. Es de notar que cuando ejecutemos un pipe, el valor devuelto por el pipe será el del programa que se ejecutó en último lugar.
Esto nos sirve para dos cosas: primero, para saber por qué preguntar cuando chequeemos el valor devuelto por un programa. Y segundo, para devolver los valores establecidos por convenio cuando salgamos de nuestros programas (ya sea hechos con lenguajes de alto nivel o guiones).
La forma de devolver valores al salir del programa es darlos como parámetro a la orden exit. Si lo hacemos sin parámetros, se devolverá un cero.
Asignación de caracteres especialesPor último, un apunte sobre asignar caracteres especiales para el intérprete a variables: cuando nosotros asignamos algo a una variable, se asigna tal cual nosotros lo vemos en la pantalla (cuando son caracteres especiales), y la sustitución se hace cuando la utilicemos en alguna expresión, si entra dentro de una orden. Veamos el siguiente ejemplo como aclaración de lo dicho.
cd /
asterisco=*.txt
cd /home/zoso/cambio
ls $asterisco
Si ejecutamos el siguiente código, la salida de éste será la lista de ficheros que concuerdan con la expresión regular almacenada en la variable asterisco, pero los que se encuentren en el directorio /home/zoso/cambio, no los que se encuentren en el directorio raíz.
ComillasEl uso de las comillas en la programación en shell es un tema bastante importante. En los lenguajes de alto nivel normalmente las comillas sólo se utilizan para las ristras, y no necesitan un aprendizaje especial para usarlas: simplemente saber (en el lenguaje C, por ejemplo) que las comillas simples son caracteres y que las comillas dobles requieren reservar memoria, porque son ristras, o sea, punteros a caracteres.
Tres tipos de comillasEn la programación en shell en UNIX hay tres tipos de comillas útiles: La comilla simple ('), la comilla doble (") y la comilla invertida (`). Cada una tiene un propósito diferente, y se deben saber usar todas bien.
La comilla simple (') sirve para coger literales. Es importante darse cuenta de que algo encerrado entre comillas simples siempre es un solo parámetro, aunque sea una ristra vacía ('').
La comilla doble sirve para sustituir lo que hay dentro (valores de variables, caracteres especiales), pero después de sustituir queremos tratarlo como si estuviera encerrado entre comillas simples. Esto es útil cuando, por ejemplo, tenemos que dar un parámetro a un programa, pero ese parámetro es leído del teclado y no sabemos si la ristra es vacía. Si nos tenemos que asegurar de que se trata como un parámetro, tendríamos que encerrarlo entre comillas simples (recordemos que '' sigue siendo un parámetro, aunque vacío), pero si lo encerramos entre comillas simples no se sustituirá la variable. La forma de hacerlo es encerrando la variable entre comillas dobles.
Veamos esto en el siguiente ejemplo:
x=100
y='500$x'
echo $y # Esto enseñará `500$x' en pantalla
y="500$x"
echo $y # Esto enseñará `500100' en pantalla
Las que quedan son las comillas invertidas (`). Estas se utilizan con órdenes, y se sustituyen por la salida del programa que está entre las comillas invertidas. Por ejemplo, si queremos almacenar en una variable el número de usuarios que hay actualmente en el sistema, podemos hacer
usuarios=`who | wc -l`
Por si queda alguna duda, who dice los usuario que hay en el sistema, poniendo uno en cada línea, y wc -l cuenta las líneas que tiene el texto que le entre por la entrada estándar, sacando el número resultante a la salida.
Separadores de parámetrosHay otras razones para desear poner parámetros encerrados entre comillas simples o dobles: si queremos el literal exacto del contenido de una variable.
Si una variable (por ejemplo, porque se le ha asignado la salida estándar de un programa) contiene saltos de línea, y no ponemos comillas, entonces el intérprete lo entenderá como que esos saltos de línea son separadores de parámetros, y por ejemplo el echo los separará con un espacio. Tendremos un problema parecido si ponemos más de un espacio para separar los parámetros:
x='10 y 5 espacios'
echo $x # Saldrá en pantalla '10 y 5 espacios'
echo `$x' # Saldrá en pantalla `$x'
echo "$x" # Saldrá en pantalla '10 y 5 espacios'
CondicionesManejar condiciones es importante en todos los lenguajes de programación: sirven para controlar el flujo del programa (el título del próximo capítulo), y eso lo es prácticamente todo.
En UNIX, al contrario de lo que podría parecer, el valor entero que representa al valor verdadero booleano es el 0, y el valor entero que representa al falso es el 1. Esto es así por el convenio antes citado de los valores devueltos por los programas en UNIX.
La orden testPara ello, en UNIX existe la orden test. Tiene una sintaxis alternativa, que es con mucho la más usada (es más clara), que es con corchetes ([]). Es importante darse cuenta de que la segunda sintaxis es también un programa (que se llama [, aunque suene raro, y que exige, supongo, que al final de la condición se ponga un ], más por estética que por otra cosa), así que necesita que lo que pongamos entre los corchetes esté separado por al menos un espacio (incluido el último corchete).
La orden test (con cualquiera de las dos sintaxis) acepta dos tipos de operadores: los unarios (que sólo tienen un parámetro) y los binarios (que necesitan dos parámetros). Si al principio de una condición ponemos un `!', separado por espacios de lo demás, como siempre, negaremos la condición que estemos poniendo en el test.
Operadores para el manejo de ficherosTodos los operadores para el manejo de ficheros en la programación en shell son unarios. Cuando se utilizan los operadores unarios con la orden test, la sintaxis es la siguiente: test operador fichero. Los operadores de ficheros que acepta la orden test son los siguientes:
Operador Significado
-f Si fichero es un fichero ordinario
-d Si fichero es un directorio
-r Si el proceso tiene permiso de lectura sobre fichero
-w Si el proceso tiene permiso de escritura sobre fichero
-x Si el proceso tiene permiso de ejecución sobre fichero
-s Si fichero tiene una longitud mayor que cero
Operadores para el manejo de ristrasLos operadores para el manejo de ristras en la programación en shell son dos binarios, y la dos unarios (en realidad dan las condiciones contrarias). La sintaxis de test con operadores binarios, como era de esperar, es test operando1 operador operando2. La tabla de operadores de manejo de ristras es la siguiente:
Operador Significado
= (binario) Si operando1 es igual a operando2
!= (binario) Si operando1 es distinto de operando2
-z (unario) Si operando es una ristra nula
-n (unario) Si operando es una ristra no nula
Operadores para el manejo de númerosBueno bueno . Ya sé que he dicho que no existen los tipos en las variables del intérprete, pero aún así es posible que la ristra ``1'' o ``20'' sea tratada como un número, así que todavía son necesarios los operadores para el manejo de números. Los operadores de manejo de números son siempre binarios, y son los siguientes:
Operador Significado
-eq operando1 y operando2 son iguales
-ne operando1 y operando2 son distintos
-gt operando1 es mayor estricto que operando2
-ge operando1 es mayor o igual que operando2
-lt operando1 es menor que operando2
-le operando1 es menor o igual que operando2
Expresiones complicadasAlgunas veces vas a necesitar evaluar expresiones complicadas en un guión, como condiciones dobles (con y o con o), y puede que incluso condiciones triples (más lo dudo).
Este es un tema delicado en la programación de shell: la forma de hacerlo es con los operadores -a y -o (and y or respectivamente), con lo que una expresión doble puede quedar tal que así (de horrible):
[ -w /home/zoso/.profile -a -n $MAIL ]
¿Feo, verdad? Esto comprueba si el proceso tiene permiso de escritura sobre el fichero /home/zoso/.profile y si la variable $MAIL tiene algún contenido.
Pues todavía no ha llegado lo peor: Si quisiésemos realizar condiciones dobles, y poner paréntesis, hay un problema añadido, y es que los paréntesis son caracteres especiales para el intérprete. Entonces, para que no los reconozca como paréntesis, y que coja los paréntesis literales, como texto, hay que escribirlos como \( y \) (sí, utilizando el símbolo \ como en C y en otros lenguajes de programación relacionados con el UNIX). Así, una condición doble quedaría en el lenguaje del intérprete así que ilegible:
[ -d ~/Mail -o \( -f ~/mail -a $cont -ne 0 \) ]
Este ejemplo comprueba si existe el directorio ~/Mail o si existe el fichero -f ~/mail y además la variable $cont, interpretada como un número, es distinta de 0. Es importante acordarse de escribir espacios entre los paréntesis, todos los operadores y los corchetes, porque al fin y al cabo van a ser interpretados por un programa que espera que se presenten todos como parámetros diferentes.
Control del flujo del programaAhora es cuando vamos a empezar a ser capaces de programar algo en shell. Las estructuras de control de flujo del programa que tenemos disponibles cuando programamos en shell son el if, case, while, for y until. Además, veremos algunas órdenes especiales y algunas construcciones un poco más raras, que sirven también para controlar el curso de los acontecimientos.
Estructura ifLa estructura if tiene una sintaxis algo inusual, porque necesita la palabra then, pero en la línea siguiente a donde está el if y la condición. Es un if bastante versátil, ya que permite cláusulas elif (else if) y else. La palabra para indicar el fin de la estructura if es la palabra fi (if al revés).
La cláusula elif, por si no lo sabes, es parecida al else, aunque necesita una condición después. Para comprender mejor el funcionamiento, veamos la siguiente equivalencia:
if [ condicion ] if [ condicion ]
then then
... ...
else elif [ condicion2 ]
if [ condicion2 ] then
then ...
... fi
fi
fi
Algunas personas, para hacer la sintaxis más clara, o al menos más parecida a otros lenguajes, como Pascal, escriben el if de la siguiente forma:
if [ condicion ]; then
fi
Para decidir qué ejecutar, la estructura if permite, además de una condición, el nombre de un programa. Lo que se hará entonces es ejecutar el programa con los parámetros dados, y dar como verdadero (y ejecutar lo que haya entre el if y el fi o el elif o el else) que el programa devuelva un cero.
En estos casos es muy útil la instrucción nula (
, ya que si lo que queremos es ejecutar un código si el programa va mal, entonces la única forma de hacerlo es la siguiente:
if orden
then
:
# No hacemos nada
else
codigo
...
fi
Como ejemplo tomaremos unas líneas que están en mi fichero ~/.bashrc:
if set | grep DISPLAY &>/dev/null; then
alias vi="vi -g"
fi
Esto ejecuta la orden set, que da las variables de entorno. Las filtra para quedarnos sólo con la que contenga la variable $DISPLAY (si es que está), y manda toda la salida a un agujero negro (/dev/null). Esto se hace para comprobar el código de salida del grep, que devolverá verdadero si deja ``salir'' alguna línea. Es decir: si está definida la variable $DISPLAY, se entra a ejecutar el alias (en realidad, se entra si existe la variable $DISPLAY o si alguna variable tiene como contenido algo con la ristra DISPLAY, lo cual es bastante improbable. Hay una forma de afinar la condición, te lo dejo como ejercicio). Si el grep devuelve falso, no se ejecuta nada.
Condiciones particularesHay dos condiciones que son relativamente comunes, que son ejecutar un código si algo inmediatamente anterior ha ido bien, o justo lo contrario, ejecutarlo sólo si lo anterior ha ido mal.
Por ello, hay dos ``estructuras'' que nos permiten manejar estas situaciones de una forma más cómoda y limpia. Estas estructuras son && y ||. Para las personas que hayan programado en C, les resultarán familiares. El && es el equivalente a la palabra reservada de Pascal and y || es el equivalente del or en Pascal.
La forma de entender qué produce cada estructura, es pensar que el intérprete sólo va a ejecutar lo estrictamente necesario para saber cómo termina la condición. Sabemos que false && cualquiercosa da falso, y que true || cualquiercosa da verdadero, así que eso es lo que va a hacer el intérprete: si utilizamos la estructura &&, ejecutará la primera instrucción, y sólo si va bien (si devuelve un cero) ejecutará la segunda, y análogamente con la estructura ||, el intérprete ejecutará la primera instrucción, y sólo ejecutará la segunda si la primera ha ido mal, es decir, si el valor de la condición total depende de lo que pase al ejecutar la segunda instrucción.
La forma de acordarse es pensarlo como en lenguaje natural: ``Hazlo y te odiaré para siempre''. O sea, que si la primera instrucción se ejecuta (...satisfactoriamente; esto es, devolviendo un 0), pasará la segunda. Análogamente, ``Hazlo o te odiaré para siempre''. O sea, si la primera no se ejecuta (satisfactoriamente), se ejecutará la segunda. Pero dejémosnos de amenazas ;-).
La estructura && puede utilizarse cuando ejecutemos una instrucción cuyo trabajo depende de que la primera haya ido bien, y la || puede ejecutarse para dar mensajes de error:
cd foo || echo "¡No puedo entrar en 'foo'!"
Estructura caseLa estructura case del Bourne Shell y compatibles es una estructura parecida a la de Pascal: cuando entra en la estructura, sale cuando empiece la siguiente etiqueta (no así en C, en el que las etiquetas del case actúan como etiquetas de un JMP en ensamblador: una vez que entran en una etiqueta, no salen del case hasta que éste acaba completamente).
Las etiquetas pueden ser cualquier ``expresión regular'' (expresión con comodines típica de los intérpretes de órdenes; no las expresiones regulares del vi, el grep, el perl y otros) válida, con lo que se puede poner un * como última etiqueta del case para actuar como else. Para indicar que termina el código de una etiqueta determinada, hay que poner al final de la última orden dos signos de punto y coma seguidos.
La estructura general del case es:
case valor
in
expreg1)
...
ultimaorden1;;
expreg2)
...
ultimaorden2;;
...
expregn)
...
ultimaordenn;;
esac
Por ejemplo, si queremos comprobar una respuesta de sí o no (de forma un poco relajada), podemos hacer:
case $resp; in
s*)
echo "Has contestado sí (o algo parecido)"
n*)
echo "Has contestado no (o algo parecido)"
*)
echo "Has contestado alguna otra cosa"
esac
Estructura whileLa estructura básica para construir bucles. La sintaxis es:
while orden
do
...
done
En orden podemos poner una orden normal y corriente (el bucle se ejecutaría mientras la orden devolviera un cero), o podríamos también poner una condición, con la orden test (o mejor, con la sintaxis alternativa []), que por otra parte no deja de ser una orden como otra cualquiera.
Si quisiésemos estar seguros de que la contestación a la pregunta del ejemplo anterior era s o n, podríamos haber hecho lo siguiente:
while [ $resp != "s" -a $resp != "n" ]; do
read resp
done
Y justo debajo podríamos poner el case anterior, para comprobar cuál fue finalmente la respuesta.
Estructura forEs otra de las estructuras importantes en los lenguajes de programación. Es más versátil que el equivalente de Pascal, pero menos que el equivalente de C (como era lógico, por otra parte).
Es una estructura que permite dos sintaxis: La primera, la del for más o menos tradicional (en los lenguajes interpretados), es decir, dar el nombre de una variable y los valores por los que tiene que ``pasar'', es la siguiente:
for variable in expreg1 expreg2 ... expregn
do
...
done
Hay una pequeña diferencia respecto a los bucles normales for, y ésta es que los valores por los que pasa la variable variable tenemos que especificarlos uno a uno (mediante expresiones regulares). Las expresiones regulares se intentarán hacer coincidir con los nombres de los ficheros del directorio actual.
Por ejemplo, si quisiéramos coger todos los ficheros normales y hacerles una sustitución con el sed, por ejemplo, una opción sería utilizar un bucle for. Por ejemplo, yo utilizo de vez en cuando bucles así para pasar texto de MS-DOS a texto UNIX:
for fichero in *.txt; do
sed 's/^M//' $fichero >TEMPORAL
mv TEMPORAL $fichero
done
En este bucle, cogemos el sed y sustituimos los caracteres de retorno de carro por nada, o sea, los borramos. Esto convierte los ficheros de texto de MS-DOS a UNIX. Pero sed manda el resultado a la salida estándar, así que tenemos que dirigirlo a otro fichero (el TEMPORAL. Aquí suponemos que no existe ningún fichero llamado TEMPORAL en el directorio actual, o por lo menos que no es importante y lo podemos borrar). Una vez tenemos el resultado en el fichero TEMPORAL, simplemente lo movemos al fichero original, sobreescribiendo el fichero antiguo y poniendo la versión nueva, sin los caracteres de retorno de carro.
La segunda sintaxis que permite el bucle for es una sintaxis sin lista. Si utilizamos esta sintaxis, el intérprete lo entenderá como que la lista que queremos es la de todos los parámetros dados al guión desde la línea de órdenes (u otro guión o programa). La sintaxis queda:
for variable
do
...
done
Si tuviéramos un programa que sólo aceptara ficheros u opciones, podríamos discriminar entre unos y otras así:
for par; do
case $par; in
-*)
echo "Opción '$par' (empieza por '-')"
*)
echo "Fichero '$par' (cualquier otra cosa)"
esac
done
Estructura untilEsta estructura es parecida a la while, aunque la condición la damos ``al revés'', es decir, que el bucle se ejecuta mientras la condición a comprobar sea falsa, y termina cuando sea verdadera. Tiene una utilidad diferente que en los lenguajes de programación. Normalmente se utiliza en los lenguajes de alto nivel para forzar la ejecución del bucle al menos una vez, aunque la condición sea desde el principio verdadera. No se puede utilizar de esa manera en la programación en shell, porque la sintaxis tiene la condición al principio.
Su uso es sólo para mejorar la legibilidad, porque podemos negar incluso la salida de los programas que llamemos, anteponiendo a la llamada al programa el caracter de negación !.
La sintaxis es como sigue:
until orden
do
...
done
Ruptura de la ejecución normal de los buclesAlgunas veces necesitamos romper la ejecución normal de los bucles. Quizás queramos saltarnos lo que queda de iteración, o quizás necesitemos en un momento dado salir del bucle.
Las dos órdenes utilizadas en la programación en shell para hacer estas cosas son las dos mismas que se utilizan en lenguaje C, y se utilizan de la misma manera: break y continue.
La primera sirve para salir del bucle y seguir la ejecución del programa, y la segunda sirve para saltarnos lo que queda de iteración y empezar la siguiente. Antes de empezar con la siguiente iteración, se volverá a chequear la condición
(supongo).
MisceláneaTodo lo que se necesita para empezar a investigar y para despertar tu interés (si a estas alturas la programación en shell no ha despertado tu interés, dudo que lo haga) ya se ha visto. Lo único que queda son decir algunas cosas más, y decir que si crees que le falta algo de potencia, que te mires algún libro. Hay algunas cosas que yo no he querido poner en este documento, como por ejemplo atrapar las señales del sistema, una orden para manejar los parámetros estilo UNIX (es decir, con - al principio, y pudiendo agrupar -l -F en -lF), y otras cosas.
Variables útiles de entornoEn los programas muchas veces es útil consultar las variables de entorno. Por ejemplo, el usuario puede tener definido su editor preferido enla variable $EDITOR. Así, si necesitamos editar un texto, podemos llamar al editor que ponga la variable $EDITOR, en vez de coger el que a nosotros nos dé la gana (ya se sabe, la guerra santa vi-emacs, en la que por cierto soy defensor del vi, hace estragos). Algunas variables útiles de entorno son:
Variable Significado
$PS1 Aspecto del apuntador (prompt) principal
$PS2 Aspecto del apuntador (prompt) secundario
$PWD Directorio actual
$HOME Directorio home del usuario que ejecuta el proceso
$USER Nombre de login del usuario que ejecuta el proceso
$$ PID del proceso
$PATH Ruta de búsqueda del usuario actual
El apuntador, como probablemente sabes, es lo que sale a la izquierda de la pantalla cuando escribes cosas. En una terminal UNIX, probablemente sea `$' en Linux seguramente es algo más complicado.
A lo mejor lo que no sabes es qué diablos es el apuntador secundario. Cuando escribes comillas (de cualquier tipo) en la línea de órdenes, pero pulsas Enter antes de terminar de escribir lo que tenías entre comillas (es decir, si abres comillas pero no las cierras), el intérprete enseñará el apuntador secundario en la siguiente línea, y seguirá poniendo apuntadores secundarios en las líneas subsiguientes hasta que cierres las comillas.
El PID de un proceso es su process id. Es un número que sirve para identificar al proceso (cuando haces un kill y similares). Este número es evidentemente único para cada proceso, y lo podemos utilizar para crear ficheros en el directorio /tmp únicos para esa interpretación del guión. Piensa que si tuviéramos a dos usuarios diferentes ejecutando el mismo guión a la vez, uno de ellos machacaría algún hipotético fichero temporal que pudiera tener el primero, y se formaría un pollo que no veas.
Algo a tener en cuenta cuando estemos programando apoyándonos en variables de entorno, es que estas variables no se pueden modificar. Podemos asignarles valores y cambiarán, pero sólo en nuestro programa. Hay que tener en cuenta que $PWD, que es la variable que controla el directorio actual, no tiene nada que ver con el núcleo del sistema operativo, es un problema exclusivo del intérprete, que es al fin y al cabo la interfaz para que el usuario se comunique con el sistema operativo. Así, si escribimos un guión que cambie el directorio actual, aunque sea mediante la orden cd, al salir del guión del intérprete ni se habrá enterado.
Ejecución en el mismo inérprete (shell)El hecho de que nosotros modifiquemos una copia de las variables es por el hecho de que los guiones se ejecutan en otro intérprete diferente al actual, y entonces las variables de entorno simplemente se copian. Para que se copien, debemos exportar las variables. Todas las variables normales ya están exportadas en el fichero de configuración (ya sea en el general o en el de cada usuario), pero quizás algunas variables inventadas por nosotros o poco comunes queremos que sean exportadas también, para que cualquier otro programa pueda acceder a ellas para consultarlas.
La manera de forzar esto es con la orden export. La sintaxis es, simplemente, export variable. Por ejemplo, export EDITOR.
Pero nosotros hablábamos de ejecutar programas en el intérprete actual, entre otras cosas para poder modificar variables de entorno. La forma de hacer esto, es poner un punto (.) antes del nombre del programa. Por ejemplo, escribir . inicializacion.
Referirse a variablesCuando vimos la forma de referirse a los parámetros, salió el delicado tema de que sólo podíamos referirnos a los parámetros que estén en las primeras nueve posiciones. Esto es así en el Bourne Shell, y se debe actuar así si se quiere hacer un guión estándar que se ejecute en cualquier sitio. Pero en el Korn Shell (y creo que también en bash, aunque no lo he probado) se pueden escribir los nombres de variables con esta sintaxis: ${variable}. El Bourne Shell acepta esta construcción cuando son variables normales, pero no cuando lo utilizamos para indicar parámetros.
Así, el Korn Shell y compatibles, podríamos referirnos al parámetro 15 con la expresión ${15}. En cualquier inérprete compatible con el bourne, si quisiéramos escribir caracteres justo después del contenido de una variable, deberíamos escribir, por ejemplo:
${foo}bar
Porque si pusiéramos simplemente
$foobar
El intérprete creería que nos referimos a una variable no declarada llamada foobar, con lo que esa expresión sería una ristra vacía.
La orden evalHay una orden que algunas veces nos puede sacar de algunos apuros: es la orden eval. En realidad esta orden no sirve para nada, simplemente, al ser una orden (como parámetros tenemos que poner otra orden), fuerza que el intérprete pase dos veces por el mismo sitio, y que evalúe dos veces la expresión. Así, si quisiéramos que el usuario introdujera por teclado el nombre de una variable a modificar, podríamos resolver el problema así:
read nombre
eval $nombre=10
La primera vez que el intérprete lo mirara, la expresión de eval quedaría como `[contenido de la variable $variable]=10'. La segunda vez, ejecutaría la orden, como quedara, con lo que conseguiríamos asignar 10 a una variable cuyo nombre ha sido elegido por el usuario.
Espero q este pequeño Thread les sea util...
Hao