Herramientas personales

Programación en Bash

De Proyectos GULIX

bash es una de tantas shell de Linux, la más popular en las distribuciones recientes como Ubuntu, Fedora o Mandriva. En la actualidad la gran mayoría de los scripts de configuración de las distribuciones de Gnu/Linux están programados en Bash, lo tanto para quien desee personalizar su sistema a fondo, tener conocimientos de bash es crítico.

En esta breve guía mostraremos varios ejemplos de programación en bash como ejemplo de los ciclos y sentencias de control vistas.

Tabla de contenidos

Dudas básicas antes de comenzar

¿Qué es un shell?

  • El shell es un interprete de comandos.
  • Pero también es un lenguaje.
  • El conjunto de comandos es un script.
  • Un script sirve como 'pegamento' de diversos comandos sencillos, que en conjunto son considerablemente poderosos.

¿Por qué aprender a programarlo?

  • Te evita hacer tareas repetitivas.
  • Es bueno conocerlo.
  • Es fácil de aprender: piensa que quieres hacer - escríbelo - revísalo (ahora ponlo todo en un archivo o script).
  • Usualmente no tienes que debugear mucho, es como si vaciaras lo que harías en el prompt de comandos, pero escrito en un archivo.
  • Es ideal para hacer algunos prototipos.

¿Cómo creo un script?

  • Crea un archivo miscript.sh con tu editor de textos favorito.
  • Dale permisos de ejecución: $chmod +x miscript.sh.
chmod +x miscript.sh.
  • Ejecutalo: $./miscript.sh
./miscript.sh

introducción

Para programar en bash se pueden utilizar todos los comandos del sistema, llamadas a otros script bash, funciones internas, y rutinas en otros lenguajes, estas características hacen que bash sea una herramienta potente que no pasa de moda.

hola mundo

#!/bin/bash
echo "Hola Mundo"

En este ejemplo, la primera línea le indica al sistema qué programa ejecutará el resto de instrucciones que están en el script. En este caso el programa es bash y la ruta donde esta ubicado el binario es /bin/bash. La segunda línea es una instrucción de bash, que permite imprimir en pantalla la frase "Hola Mundo"

Si quisiéramos usar variables, podemos crear un hola mundo como sigue:

#!/bin/bash
saludo="Hola Mundo"
echo $saludo #esto escribe Hola Mundo

De este par de ejemplos podemos ver básicamente los siguientes puntos:

  1. un programa en bash comienza por buena costumbre con #!/bin/bash
  2. al crear una variable se coloca el nombre de la variable seguido por un = y el valor que contendrá
  3. al invocar una variable se le antepone el signo $ al nombre de esta.
  4. los comentarios empiezan con #

nota

entre la variable, el = y el valor no deben existir espacios

Una tercer versión del hola mundo puede ser la siguiente:

#!/bin/bash
saludo="Hola"
echo "$saludo Mundo"

Notese que con las comilla sigue siendo el resultado el mismo (Hola Mundo) y no $saludo Mundo

utilización de variables

Ya comenzamos en la sección hola mundo a ver la utilización de variables, pero su potencialidad es mayor, dado a que se puede almacenar un una variable el resultado de alguna operación, por ejemplo, podriamos crear un bash que nos salude dando algunos datos del día y semana del año en curso, como el script que sigue:

#!/bin/bash
ddY=$(date +%j)
smY=$(date +%U)
echo "Hola, hoy es el dia $ddY del año y esta es la semana $smY"

Notemos que para asignar un valor de un comando, este necesita ser evaluado, para ello es que se utiliza el $( )

Variables especiales

En bash existen tres variables especiales, estas son las siguientes:

  • $@ : Entrega la lista de parámetros
  • $# : Entrega el número de la cantidad de parámetros
  • $? : Entrega el último valor regresado

Un ejemplo:

#!/bin/bash
if [ "$#" == 0 ]; then
echo "no hay parametros"
else
echo "hay $# parametros y son: $@"
fi

Evaluaciones numéricas

un ejemplo.

#!/bin/bash
total=$((1+1))
echo $total

Utilizando let

#!/bin/bash
let x=1 # le asigno 1 a x
let x+=1 # lo incremento en 1, el termino += es equivalente al de C
echo $x # muestra 2

