Programación del Shell Linux

Aquí tenemos un libro libre y completo sobre Shell

La sed del "conocimiento libre" es muy bienvenida.

Comandos Shell Script

Diagrama Sintáctico
Júlio Neves
Júlio Neves
Home | Artículos Português InglesAgradecimientos | Links Amigos

Comprar el libro

Changelogs

  Conversación de Bar 1  

  Conversación de Bar 2  

  Conversación de Bar 3  

  Conversación de Bar 4  

  Conversación de Bar 5  

  Conversación de Bar 6  

  Conversación de Bar 7  

  Conversación de Bar 8  

  Conversación de Bar 9  

  Conversación de Bar 10  

  Conversación de Bar 11  

  Aperitivo  

Conversación de Bar - Parte IV


- Y entonces amigo mio, intentaste hacer el ejercicio que te pedí para reforzar tus conocimientos?

- Claro, que si! En programación, si no se practica, no se aprende. Me pediste que hiciera un scriptisiño para informar si un determinado usuario, que será pasado como parámetro, esta logado (ajjjj!) o no. Hice lo siguiente:

$ cat logado #!/bin/bash # Busca si una persona está logada o no if who | grep $1 then echo $1 está logado else echo $1 no se encuentra en la vecindad fi

- Calma amigo! Ya vi que hoy llegaste lleno de deseos de trabajar, primero vamos a pedir nuestros "choppes" de costumbre y después vamos al Shell. Chico!, tráeme dos "choppes", uno sin espuma!

- Ahora que ya mojamos nuestros labios, vamos a echar un vistazo a la ejecución de tu programa:

$ logado jneves jneves pts/0 Oct 18 12:02 (10.2.4.144) jneves está logado

Realmente funcionó. Pasé mi login como parámetro y él me informó que estaba logado, sin embargo, al mismo tiempo salió una línea que no pedí. Esta línea es la salida del comando who, y para evitar que eso pase, lo único que hay que hacer es mandarla hacia el agujero negro que a estas altura ya sabes que es el /dev/null. Veamos entonces como quedaría:

$ cat logado #!/bin/bash # Busca si una persona está logada o no (versión 2) if who | grep $1 > /dev/null then echo $1 está logado else echo $1 no se encuentra en la vecindad fi

Ahora vamos a los tests:

$ logado jneves jneves está logado $ logado chico chico no se encuentra en la vecindad
Pinguim com placa de atenção (em espanhol) Ah, ahora si! Acuérdate de esto: la mayor parte de los comandos tienen una salida patrón y una salida de errores (el grep es una de las pocas excepciones, ya que no da mensajes de error cuando no encuentra una cadena) y es necesario estar muy atentos para redirecionarlas hacia el agujero negro cuando sea necesario.

Bueno, ahora vamos a cambiar de asunto: la última vez que nos encontramos aquí en el Bar, te estaba mostrando los comandos condicionales y cuando ya estábamos con la garganta seca hablando sobre el if, me preguntaste como se verifican condiciones. Veamos entonces

El Comando test

Bien, todos estamos acostumbrados a usar el if para verificar condiciones, y estas condiciones siempre son: mayor, menor, mayor o igual, menor o igual, igual y diferente. En Shell para verificar condiciones, usamos el comando test, sólo que este es mucho más poderoso de lo que estamos habituados. Primero te voy a mostrar las principales opciones (existen muchas otras), para verificar la existencia de archivos en el disco:

Opciones del Comando test para archivos
  -x arch     arch existe y con derechos de ejecución  
  Opción     Verdadero si:  
  -e arch     arch existe  
  -s arch     arch existe y tiene tamaño mayor que cero  
  -f arch     arch existe y es un archivo regular  
  -d arch     arch existe y es un directorio;  
  -r arch     arch existe y con derechos de lectura  
  -w arch     arch existe y con derechos de escritura  

Observa ahora las principales opciones para verificar cadenas de caracteres:

Opciones del comando test para cadenas de caracteres
  c1 = c2    Cadena c1 y c2 son idénticas 
  Opción     Verdadero si:  
  -z cadena    Tamaño de cadena es cero 
  -n cadena    Tamaño de cadena es mayor que cero 
  cadena    La cadena cadena tiene tamaño mayor que cero 

Y crees que se acabó ahí? Pues estás engañado! Ahora viene la parte a la que estás más acostumbrado, o sea las famosas comparaciones con números. Fijate en la tabla que sigue:

