Ir a contenido principal

Bienvenido a My developerworks. Si no tiene un ID de IBM y un password, regístrese aquí.

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. Este perfil incluye el nombre, apellido y nombre de usuario que poporcinó cuando se registró en developerWorks. Cierta información de su perfil será mostrada públicamente, pero usted puede editar la información en cualquier momento. Su nombre, apellido (a menos que usted elija ocultarlo), y nombre de usuario acompañarán el contenido que usted publica.

Toda la información enviada es segura.

La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

Toda la información enviada es segura.

Preparación para el examen 102 de LPI, Tema 109: Shells, scripting, programación y compilación

Tema 109 del examen de Administración Nivel Junior (LPIC-1)

Ian Shields, Senior Programmer, IBM
Ian Shields
Ian Shields trabaja en múltiples proyectos Linux para la zona Linux dev developerWorks. Es Senior Programmer de IBM en el Research Triangle Park (RTP), Carolina del Norte. Ingresó a IBM en Canberra, Australia, como Systems Engineer en 1973, y desde entonces se dedica a sistemas de comunicaciones y computación ubicua en Montreal, Canadá, y en el RTP de Carolina del Norte. Es propietario de numerosas patentes y publicó diversos trabajos. Tiene una diplomatura en Matemática Pura y Filosofía de la Universidad Nacional de Australia. Es Máster y Doctor en Ciencias de la Computación de la Universidad Estatal de Carolina del Norte.

Resumen:  En este tutorial, Ian Shields continúa preparándolo para rendir el® Examen 102 de la Administración Nivel Junior de Linux Professional Institute (LPIC-1). En éste, el quinto de una serie de nueve tutoriales, Ian lo introduce en el mundo del shell Bash, y le presenta los scripts y la programación del shell Bash. Al finalizar este tutorial, usted sabrá cómo personalizar el entorno de su shell, cómo usar estructuras de programación de shell para crear funciones y scripts, cómo activar y desactivar variables de entorno, y cómo usar los diversos scripts de inicio de sesión.

Ver más contenido de esta serie

Fecha:  23-11-2009
Nivel:  Intermediaria

Comentario:  

Scripts del shell

Esta sección se ocupa del material para el tema 1.109.2 del examen 102 de la Administración Nivel Junior (LPIC-1). El tema tiene un valor de 3.

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 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
[ian@pinguino ~]$ [ "abc" \> "abc" ];echo $?
1
					

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
OperadorCaracterística
-dDirectorio
-eExiste (también -a)
-fArchivo regular
-hVínculo simbólico (también -L)
-pCanalización nombrada
-rLegible por usted
-sNo vacío
-SSocket
-wEditable por usted
-NHa 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
OperadorVerdadero si
-ntVerifique si el archivo 1 es más reciente que el archivo 2. Para ello, se utiliza la fecha de modificación, así como la siguiente comparación.
-otVerifique si el archivo 1 es más antiguo que el archivo 2.
-efVerifique 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 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.


