Esta página puede ser redistribuida libremente bajo los términos de la licencia GPL. Vease ( GPL texto original ) o si lo prefiere (Traducción española no oficial de la GPL) Al margen de las obligaciones legales que se derivan del uso de esta licencia rogamos sea respetada la referencia a su lugar de publicación original www.ciberdroide.com. y a su autor original Antonio Castro Snurmacher (Madrid 01/01/2000).

Ausencia de Garantía

Esta ausencia de garantía se hace extensa a cualquier tipo de uso de este material y muy especialmente a las prácticas, ejercicios, y de ejemplos que encuentre en estas páginas. Deberá trabajar siempre salvo indicación contraria con un SO Linux y con un usario distinto de 'root' sin privilegios especiales. Como directorio de trabajo se procurará usar el directorio '/tmp' o algún otro que no contenga información valiosa. Tampoco se considera buena idea practicar en una máquina que contenga información valiosa.

Todo esto son recomendaciones de prudencia. En cualquier caso si algo sale mal toda la responsabilidad será únicamente suya. En ningún caso podrá reclamar a nadie por daños y perjuicios derivados del uso de este material. Para más información lea el contenido de la licencia GPL y abstengase de hacer prácticas si no está dispuesto a asumir toda la responsabilidad.

PROGRAMACION SHELL-SCRIPT (Primera parte)

Funciones
Se pueden definir funciones que podrán ser llamadas por su nombre permitiendo el paso de parámetros de la misma forma que cuando se llama a una shell. También puede devolver códigos de retorno. Las variables alteradas dentro de una función tienen efecto visible fuera de la función ya que una función no se ejecuta invocando a una sub-shell. Una función se puede definir en un shell-script o en la propia linea de comandos. Si se hace esto último será como si añadiéramos un comando interno.

$ fff() { echo "---------------------------" ; }
$ fff

---------------------------

Si tecleamos 'set' después de esto veremos todas las variables definidas y también aparecerá esta función que acabamos de crear.

$ set

Ahora vamos a editar un shell-script que use una función. Edite un fichero que llamaremos 'pru_funcion1' que tenga lo siguiente.

# pru_funcion1
# Funcion que muestra dos parametros de entrada y modifica una variable
funcion1()
{
	echo '<'$1'>'
	echo '('$2')'
	var=1234
}
## main ##
funcion1 111 222 
echo $var

Ahora damos permisos de ejecución y ejecutamos simplemente mediante './pru_funcion1'.

$ ./pru_funcion1
<111>
(222)
33
1234

Edite y pruebe el siguiente código.

# Visualizar los parametros del 6 al 14
function()
{
	shift 5
	echo $1 $2 $3 $4 $5 $6 $7 $8 $9
}
## main ##
funcion 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

Para definir la sintaxis de una función en shell-script podríamos usar lo siguiente:

 _nombre_funcion_ () { _lista_de_ordenes_ } 
Una lista de ordenes sería una o más ordenes separadas por algún operador como por ejemplo ';', '||', '&&', '&' o . Recuerde que el shell-script tiene un significado similar a ';' y si desea suprimir este significado deberá preceder inmediatamente con '\' para escapar el carácter y que no sea interpretado por la shell. En una cadena delimitada por comillas simples o dobles no tiene este significado pero puede usarse '\' para evitar que el carácter quede insertado en la cadena.

$ echo \
> kkk\
> xxx

kkkxxx

Códigos de retorno
La palabra reservada 'return' interrumpe la ejecución de una función asignando un valor al código de retorno de esa función. Su uso no es necesario al final de la función. La palabra reservada 'exit' termina la ejecución de un shell-script liberando recursos y devolviendo un código de retorno el intérprete de comandos. 'exit 0' terminará retornando cero. Si solo ponemos 'exit' el código retornado será como si hiciéramos 'exit $?'. Es decir retorna el valor del último comando.

Ahora edite un fichero que llamaremos 'pru_funcion2' que tenga lo siguiente.

# pru_funcion2
# Funcion que devuelve un código de retorno. Y shell que devuelve otro
funcion2()
{
	var=1234
	return 33
	var=4567
}
## main ##
funcion2 
echo $?
echo $var
exit 888