let permite utilizar asignaciones y operaciones de asignación equivalentes a las de C, otras operaciones son

  • incremento +=
  • decremento -=
  • mutiplicar por *=
  • dividir por /=
  • modulo por %=
  • incremento en 1 ++
  • decremento en 1 --

Utilizando read

read se usa para leer una variable desde la entrada común stdin

#!/bin/bash
echo -n "ingrese su nombre: "
read nombre
echo "Hola $nombre"

Concatenación de cadenas

un ejemplo.

#!/bin/bash
cadena1="hola"
espacio=" "
cadena2="mundo"
echo $cadena1$espacio$cadena2

Caracteres especiales

  • # indica un comentario
  • ; separador, permite tener dos o más instrucciones en la misma linea
  • ;; terminador de bloques en la instrucción case
  • : instrucción que no hace nada, el típico NOP (do-nothing operation)
  • ! indica negación
  • * indica todo, si se usa como operación arimética, indica multiplicación
  • ** multiplicación exponencial.


Sentencias de Control

Antes de comenzar con las sentencias propiamente tal, nos detendremos en un par de operadores para crear las sentencias condicionales y así poder armar algunas expresiones a evaluar.

algunos operadores

Operadores para operaciones numéricas y lógicas

  • No !
  • y &&
  • o ||
  • menor o igual -le
  • mayor o igual -ge
  • mayor que -gt
  • menor que -lt
  • igual que -eq
  • distinto que -ne
  • operador lógico AND -a
  • operador lógico OR -o

Operadores para la gestión de archivos

  • -f Si es un archivo tradicional
  • -d Si es un directorio
  • -r Si tiene permisos de lectura
  • -w Si tiene permisos de escritura
  • -x Si tiene permisos de ejecución
  • -s Si tiene una longitud mayor que cero

if else

ejemplos:

#!/bin/bash
if((4 < 6)); then
 echo "cuatro es menor que seis"
fi
#!/bin/bash
if((4 > 6)); then
 echo "cuatro es mayor que seis"
else
 echo "cuatro no es mayor que seis"
fi

case

ejemplo: el siguiente script recibe un parametro e indicará el valor el palabras, en caso que sea uno de los que estén en la lista de lo contrario no hará nada.

#!/bin/bash
numero=$1
case $numero in
1)
 echo "uno"
;;
2)
 echo "dos"
;;
3)
 echo "tres"
;;
4)
 echo "cuatro"
;;
5)
 echo "cinco"
;;
6)
 echo "seis"
;;
7)
 echo "siete"
;;
8)
 echo "ocho"
;;
9)
 echo "nueve"
;;
esac

for

Un ejemplo típico de for es borrar todos los archivos de respaldo .bkp de los subdirectorios. En una linea de comando sería:

for i in $( find | grep "\.bkp" ); do echo "borrando $i"; rm -rf $i; done

En un script

#!/bin/bash
for i in $( find | grep "\.bkp" ); 
  do 
    echo "borrando $i"
    rm -rf $i
  done

En pocas palabras, lo que está haciendo el for, es lo siguiente:

  1. para cada i en lo encontrado por la sentencia find | grep "\.bkp"
  2. muestra "borrando $i"
  3. borra $i

veamos otro ejemplo de un for aplicando la lógica tradicional de los lenguajes de programación.


#!/bin/bash
cadena=""
 for((a=0;a<8;a++))
  do
    cadena=$cadena$a
    echo $cadena
  done

donde se obtiene como resultado lo siguiente:

./lala.sh 
0
01
012
0123
01234
012345
0123456
01234567

while

ejemplo:

#!/bin/bash 
CONTADOR=0
while [  $CONTADOR -lt 10 ]; do
   echo El contador es $CONTADOR
   let CONTADOR=CONTADOR+1 
done

until

ejemplo:

#!/bin/bash 
CONTADOR=20
until [  $CONTADOR -lt 10 ]; do
    echo CONTADOR $CONTADOR
    let CONTADOR-=1
done

Manipulación de Cadenas

En Bash podemos realizar multiples manipulaciones de Cadenas, sin embargo algunas funcionalidades es mucho más rápida realizarlas apoyandose en herramientas externas y que por lo general son parte de la mayoría de las instalaciones básicas de los sistemas *Unix.


expr, sed y awk