Listado 35. Inclusión de pruebas aritméticas con [[
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable
					


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)
-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 declare a 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 <. No obstante, usted cuenta con una pequeña calculadora a mano para evaluar la aritmética a medida que el shell la realiza.

Posiblemente haya observado la cláusula else y los dos últimos ejemplos. Como puede ver, no es erróneo pasar xyz a mycalc, pero la evaluación que realiza arroja 0. Esta función no es lo suficientemente inteligente para identificar los valores del carácter en el ejemplo de uso final y, por lo tanto, para poder alertar al usuario. Usted podría usar una prueba de correspondencia con el patrón de cadenas como por ejemplo
[[ ! ("$*" == *[a-zA-Z]* ]]
(o la forma adecuada en su localidad) con el fin de eliminar cualquier expresión que contenga caracteres alfabéticos, pero esto evitaría el uso de notación hexadecimal en sus datos de entrada, debido a que usted podría usar 0x0f para representar 15 con una notación hexadecimal. De hecho, el shell permite bases hasta 64 (con notación base # value), por lo cual usted legítimamente podría usar cualquier carácter alfabético, más _ y @ en sus datos de entrada. Las notaciones octal y hexadecimal usan la notación usual de un 0 inicial para la octal y de un 0x o 0X inicial para la hexadecimal. Estos ejemplos se muestran en el Listado 37.


Listado 37. Cálculo con diferentes bases
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574
					

La purificación adicional de los datos de entrada se encuentra fuera del alcance de este tutorial, de manera que usted deberá usar la calculadora con el cuidado adecuado.

El enunciado elif es en verdad conveniente. Lo ayudará con la escritura de sus scripts al permitirle simplificar la indentación. Es posible que se sorprenda al ver los datos de salida del comando type para la función mycalc como se muestra en el Listado 38.


Listado 38. Type mycalc
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
{
    local x;
    if [ $# -lt 1 ]; then
        echo "This function evaluates arithmetic for you if you give it some";
    else
        if (( $* )); then
            let x="$*";
            echo "$* = $x";
        else
            echo "$* = 0 or is not an arithmetic expression";
        fi;
    fi
}
					

Enunciados con mayúsculas y minúsculas

El comando compuesto case simplifica las pruebas cuando usted cuenta con una lista de posibilidades y desea actuar de acuerdo a si un valor corresponde a una posibilidad en particular. El comando compuesto case se inicia con case WORD in y termina con esac ("case" deletreado de manera inversa). Cada caso de mayúscula o minúscula consiste en un patrón único, o en múltiples patrones separados por |, seguidos por ), una lista de enunciados, y finalmente un par de signos punto y coma. (;;).

A modo de ejemplo, imagine un comercio que sirve café, café descafeinado (decaf), té o gaseosas. Se puede usar la función del Listado 39 para determinar la respuesta a una orden.


Listado 39. Uso de comandos con mayúsculas y minúsculas
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    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"
        ;;
    esac
}
[ian@pinguino ~]$ myorder decaf
Hot coffee coming right up
[ian@pinguino ~]$ myorder tea
Hot tea on its way
[ian@pinguino ~]$ myorder milk
Sorry, we don't serve that here
					

Observe el uso de '*' para encontrar la correspondencia entre cualquier cosa que no hubiera hecho coincidir anteriormente.

Bash posee otra construcción similar a case que se puede usar para imprimir los datos de salida a una terminal y hacer que un usuario seleccione los artículos. Es el comando select, el cual no se tratará en este documento. Consulte las páginas man sobre bash, o escriba help select para aprender más sobre este tema.

Por supuesto, existen muchos problemas con un enfoque tan simple para resolver el problema; usted no puede pedir dos bebidas al mismo tiempo, y la función no maneja nada que no sean datos de entrada en minúsculas. ¿Puede hacer usted una correspondencia que no distinga entre mayúsculas y minúsculas? La respuesta es "si". Veamos cómo se hace.


Valores de devolución

El shell Bash cuenta con un builtin shopt que le permite activar o desactivar numerosas opciones del shell. Una de ellas es nocasematch, que, si está activado, indica al shell que ignore la diferencia entre mayúsculas y minúsculas al realizar la correspondencia entre cadenas. Es probable que lo primero que usted piense sea en usar el operando -o que aprendió con el comando test. Desafortunadamente, nocasematch no es una de las opciones que usted puede verificar con -o, de manera que deberá recurrir a otra cosa.

El comando shopt, al igual que la mayoría de los comandos de UNIX y Linux, determina un valor de devolución que usted puede examinar usando $?. Las pruebas que usted aprendió a realizar anteriormente no son elementos son valores de devolución. Si usted analiza las pruebas que realiza en un enunciado if, notará que en verdad verifican el valor de devolución del comando de verificación subyacente para obtener un resultado Verdadero (0) o Falso (1 o cualquier valor superior a 0). Esto funciona incluso si usted no aplica una prueba, pero usa algún otro comando. El éxito se indica con un valor de devolución de 0, y la falla se indica con un valor de devolución distinto de 0.

Con estos conocimientos, ahora usted puede verificar la opción nocasematch, configurarla si aún no lo está, y luego volverla a la forma preferida por el usuario cuando termina la función. El comando shopt cuenta con cuatro opciones convenientes, -pqsu para imprimir el valor actual, no imprimir nada, establecer la opción y desactivar la opción. Las opciones -p y -q establecen un valor de devolución de 0 que indica que la opción del shell está configurada, y de 1 que indica que está desactivada. La opción -p imprime el comando requerido para fijar la opción en su valor actual, mientras que la opción -q o simplemente fija un valor de devolución de 0 o 1.

La función que usted ha modificado usará el valor de devolución desde shopt para fijar una variable local que represente el estado actual de la opción nocasematch, establecer la opción, ejecutar el comando de mayúscula-minúscula, y luego restablecer la opción. nocasematch a su valor original. El Listado 40 muestra una manera de hacerlo.


Listado 40. Verificación de los valores de devolución de los comandos
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    local restorecase;
    if shopt -q nocasematch; then
        restorecase="-s";
    else
        restorecase="-u";
        shopt -s nocasematch;
    fi;
    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"
        ;;
    esac;
    shopt $restorecase nocasematch
}
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
[ian@pinguino ~]$ # nocasematch is currently unset
[ian@pinguino ~]$ myorder DECAF
Hot coffee coming right up
[ian@pinguino ~]$ myorder Soda
Your ice-cold soda will be ready in a moment
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
[ian@pinguino ~]$ # nocasematch is unset again after running the myorder function
					

Si usted quiere que su función (o script) devuelva un valor que pueda ser verificado por otras funciones o comandos, use el enunciado return de su función. El Listado 41 le muestra cómo lograr un valor de devolución 0 para una bebida que usted puede servir y uno de 1 si el cliente pide otra cosa.


Listado 41. Configuración de sus propios valores de devolución a partir de las funciones
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    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
}
[ian@pinguino ~]$ myorder coffee;echo $?
Hot coffee coming right up
0
[ian@pinguino ~]$ myorder milk;echo $?
Sorry, we don't serve that here
1
					