Ejecutaremos este ejemplo

$./pru_funcion2

33
1234

Comandos true y false
Los comandos que finalizan correctamente suelen retornar 0. Los que finalizan con algún error suelen retornar un valor distinto de 0 y de esa forma distintos códigos de retorno pueden indicar distintas causas de error o no éxito.

En una expresión lógica donde lo que interesa es simplemente distinguir entre algo que se cumple o que no se cumple se tomará 0 como valor lógico TRUE (o sea cierto) y distinto de 0 se tomará como FALSE (falso).

'true' y 'false' son comandos que no hacen nada. Simplemente retornan el valor lógico TRUE o FALSE respectivamente. El valor de $? siempre devuelve el último código de retorno obtenido por la shell después de ejecutar el último comando.

Ejemplos:

$ true ; echo $?

0
$ false ; echo $?

1

Esperamos que ejemplos tan complicados como estos dos últimos no le desanimen para continuar con el curso.

Comando test
Como ya dijimos el lenguaje shell-script utiliza muy frecuentemente comandos externos del sistema operativo. Queremos explicar el comando 'test' antes de explicar las sentencias condicionales de bash porque de esta forma podremos usar ejemplos más completos.

'test' es un comando externo que devolverá un código de retorno 0 o 1 dependiendo del resultado de la expresión que siga a continuación.

Se usa muchísimo y tiene dos formatos. 'test <expr>' o '[ <expr> ]'. ambas formas son equivalentes.

Las expresiones de este tipo que solo pueden valer TRUE o FALSE se denominan expresiones lógicas.

Comprobación sobre ficheros
-r <fichero> TRUE si el fichero existe y es legible.
-w <fichero> TRUE si el fichero existe y se puede escribir.
-x <fichero> TRUE si el fichero existe y es ejecutable.
-f <fichero> TRUE si el fichero existe y es de tipo regular (normal).
-d <fichero> TRUE si existe y es un directorio.
-c <fichero> TRUE si existe y es un dispositivo especial de caracteres.
-b <fichero> TRUE si existe y es un dispositivo especial de bloques.
-p <fichero> TRUE si existe y es un pipe (fifo).
-u <fichero> TRUE si existe y tiene el bit set-user-ID.
-s <fichero> TRUE si existe y tiene tamaño mayor que 0.
Comparación de cadenas
-z <cadena> TRUE si es una cadena vacía.
-n <cadena> TRUE si es una cadena no vacía.
<cadena1> = <cadena2> TRUE si ambas cadenas son idénticas. OJO hay dejar espacios
a un lado y otro del signo igual para no confundir la orden
con una asignación.
<cadena1> != <cadena2> TRUE si son distintas.
<cadena> TRUE si no es cadena nula. (Una variable que no existe
devuelve cadena nula).
Comparación de números
<num1> -eq <num2> TRUE si ambos números son iguales.
<num1> -ne <num2> TRUE si ambos números son distintos.
<num1> -gt <num2> TRUE si <num1> mayor que <num2>.
<num1> -ge <num2> TRUE si <num1> mayor o igual que <num2>.
<num1> -lt <num2> TRUE si <num1> menor que <num2>.
<num1> -le <num2> TRUE si <num1> menor o igual que <num2>.
Operadores lógicos
! <expresión_logica> Operador lógico NOT retorna TRUE si <expresión_logica> es
FALSE y retorna FALSE si <expresión_logica> es TRUE.
<ExprLogi1> -a <ExprLogi2> Operador lógico AND retorna TRUE si <ExprLogi1> y
<ExprLogi2> son ambas TRUE y FALSE en caso contrario.
<ExprLogi1> -o <ExprLogi2> Operador lógico OR retorna TRUE si <ExprLogi1> o
<ExprLogi2> son alguna de ellas TRUE y FALSE en caso contrario.
Agrupación de expresiones
( <expr> ) Se pueden usar paréntesis para agrupar expresiones lógicas.
Es decir que valgan TRUE o FALSE

Ejemplos:

$ test -r /etc/passwd ; echo $?

0
$ test -w /etc/passwd ; echo $?
 