Opciones del comando test para números
  n1 -le n2     n1 es menor o igual a n2     less or equal  
  Opción     Verdadero si:     Significado  
  n1 -eq n2     n1 y n2 son iguales     equal  
  n1 -ne n2     n1 y n2 no son iguales     not equal  
  n1 -gt n2     n1 es mayor que n2     greater than  
  n1 -ge n2     n1 es mayor o igual a n2     greater or equal  
  n1 -lt n2     n1 es menor que n2     less than  

Además de todo eso, se suman a las opciones que te mostré las siguientes opciones:

Operadores
  -o     O lógico  
  Operador     Finalidad  
  Paréntesis ( )     Agrupar  
  Admiración !     Negar  
  -a     Y lógico  

Ufa! Como viste hay mucha cosa y como te dije al comienzo, nuestro if es mucho más poderoso que los de otros. Vamos a ver en unos ejemplos como funciona todo esto, primero verificaremos la existencia de un directorio:

Ejemplos:

    if  test -d lmb
    then
        cd lmb
    else
        mkdir lmb
        cd lmb
    fi

En este ejemplo, verifiqué la existencia de un directorio definido lmb, en caso negativo (else), éste seria creado. Ya sé, vas a criticar mi razonamiento diciendo que el script no está optimizado. Lo sé perfectamente, pero quería que entendieras este ejemplo, para poder usar después el signo de admiración (!) como un negador del test. Mira esto:

    if  test ! -d lmb
    then
        mkdir lmb
    fi
    cd lmb

De esta forma el directorio lmb sería creado solamente si este no existiese, y esta negación se debe al signo de admiración (!) que precede a la opción -d. Al finalizar la ejecución de este fragmento de script, el programa estaría seguramente dentro del directorio lmb.

Vamos a ver dos ejemplos para entender como se diferencia la comparación entre números y entre cadenas.

    cad1=1
    cad2=01
    if  test $cad1 = $cad2
    then
        echo Las variables son iguales.
    else
        echo Las variables son diferentes.
    fi

Ejecutando el fragmento del programa arriba, resulta:

     Las variables son diferentes.

Vamos a modificarlo un poco, de manera que la comparación esta vez sea numérica:

    cad1=1
    cad2=01
    if  test $cad1 -eq $cad2
    then
        echo Las variables son iguales.
    else
        echo Las variables son diferentes.
    fi

Y lo ejecutamos nuevamente:

     Las variables son iguales.

Como viste, en las dos ejecuciones obtuve resultados diferentes porque la cadena 01 es realmente diferente de la cadena 1, sin embargo, la cosa cambia cuando las variables son verificadas en forma numérica, ya que el número 1 es igual al número 01.

Ejemplos:

Para mostrar el uso de los conectores -o (O) y -a (Y), tengo un ejemplo bien grosero, hecho directamente en el prompt (pido disculpas a los zoólogos, ya que no entendiendo nada de reino, clase, orden, familia, género y especie, puede que lo que estoy llamando familia o género tenga grandes posibilidades de ser incorrecto):

$ Familia=felina $ Genero=gato $ if test $Familia = canina -a $Genero = lobo -o $Familia = felina -a $Genero = leon > then > echo Cuidado > else > echo Se puede acariciar > fi Se puede acariciar

En este ejemplo en caso de que el animal fuera de la familia canina Y (-a) del género lobo, O (-o) de la familia felina Y (-a) del género leon, se daria un aviso de alerta, en caso contrario el mensaje sería de incentivo.

Pinguim com placa de dica Los signos de mayor (>) al inicio de las líneas internas al if son los prompts de continuación (que están definidos en la variable $PS2) y cuando el Shell identifica que un comando continuará en la línea siguiente, automáticamente los va colocado, hasta que el comando sea finalizado.

Vamos a cambiar el ejemplo para ver si continúa funcionando:

$ Familia=felino $ Genero=gato $ if test $Familia = felino -o $Familia = canino -a $Genero = onza -o $Genero = lobo > then > echo Cuidado! > else > echo Puede acariciar > fi Cuidado!

Obviamente la operación resultó en error, ya que la opción -a tiene prioridad sobre la -o, y así lo que se evaluó primero, fué la expresión:

     $Familia = canino -a $Genero = onza

Que fué evaluada como falsa, y dió el seguiente resultado:

     $Familia = felino -o FALSO -o $Genero = lobo

Que una vez resuelta dió:

     VERDADERO -o FALSO -o FALSO

Como ahora todos los conectores son -o, y para que una serie de expresiones conectadas entre sí por diversos O lógicos sea verdadera, basta que una de ellas lo sea, la expresión final resultó como VERDADERO y el then fue ejecutado de forma incorrecta. Para que vuelva a funcionar hagamos lo seguiente:

$ if test \($Familia = felino -o $Familia = canino\) -a \($Genero = onza -o $Genero = lobo\) > then > echo Cuidado! > else > echo Puede acariciar > fi Puede acariciar

De esta forma, con el uso de los paréntesis agrupamos las expresiones con el conector -o, dando prioridad a sus ejecuciones y resultando:

     VERDADERO -a FALSO

Para que sea VERDADERO el resultado de dos expresiones ligadas por el conector -a es necesario que ambas sean verdaderas, lo que no es el caso del ejemplo arriba citado. Así el resultado final fue FALSO, siendo entonces el else correctamente ejecutado.

Si quisieramos escojer un CD que tenga músicas de 2 artistas diferentes, nos sentimos tentados a usar un if con el conector -a, pero siempre es bueno recordar que el bash nos dá muchos recursos y eso podría ser hecho de forma mucho más simple con un único comando grep, de la siguiente manera:

$ grep Artista1 musicas | grep Artista2

De la misma forma, para escojer CDs que tengan la participación del Artista1 y del Artista2, no es necesario montar un if con el conector -o. El egrep (o grep -E, siendo éste más recomendable), también nos resuelve eso. Fijate como:

$ egrep (Artista1|Artista2) musicas

O (en ese caso específico) el propio grep puro y simple podría ayudarnos:

$ grep Artista[12] musicas

En el egrep arriba, fue usada una expresión regular, donde la barra vertical (|) trabaja como un O lógico y los paréntesis son usados para limitar la amplitud de éste O.Ya en el grep de la línea siguiente, la palabra Artista debe ser seguida por alguno de los valores de la lista formada por los paréntesis rectos ([ ]), o sea, 1 o 2.

- Está bien, acepto el argumento, el if del Shell es mucho más poderoso que los otros conocidos, pero entre nosotros, esa construción del if test ... es muy extraña, y poco legible.

- Si, tienes toda la razón, tampoco me es simpática y me parece que a nadie le gusta. Creo que fue por eso, que el Shell incorporó otra sintáxis que substituye el comando test.

Ejemplos:

Para esto vamos a ver nuevamente aquél ejemplo para cambiar de directorios, que era así:

    if  test ! -d lmb
    then
        mkdir lmb
    fi
    cd lmb

y utilizando la nueva sintáxis, vamos a hacerla así:

    if  [ ! -d lmb ]
    then
        mkdir lmb
    fi
    cd lmb

O sea, el comando test puede ser substituído por un par de paréntesis rectos ([ ]), separados por espacios en blanco de los argumentos, lo que aumentará enormemente la legibilidad, pues el comando if quedara con una sintáxis parecida a de otras lenguajes y por este motivo, usaré el comando test de esta forma de ahora en adelante.

Querida, Encojieron el Comando Condicional!

Si creees que se acabó, estás muy equivocado. Repara en la tabla (booleana) siguiente:

Valores Booleanos Y O
 FALSO-FALSO   FALSO   FALSO 
 VERDADERO-VERDADERO   VERDADERO   VERDADERO 
 VERDADERO-FALSO   FALSO   VERDADERO 
 FALSO-VERDADERO   FALSO   VERDADERO 

O sea, cuando el conector es Y y la primera condición es verdadera, el resultado final puede ser VERDADERO o FALSO, dependiendo de la segunda condición, ya en el conector O, en caso que la primera condición sea verdadera, el resultado siempre será VERDADERO y si la primera fuera falsa, el resultado dependerá de la segunda condición.

Bueno, las personas que crearon el interpretador no son bobas y están siempre intentando optimizar al máximo los algoritmos. Por tanto, en el caso del conector Y, la segunda condición no será evaluada, en el caso de que la primera sea falsa, ya que el resultado será siempre FALSO. Ya con el O, la segunda será ejecutada solamente en el caso de que la primera sea falsa.

Aprovechando eso, crearon una forma abreviada de hacer tests. Bautizaron el conector Y de && y el O de || y para ver como funciona esto, vamos a usarlos como test en nuestro viejo ejemplo de cambiar de directorio, que en su última versión estaba así:

    if  [ ! -d lmb ]
    then
        mkdir lmb
    fi
    cd lmb

Eso también podría ser escrito de la siguiente manera:

    [ ! -d lmb ] && mkdir lmb
    cd lmb

O inclusive sacando la negación (!):

    [ -d lmb ] || mkdir lmb
    cd lmb