Si usted no especifica su propio valor de devolución, el valor de devolución será el del último comando ejecutado. Las funciones suelen ser reutilizadas en situaciones que usted nunca previó, de manera que establecer su propio valor será una buena práctica.

Los comandos pueden devolver valores que no sean 0 y 1, y en ocasiones será conveniente distinguirlos. Por ejemplo, el comando grep devuelve 0 si el patrón tiene correspondencia, y 1 si no la tiene, pero también devuelve 2 si el patrón es inválido o la especificación del archivo no se corresponde con ningún archivo. Si usted debe distinguir además otros valores de devolución que no sean simplemente éxito (0) o falla (valores distintos a 0), probablemente deberá usar un comando case o quizás un comando if con muchas partes elif.


Sustitución de comandos

Usted conoció la sustitución de comandos en el tutorial " Preparación para el examen 101 de LPI (tema 103): Comandos GNU y UNIX ", pero hagamos una breve revisión.

La sustitución de comandos le permite usar los datos de salida de un comando como datos de entrada para otro comando simplemente si encierra el comando ente $( y ) o con un par de acentos invertidos - `. La forma $() presenta algunas ventajas si desea anidar los datos de salida de un comando como parte del comando que generará los datos de salida finales. Por otro lado, será más fácil descubrir qué es lo que sucede realmente ya que el paréntesis tiene una forma derecha y otra izquierda, contrariamente al acento invertido. Sin embargo, la opción es suya, y los acentos invertidos son una opción muy común.

Con frecuencia usará la sustitución de comandos con bucles (que se tratan más adelante en Bucles). No obstante, también la puede usar para simplificar la función myorder que acaba de crear. Debido a que shopt -p nocasematch en realidad imprime el comando que usted necesita para establecer la opción nocasematch en su valor actual, sólo deberá guardar esos datos de salida y luego ejecutarlos al finalizar el enunciado case. Con esto se restaurará la opción nocasematch independientemente de si usted la modificó o no. Su función revisada será similar al Listado 42. Pruébela usted mismo.


Listado 42. Sustitución de comandos en lugar de pruebas de valor de devolución
[ian@pinguino ~]$ type myorder
myorder is a function
myorder ()
{
    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
}
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
[ian@pinguino ~]$ myorder DECAF
Hot coffee coming right up
[ian@pinguino ~]$ myorder TeA
Hot tea on its way
[ian@pinguino ~]$ shopt -p nocasematch
shopt -u nocasematch
					


Depuración

Si usted ha escrito funciones por sí mismo y ha cometido errores de tipeo que lo dejaron pensando en qué estaba mal, es probable que también se pregunte cómo se depuran las funciones. Afortunadamente el shell le permite establecer la opción -x para rastrear los comandos y sus argumentos a medida que el shell los ejecuta. El Listado 43 muestra cómo funciona para la función myorder del Listado 42.


Listado 43. Rastreo de la ejecución
[ian@pinguino ~]$ set -x
++ echo -ne '\033]0;ian@pinguino:~'

[ian@pinguino ~]$ myorder tea
+ myorder tea
++ shopt -p nocasematch
+ local 'restorecase=shopt -u nocasematch' rc=0
+ shopt -s nocasematch
+ case "$*" in
+ echo 'Hot tea on its way'
Hot tea on its way
+ shopt -u nocasematch
+ return 0
++ echo -ne '\033]0;ian@pinguino:~'

[ian@pinguino ~]$ set +x
+ set +x
					

Usted puede aplicar esta técnica para sus alias, funciones o scripts. Si necesita más información, agregue la opción -v para obtener datos de salida detallados.


Bucles

El Bash y otros shells cuentan con tres construcciones de bucle que son de alguna manera similares a las del lenguaje C. Cada una ejecutará una lista de comandos entre cero y varias veces. La lista de comandos se encuentra rodeada por las palabras do y done, cada uno de ellos precedida por punto y coma.

for
vienen en dos sabores. La forma más común de scripting de los shells itera sobre un conjunto de valores, ejecutando la lista de comandos una vez para cada valor. El conjunto puede estar vacío, en cuyo caso no se ejecuta la lista de comandos. La otra manera es más un lenguaje C tradicional para bucles, que usa tres expresiones aritméticas para controlar una condición inicial, una función de escalón y una condición final.
while
evalúan una condición cada vez que el bucle seinicia y ejecuta la lista de comandos si la condición es verdadera. Si la condición no es inicialmente verdadera, los comandos nunca se ejecutan.
until
ejecutan la lista de comandos y evalúan una condición cada vez que el bucle finaliza. Si la condición es verdadera, el bucle vuelve a ejecutarse. Incluso si la condición no es inicialmente verdadera, los comandos se ejecutan por lo menos una vez.

Si las condiciones que se verifican corresponden a una lista de comandos, entonces se usará el valor de devolución del último ejecutado. El Listado 44 muestra los comandos de bucle.


Listado 44. Bucles for, while, y until
[ian@pinguino ~]$ for x in abd 2 "my stuff"; do echo $x; done
abd
2
my stuff
[ian@pinguino ~]$ for (( x=2; x<5; x++ )); do echo $x; done
2
3
4
[ian@pinguino ~]$ let x=3; while [ $x -ge 0 ] ; do echo $x ;let x--;done
3
2
1
0
[ian@pinguino ~]$ let x=3; until echo -e "x=\c"; (( x-- == 0 )) ; do echo $x ; done
x=2
x=1
x=0
					

Estos ejemplos son en cierta medida artificiales, pero sirven para ilustrar los conceptos. La mayoría de las veces, será conveniente iterar sobre los parámetros a una función o script del shell, o a una lista creada por sustitución de comandos. Anteriormente, usted descubrió que los shells pueden referirse a la lista de parámetros pasados como $* o $@ y que el hecho de que usted haya encerrado estas expresiones entre comillas o no afectó la manera en que las mismas fueron interpretadas. El Listado 45 muestra una función que imprime la cantidad de parámetros y luego imprime los parámetros de acuerdo con las cuatro alternativas. El Listado 46 muestra la función en acción, con un carácter adicional agregado delante de la variable IFS para la ejecución de la función.


Listado 45. Función para imprimir información sobre los parámetros
[ian@pinguino ~]$ type testfunc
testfunc is a function
testfunc ()
{
    echo "$# parameters";
    echo Using '$*';
    for p in $*;
    do
        echo "[$p]";
    done;
    echo Using '"$*"';
    for p in "$*";
    do
        echo "[$p]";
    done;
    echo Using '$@';
    for p in $@;
    do
        echo "[$p]";
    done;
    echo Using '"$@"';
    for p in "$@";
    do
        echo "[$p]";
    done
}
					


Listado 46. Impresión de la información de los parámetros con testfunc
[ian@pinguino ~]$ IFS="|${IFS}" testfunc abc "a bc" "1 2
> 3"
3 parameters
Using $*
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$*"
[abc|a bc|1 2
3]
Using $@
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$@"
[abc]
[a bc]
[1 2
3]
					

Analice cuidadosamente las diferencias, en especial para las formas entre comillas y los parámetros que incluyen espacios en blanco como espacios o caracteres de nuevas líneas.

Break y continue

El comando break le permite salir de un bucle de manera inmediata. Como alternativa, usted puede especificar una cantidad de niveles de los cuales salir si posee bucles anidados. De manera que si usted tenía un bucle until dentro de un bucle for dentro de un bucle for y todos ellos dentro de un bucle while, entonces break 3 inmediatamente finalizaría el bucle until y ambos bucles for, para devolver el control al código que se encuentra dentro del bucle while.

El enunciado continue le permite bordear los enunciados restantes en la lista de comandos y pasar inmediatamente a la siguiente iteración del loop.


Listado 47. Uso de break y continue
[ian@pinguino ~]$ for word in red blue green yellow violet; do
> 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.


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

En el tutorial anterior, Preparación para el examen 101 de LPI: Dispositivos, sistemas de archivos Linux, y el Estándar de Jerarquía del Sistema de Archivos, usted aprendió a cambiar el dueño y el grupo de un archivo y a configurar los permisos suid y sgid. 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.


Figura 2. Programas con permiso suid
Programs with suid permission

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, pero consulte el tutorial anterior, Preparación para el examen 101 de LPI: Dispositivos, sistemas de archivos Linux, y el Estándar de Jerarquía del Sistema de Archivos, si debe restringir el script para que solamente los miembros de un cierto grupo puedan ejecutarlo.

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.

Esto nos trae al final de este tutorial. Nos hemos ocupado de una gran cantidad de material sobre shells y scripting. No se olvide de calificar este tutorial y darnos su opinión.

3 de 5 | Anterior | Siguiente

Comentario



static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Linux
ArticleID=449222
TutorialTitle=Preparación para el examen 102 de LPI, Tema 109: Shells, scripting, programación y compilación
publish-date=11232009
author1-email=ishields@us.ibm.com
author1-email-cc=

Etiquétalo Etiquetas

Help
Utilice el campo de búsqueda para encontrar todo tipo de contenido en My developerWorks con esa etiqueta.

Utilice el deslizador para controlar cuántas etiquetas deben mostrarse.

Las etiquetas populares muestran las etiquetas más difundidas en esta zona particular de contenido (por ejemplo: Java, Linux, WebSphere).

Mis Etiquetas muestra sus etiquetas en esta zona particular de contenido (por ejemplo: Java, Linux, WebSphere).

Utilice el campo de búsqueda para encontrar todo tipo de contenido en My developerWorks con esa etiqueta. Las etiquetas populares muestran las etiquetas más difundidas en esta zona particular de contenido (por ejemplo: Java, Linux, WebSphere). Mis Etiquetas muestra sus etiquetas en esta zona particular de contenido (por ejemplo: Java, Linux, WebSphere).