1
$ test -x /etc/passwd ; echo $?
 
1
$ test -c /dev/null  ; echo $?

0
$ test -r /etc/passwd -a -c /dev/null ; echo $?
 
0
$ test -w /etc/passwd -a -c /dev/null ; echo $?
 
1
$ test -r /etc/passwd -a -f /dev/null ; echo $?
 
1
$ [ -s /dev/null ] ; echo $?

1
$ [ ! -s /dev/null ] ; echo $?

0
$ [ "$$" = "zzzzzzzzzzzz" ] ; echo $?

1
$ [ 0 -lt $$  ] ; echo $?

0
$ [ 0 -lt $$  -a  true ] ; echo $?

Comando expr
Este comando admite como parámetros los elementos de una expresión aritmética pero hay que recordar que algunos operadores deben ser escapados con '\'. Por ejemplo .

$ expr 11 \* 2 

A diferencia de 'bc' no podemos usar valores demasiado altos porque el resultado no sería correcto. Recuerde que 'bc' trabaja con precisión arbitraria.

El comando 'expr' es útil pero no deja de ser un comando externo y por lo tanto su uso resulta ineficiente. Afortunadamente la propia shell tiene capacidad de evaluar expresiones aritméticas.

Expresiones aritméticas dentro de Bash
La shell por defecto asume que todas las variables son de tipo cadena de caracteres. Para definir una variable de tipo numérico se usa 'typeset -i'. Esto añade un atributo a esa variable para que la shell realice una expansión distinta sobre esta variable. Para evaluar una expresión aritmética asignándola a una variable usaremos 'let'.

- + Menos unário y Más unário
* / % Multiplicación, División, y Resto
+ - Suma, y Resta
<= >= < >
== !=
Comparaciones Menor o igual,
Mayor o igual, Menor, Mayor
Igualdad, desigualdad
= *= /= %= += -= Asignación simple,
Asignación con operador
(multiplicación, división, resto, suma, y resta)
Ejemplos:

$ typeset -i j=7
$ typeset -i k
$ typeset -i m
$ echo $j

7
$ let j=j+3
$ echo $j
10
$ let j+=3
$ echo $j
13
$ let k=j%3
$ let m=j/3
$ echo '(' $m '* 3 ) +' $k '=' $j
+AMA
( 4 * 3 ) + 1 = 13

Puede que el operador '%' no le resulte familiar. Es el operador resto también se llama módulo y es lo que sobra después de la división. Lo cierto es que es preferible continuar explicando otras cosas y más adelante tendremos oportunidad de poner algunos ejemplos con expresiones aritméticas.

Hay una serie de operadores que trabajan a nivel de bits pero no vamos a explicarlos de momento.

Operadores '&&' y '||'
Son operadores AND y OR a nivel de shell y poseen circuito de evaluación corto. No tienen que ver con los operadores -a y -o ya que estos eran interpretados por el comando 'test'. Estos operadores '&&' y '||' serán colocados separando comandos que lógicamente retornan siempre algún valor.

'<comando1> && <comando2> && <comando3>' significa que debe cumplirse <comando1> y <comando2> y <comando3> para que se cumpla la expresión y '<comando1> || <comando2> || <comando3>' significa que debe cumplirse <comando1> o <comando2> o <comando3> para que se cumpla la expresión.

Si el resultado de '<comando1> && <comando2> && <comando3>' fuera 0 (TRUE) significaría que los tres comandos han retornado 0 (TRUE). Si por el contrario el resultado hubiera sido distinto (FALSE) solo sabríamos que por lo menos uno de los comandos retornó FALSE. Supongamos que el primer comando retornó TRUE. La shell deberá continuar ejecutando el segundo comando. Supongamos que el segundo comando retorna FALSE. En este momento la shell no continua ejecutando el tercer comando porque da igual lo que retorne el resultado será FALSE. La razón es que se necesitaban todos TRUE para un resultado TRUE. El hecho de no ejecutar las partes siguientes una vez se sabe el resultado es lo que se llama circuito de evaluación corto y su uso principal es el de ahorrar ejecuciones de ordenes innecesarias aunque se puede usar tambien para ejecutar cosas solo bajo ciertas condiciones.