expr, sed y awk son 3 herramientas que nos son muy utiles para el manejo de cadenas en Bash. expr : es una utilidad para evaluar expresiones sed : es considerado un editor de textos no interactivo awk : es un lenguaje de patrones interpretados enfocados en el procesamiento, muy podedoso.


Expresión Detalle
${#cadena} Largo de una Cadena utilizando Bash puro
expr length $cadena Largo de una Cadena utilizando expr
expr index cadena_donde_buscar cadena_a_buscar” Busqueda de subcadenas
expr match cadena_donde_buscar patrón_caracteres_buscar búsqueda de una subcadena dado por un patrón
expr substr cadena posición n_caracteres Subcadena

.... otros....

Manipulando caracteres especiales

es común que necesitemos ocasionalmente manipular caracteres "raros" en nuestras rutinas. Para esto podemos optar al manejo de tales caracteres en sus códigos ascii. Así:

echo -e "\x41\x20\x42\x20\x43"

Funciones

La utilización de funciones dentro de cualquier script pueden simplificar mucho el código, además de mejorar estéticamente la presentación y comprensión del mismo.

vemos un ejemplo simple :

#!/bin/bash 
 function funcion1 {
   echo "estamos en la 1 y le pasamos el parametro:"$1
 } 
 function funcion2 {
   echo "estamos en la 2 y le pasamos el parametro:"$1
 }  
 # ahora a ejecutarlas!  
 funcion1 hola
 funcion2 chao

la salida en este caso es la siguiente:

estamos en la 1 y le pasamos el parametro:hola
estamos en la 2 y le pasamos el parametro:chao

Menús de selección sencillos

un ejemplo simple:

#!/bin/bash
echo "te gusta el loly?"
OPCIONES="si no"
select opt in $OPCIONES; do
  if [ "$opt" = "si" ]; then
     echo que bien!
     exit
  elif [ "$opt" = "no" ]; then
     echo que mal!
     exit
  fi
done


Ejemplos

Ejemplo1: Script para recodificar todos los archivos .mp3 de un directorio especificado en la linea de comandos.

El objetivo es lograr un Script que permita recodificar los archivos Mp3 de un directorio específico, por ejemplo pasar todo un disco de 192Kbps a 128Kbps. La lógica nos dice que se deben seguir los siguiente pasos:

  • paso 1 : entrar al directorio donde están nuestros archivos a recodificar.
  • paso 2 : solo para cada uno de los archivos que sean .mp3, se realizarán los siguientes pasos.
  • paso 3 : verificar si el archivo corresponde a un archivo y no a un link o un directorio.
  • paso 4 : recodificar el archivo y guardarlo con otro nombre.
  • paso 5 : ya no necesitamos el archivo viejo, asi que lo borramos.
  • paso 6 : renombrar nuestro nuevo archivo recodificado con el nombre que tenía el archivo que borramos.
  • paso 7 : si quedan más archivos por recodificar, volver al paso 2.

script:

#!/bin/bash
cd "$1"
for i in *.mp3
 do
       if [ -f "$i" ]; then
               lame -h -b 128 "$i" "$i.mp3"
               rm "$i"
               mv "$i.mp3" "$i"
       fi
  done

El comando cd $1, nos permite entrar al directorio que le hemos indicado al script como parámetro. El comando for nos permite barrer todos los archivos que se filtren mediante la expresión *.mp3, es decir todos los archivos que terminen su nombre con .mp3. El comando if, permite asegurarse que el archivo realmente es un archivo y no un link roto o una carpeta, el comando lame es un codificador de audio, que en este caso se encargar de recodificar el archivo de turno en el for.


Ejemplo2: calcular el digito verificador de un rut chileno

#!/bin/bash
# generar el digito verificador a partir del rut sin digito :)
# by Lacosox.org
  rut=12345678
  div=1
  serie=2
  sum=0
  verificador=0
    for((a=0;a<8;a++))
       do
               temp=$((rut/div))
               div=$(($div*10))
               if [ $serie = "8" ]; then
                       serie=2
               fi
               sum=$(((((($temp-((((rut/div))*10))))*serie))+$sum))
               serie=$(($serie+1))
       done
       verificador=$((11-(($sum%11))))
       if [ $verificador = "10" ]; then
           verificador=k
       fi
       if [ $verificador = "11" ];then
           verificador=0
       fi
  echo "rut completo: "$rut"-"$verificador

Ejemplo 3 : Cada 10 minutos mostrar el mensaje "Anda a estudiar!"

#!/bin/bash
#by lacosox.org
total=$((10*60)) # 10 minutos son 10*(60 segundos)
 while ((0==0)); do
   sleep $total    
   echo "Anda a estudiar!"
 done

Ejemplo 4: Función LastIndexOf para bash

Este método devuelve la primera posición dentro del String donde comienza la subcadena pasada como argumento, pero realizando la búsqueda de derecha a izquierda.

#!/bin/bash
#Función LastIndexOf by lacosox.org
# retorna la posición donde está la subcadena, buscando de atrás hacia adelante, si no existe, retorna 0.
#
function lastIndexOf {
  todo=$1;
  cadena_a_buscar=$2;
  largo_todo=${#todo};
  largo_cadena_a_buscar=${#cadena_a_buscar};
  if [ $largo_todo -ge $largo_cadena_a_buscar ];then
     for((a=$(($largo_todo-$largo_cadena_a_buscar));a>=0;a--))
       do
		subcadena=${todo:a:largo_cadena_a_buscar};
		if [ $subcadena = $cadena_a_buscar ];then
			echo  $((a+1));
			break;
		fi
	done
  else
    echo "0";
  fi	
}

Ejemplo de uso:

function lastIndexOf {
  todo=$1;
  cadena_a_buscar=$2;
  largo_todo=${#todo};
  largo_cadena_a_buscar=${#cadena_a_buscar};
  if [ $largo_todo -ge $largo_cadena_a_buscar ];then
     for((a=$(($largo_todo-$largo_cadena_a_buscar));a>=0;a--))
       do
		subcadena=${todo:a:largo_cadena_a_buscar};
		if [ $subcadena = $cadena_a_buscar ];then
			echo  $((a+1));
			break;
		fi
	done
  else
    echo "0";
  fi	
}
si=`lastIndexOf "esta es la cadena esta" "ta"`;
echo $si

salida:

dalacost@lacosox.org:~$ ./test.sh 
21

Ejemplo 5: Borrar archivos desde una lista predefinida en otro archivo

Primero editaremos un archivo que contendrá la lista de archivos a borrar, luego construiremos un script que lea este archivo y borre linea a linea los archivos.

para crear el archivo, debemos tener en cuenta la forma que tendrá el script para leer cada linea y borrar cada uno de los nombres que en el archivo se listen.

la herramienta tree nos ayuda a listar archivos con sus rutas completas. Lo que será de gran ayuda para ejecutar el script sobre cualquier directorio.

listando todos los archivos que contengan en su nombre los caracteres ".jpg" y guardando en un archivo.

$tree -i -f | grep .jpg > lista_de_archivos_a_borrar.txt

Luego el script sería de la siguiente manera.

for linea in $(cat lista_de_archivos_a_borrar.txt);
do 
   rm $linea;
done

Ejemplo 6: Listando el tamaño de un archivo o directorio

Listará el tamaño de un archivo o directorio que se le entrega como parámetro, indicará un mensaje de error en caso que la ejecución no sea la correcta.

#!/bin/bash

Error(){
        echo "Error. Sintaxis de uso: $0/ archivo | directorio"
}

if [ $# -lt 1 ]; then
        Error
elif [ -d $1 ]; then
        echo "$1 es un directorio y su tamaño es el siguiente:"
        du -hs $1
   elif [ -f $1 ]; then
        echo "$1 es un archivo regular y su tamaño es el siguiente:"
        du -hs $1
   else echo "$1 no existe."
fi


Otra opción sencilla es la utilización del comando du:

#Ejemplo 1 para saber tamaño total del home de usuario knx (incluye tamaño de todos los subdirectorios)
du -h -s /home/knx/
# Ejemplo 2 , lo mismo pero con detalle
du -h /home/knx/

Ejemplo 7: Obteniendo un calendario para la oficina desde terminal

Para ejecutar este script debes tener instalado la herramienta cowsay, para instalar esta utilidad en debian solo debes ejecutar el comando apt-get install cowsay.

Este ejemplo de script esta escrito completamente desde la terminal, es decir no está dentro de un archivo que luego se ejecuta. Sin embargo igualmente es posible utilizar las sentencias de bash directamente, siempre y cuando estas estén bien utilizadas.


root@fenix:/home/knx# cowsay -f tux Calendario 2009-2010  de lo que queda del \
 gobierno del CEII actual && cal -3m 11 2009 && c=2;while [ $c -lt 10 ];\
 do cal -3m $c 2010;let c=c+3;done
______________________________________
/ Calendario 2009-2010 de lo que queda \
\ del gobierno del CEII actual         /
--------------------------------------
  \
   \
    \
        .--.
       |o_o |
       |:_/ |
      //   \ \
     (|     | )
    /'\_   _/`\
    \___)=(___/

    octubre 2009         noviembre 2009        diciembre 2009    
lu ma mi ju vi sá do  lu ma mi ju vi sá do  lu ma mi ju vi sá do 
          1  2  3  4                     1      1  2  3  4  5  6
5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13
12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20
19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27
26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31        
                      30                                       
     enero 2010           febrero 2010           marzo 2010      
lu ma mi ju vi sá do  lu ma mi ju vi sá do  lu ma mi ju vi sá do 
             1  2  3   1  2  3  4  5  6  7   1  2  3  4  5  6  7
4  5  6  7  8  9 10   8  9 10 11 12 13 14   8  9 10 11 12 13 14
11 12 13 14 15 16 17  15 16 17 18 19 20 21  15 16 17 18 19 20 21
18 19 20 21 22 23 24  22 23 24 25 26 27 28  22 23 24 25 26 27 28
25 26 27 28 29 30 31                        29 30 31           
                                                              
     abril 2010            mayo 2010             junio 2010      
lu ma mi ju vi sá do  lu ma mi ju vi sá do  lu ma mi ju vi sá do 
          1  2  3  4                  1  2      1  2  3  4  5  6
5  6  7  8  9 10 11   3  4  5  6  7  8  9   7  8  9 10 11 12 13
12 13 14 15 16 17 18  10 11 12 13 14 15 16  14 15 16 17 18 19 20
19 20 21 22 23 24 25  17 18 19 20 21 22 23  21 22 23 24 25 26 27
26 27 28 29 30        24 25 26 27 28 29 30  28 29 30           
                      31                                       
     julio 2010           agosto 2010         septiembre 2010    
lu ma mi ju vi sá do  lu ma mi ju vi sá do  lu ma mi ju vi sá do 
          1  2  3  4                     1         1  2  3  4  5
5  6  7  8  9 10 11   2  3  4  5  6  7  8   6  7  8  9 10 11 12
12 13 14 15 16 17 18   9 10 11 12 13 14 15  13 14 15 16 17 18 19
19 20 21 22 23 24 25  16 17 18 19 20 21 22  20 21 22 23 24 25 26
26 27 28 29 30 31     23 24 25 26 27 28 29  27 28 29 30        
                     30 31                                    
root@fenix:/home/knx#


Ejemplo 8: Obteniendo la IP de la interfaz que mantiene la conexión activa hacia Internet

A continuación vemos 2 formas de obtener cual es la IP de conexión activa que actualmente esta utilizando el sistema para salir a Internet. Para obtenerla se han utilizado los comandos grep, ip, awk,sed. Esta IP no necesariamente corresponde a la ip pública del usuario, pero si a la IP por la cual esta conectado a la red. Esto puede ser utilizado por ejemplo en firewall iptables en los cuales se necesita conocer la IP activa asignada a la interfaz con la cual se está saliendo a Internet.

Forma 1
echo $(ip -4 addr show dev $(ip route|grep default|awk '{print $5}') | \
     grep inet | head -1 | \
     sed -r 's/.*inet ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/')
Forma 2
echo $(ip -f inet addr show dev $(ip route | grep default | awk '{print $5}') | \
    grep inet | awk '{print $2}' | \
    sed 's/\/.*//')

Link donde encontrar más ayuda

Autores de este tutorial

  • --RAcl 04:57 28 ene 2009 (UTC)
  • --Dalacost 23:51 28 ene 2009 (UTC)
  • --KnX 04:07 28 jun 2012 (UTC)

Ultima actualización

  • --RAcl 19:36 28 ago 2013 (UTC)

Leer más


Buscar