En el primer caso, si el primer comando (el test que está representado por los paréntesis rectos) estuviera bien resultado, o sea, no existe el directorio lmb, el mkdir será ejecutado, porque la primera condición era verdadera y el conector era Y.

En el ejemplo siguiente, verificaremos si el directorio lmb existe (en el anterior verificabamos si no existía) y en caso de que eso fuera verdadero, el mkdir no sería ejecutado porque el conector era O. Otra forma:

     cd lmb || mkdir lmb

En este caso, si el cd diera error, sería creado el directorio lmb pero no sería ejecutado el cd hacia dentro de él. Para ejecutar más de un comando de esta forma, es necesario que hagamos un agrupamiento de comandos, eso se consigue con el uso de llaves ({ }). Mira como sería la forma correcta:

    cd lmb ||
        {
        mkdir lmb
        cd lmb
        }

Todavia no está bien, porque en el caso de que el directorio no exista, el cd dará el mensaje de error correspondiente. Entonces debemos hacer:

    cd lmb 2> /dev/null ||
        {
        mkdir lmb
        cd lmb
        }

Como viste, el comando if nos permitió hacer un cd seguro de diversas maneras. Es siempre bueno recordar que el seguro a que me referí, es en lo referente al hecho de que al final de la ejecución, tu siempre estarás dentro de lmb, siempre que tengas permisos para entrar en lmb, permisos para crear un directorio en ../lmb, haya espacio en el disco, ...

Y toma test!

Piensas que ya se acabó? Gran error! Todavia tenemos una forma de test más. Esta es muy buena porque te permite usar patrones para la comparación. Estos patrones atienden a las normas de Generación de Nombres de Archivos (File Name Generation, que son ligeramente parecidas con las Expresiones Regulares, pero no pueden ser confundidas con éstas). La diferencia de sintáxis de este para el test que acabamos de ver, es que este trabaja con dos parêntesis rectos de la siguiente forma:

[[ expresión ]]

Donde expresión es una de las que constan en la tabla siguiente:

Expresiones Condicionales Para Padrones
  expr1 ¦¦ expr2     "O" lógico, Verdadero si expr1 o expr2 fueran Verdaderos  
  Expresión     Retorna  
  cadena == padrón
  cadena1 = padrón  
  Verdadero si cadena1 es igual a padrón  
  cadena1 != padrón     Verdadero si cadena1 no es igual a padrón.  
  cadena1 < cadena1     Verdadero si cadena1 está antes de cadena1 alfabéticamente.  
  cadena1 > cadena1     Verdadero si cadena1 está después de cadena1 alfabéticamente  
  expr1 && expr2     "Y" lógico, Verdadero si ambos expr1 y expr2 son Verdaderos  
$ echo $H 13 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora no válida Hora no válida $H=12 $ [[ $H == [0-9] || $H == 1[0-2] ]] || echo Hora no válida $

En este ejemplo,verificamos si el contenido de la variable $H esta comprendido entre cero y nueve ([0-9]) o (||) si esta entre diez y doze (1[0-2]), dando un mensaje de error en caso que no sea asi.

Ejemplos:

Para saber si una variable tiene el tamaño de un y solamente un caracter, haz:

$ var=a $ [[ $var == ? ]] && echo var tiene un caracter var tiene un caracter $ var=aa $ [[ $var == ? ]] && echo var tiene un caracter $

Como puedes imaginar, este uso de patrones para comparación, aumenta mucho el poderío del comando test. En el inicio de esta conversación, antes del último "choppe", afirmabamos que el comando if del interpretador Shell es más poderoso que sus similares en otros lenguajes. Ahora que conocimos todo su espectro de funciones, dime: estas de acuerdo o no con esta afirmación?

Acaso Casa con case

Veamos un ejemplo didáctico: dependiendo del valor de la variable $opc el script deberá ejecutar una de las opciones: inclusión, exclusión, alteración o fin. Fijate como quedaría este fragmento de script:

    if  [ $opc -eq 1 ]
    then
        inclusión
    elif [ $opc -eq 2 ]
    then
        exclusión
    elif [ $opc -eq 3 ]
    then
        alteración
    elif [ $opc -eq 4 ]
    then
        exit
    else
        echo Digite una opción entre 1 y 4
    fi

En este ejemplo viste el uso del elif con un else if, esta es una sintáxis válida y aceptada, pero podríamos hacerlo mejor y sería usando el comando case, que tiene la sintáxis siguiente:

    case $var in
        patrón1) cmd1
                 cmd2
                 cmdn ;;
        patrón2) cmd1
                 cmd2
                 cmdn ;;
        patrónn) cmd1
                 cmd2
                 cmdn ;;
    esac