Con el operador '||' (OR) pasa algo similar. Si el resultado de '<comando1> || <comando2> || <comando3>' fuera 1 (FASE) significaría que los tres comandos han retornado 1 (FALSE). Si por el contrario el resultado hubiera sido distinto 0 (TRUE) solo sabríamos que por lo menos uno de los comandos retornó TRUE. Supongamos que el primer comando retornó FALSE. La shell deberá continuar ejecutando el segundo comando. Supongamos que el segundo comando retorna TRUE. En este momento la shell no continua ejecutando el tercer comando porque da igual lo que retorne el resultado será TRUE. Se necesitaban todos FALSE para un resultado FALSE.

Un comando normal como por ejemplo 'grep' o 'echo' que termina bien devuelve TRUE. Si hubiéramos redirigido la salida de 'grep'o de 'echo' a un fichero sin permiso de escritura o a un fichero con un path inexistente el retorno de 'grep' o de 'echo' habría sido FALSE indicando la existencia de error. En otras ocasiones FALSE no indicará error sino que el comando no tuvo éxito. Esto último sería aplicable a 'grep' (patrón localizado o no localizado) pero no sería aplicable a un comando como 'echo'.

Veamos un ejemplo con 'grep'.

$ echo hola | grep hola ; echo $?

hola
0
$ echo hola | grep hola > /hh/hh/hh ; echo $?

bash: /hh/hh/hh: No existe el fichero o el directorio
1
$ echo xxx | grep hola ; echo $?

1

Cuando ejecutamos comandos con un pipe ('|') el código de retorno es el del último comando ejecutado. En nuestro caso el primer código 1 se debe a la imposibilidad de generar la salida. El segundo código 1 se debe a que la cadena no fue encontrada. En los tres casos $? recoge el código retornado por 'grep'.

Ahora vamos a probar ejemplos que ilustren el circuito de evaluación corto. Primero para OR

$ # Como un OR queda evaluado cuando se encuentra el primer resultado TRUE
$ # solo se evaluaran los comandos hasta obtener el primero que devuelva 
$ # 0 (TRUE)
$ echo "hola" || echo "adiós"

hola
$ # El resultado sera 0
$ echo $?

0

Ahora para AND.

$ # Como un AND no queda hasta que se evalúa el primer resultado FALSE
$ # se evaluaran los dos comandos y devolverá igualmente TRUE.
$ echo "hola" && echo "adiós"

hola
adiós
$ # El resultado sera 0 
$ echo $?

0

El circuito de evaluación corto ha de usarse correctamente. Si sabemos que un comando dará casi siempre un resultado que puede hacer innecesario la ejecución de otros comandos lo colocaremos en primer lugar. Los comandos lentos se colocan en último lugar porque de esa forma puede no ser necesario ejecutarlos. Algunas veces no nos interesa un resultado sino solo que se ejecute una orden dependiendo del resultado de otra orden. Es decir queremos ejecutar solo si se cumple una condición.

$ test ! -w fichero && echo "Cuidado fichero sin permiso de escritura"
$ test  -w fichero || echo "Cuidado fichero sin permiso de escritura"

Test
Puede comprobar sus conocimientos respondiendo el siguiente test.
Para ello seleccione las opciones que se ajusten a la verdad y luego pulse el boton para ver el resultado de su test.

1 ALGO="XXXXX" ; test \$ALGO # Esto retorna TRUE
2 test "444" != "555" # Esto retorna TRUE
3 test 1 > 4 # Esto retorna TRUE
4 typeset -i j=7 ; let j-=2 ; test $j -gt 5 # Esto retorna TRUE
5 test -r /dev/null || test -x /dev/null || test -s /dev/null # Esto retorna TRUE
6 test -w /dev/null && test -x /dev/null && test -s /dev/null # Esto retorna TRUE
7 'exit' es un comando externo que termina la ejecución de un comando.

Resultado del test

Si quiere hacernos llegar alguna duda, aclaración,
crítica, o contribución personal, utilice nuestro
formulario de contacto y nosotros le contestaremos
contacto