Donde la variable $var es comparada a los patrones patrón1, ..., patrónn y en el caso de que uno de ellos coincida, el bloque de comandos cmd1, ..., cmdn correspondiente será ejecutado hasta que encuentre un doble punto y coma (;;), en donde el flujo del programa se desviará hacia la instrucción inmediatamente siguiente, o sea el esac.

En la formación de los patrones, son aceptados los siguientes caracteres:

Caracteres Para Formacion de Padrones
¦   O lógico  
  Caracter     Significado  
*   Cualquier caracter ocurriendo cero o más veces  
?   Cualquier caracter ocurriendo una vez  
[...]   Lista de caracteres  

Para mostrar como realmente queda mejor, vamos a repetir el ejemplo anterior, sólo que esta vez usaremos el case y no el if ... elif ... else ... fi.

    case $opc in
        1) inclusión ;;
        2) exclusión ;;
        3) alteración ;;
        4) exit ;;
        *) echo Digite una opción entre 1 y 4
    esac

Como debes haberte dado cuenta, usé el asterisco como la última opción, o sea, si el asterisco quiere decir cualquier cosa, entonces servirá para cualquier cosa que no este en el intervalo del 1 al 4. Otra cosa a tener en cuenta es que el doble punto y coma no es necesario antes del esac.

Ejemplos:

Vamos ahora a hacer un script más radical. Te dará los buenos días, buenas tardes o buenas noches, dependiendo de la hora en que sea ejecutado, pero primero mira estos comandos:

$ date Tue Nov 9 19:37:30 BRST 2004 $ date +%H 19

El comando date informa de la fecha completa del sistema, sin embargo tiene diversas opciones para su enmascaramiento. En este comando, la formatación comienza con un signo de más (+) y los caracteres de formatación vienen después de un signo de porcentaje (%), así el %H significa la hora del sistema. Dicho esto, vamos al ejemplo:

$ cat bienvenido.sh #!/bin/bash # Programa bien educado que # da los Buenos dias, buenas tardes o # las buenas noches dependiendo de la hora Hora=$(date +%H) case $Hora in 0? | 1[01]) echo Buenos días ;; 1[2-7] ) echo Buenas tardes ;; * ) echo Buenas noches ;; esac exit

Fue pesado, verdad?. - Que vá!. Vamos a desmenuzar la resolución caso a caso (o sería case-a-case? smile )

0? | 1[01] - Significa cero seguido de cualquier cosa (?), o (|) uno seguido de cero o uno ([01]) o sea, esta línea pegó 01, 02, ... 09, 10 y 11;

1[2-7]     - Significa uno seguido de la lista de dos a siete, o sea, esta línea pegó 12, 13, ... 17;

*          - Significa todo aquello que no se encuadró en ninguno de los patrones anteriores.

- Mira, hasta ahora hablé mucho y bebí poco. Ahora te voy a pasar un ejercicio para que lo hagas en tu casa y me des la respuesta la próxima vez que nos encontremos aqui en el bar, de acuerdo?

- De acuerdo, pero antes informe a las personas que nos están acompañando en este curso, como pueden hacerlo para encontrarle, para hacerle críticas, hacer chistes, invitarle a una cerveza, un curso, unas charlas o hasta si quieren, para hablar mal de los políticos.

- es fácil, mi e-mail es julio.neves@gmail.com, pero para de distraerme, que no me voy a olvidar de pasarte el script de los deberes, y este es: quiero que hagas un programa que recibirá como parámetro el nombre de un archivo y que cuando sea ejecutado grabe este archivo con el nombre original seguido de una tilde (~), además colocaras este archivo dentro del vi (de paso, el mejor editor del cual se tiene noticia) para ser editado. Esto sirve para tener siempre la última copia buena del archivo en el caso de que la persona haga alteraciones indebidas. Obviamente, tendrás que hacer las investigaciones necesarias, como verificar si fué pasado un parámetro, si el archivo que fué pasado existe, ... En fin, lo que se te venga a la cabeza y tu creas que deba estar en el script. Entendiste?

- Hum, hum...

- Chico! Traeme otra más sin espuma, que aquí el amigo ya está pensando! smile

Gracias y hasta la próxima

-- HumbertoPina - 05 Oct 2006

Licencia Creative Commons - Reconocimiento y no comercial (CC) 2009 Por Visitantes del Bar de Júlio Neves.
Todo el contenido de esta página puede ser usada de acuerdo a la Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.