Bash Scripting 1° Parte
En esta publicación haré una breve introducción a la programación de scripts en bash. La primera pregunta que debemos hacernos es para que nos sirven estos scripts, principalmente para automatizar tareas, este “lenguaje” es bastante amplio por lo cual tenemos una basta gama de opciones a la hora de implementar una solución a un problema.
Variables
Para empezar a programar scripts tenemos que conocer cosas básicas de la programación, por ejemplo las variables, básicamente podemos definir a una variable como un dato, este dato a medida que el script corra puede ir tomando distintos valores y dependiendo de los valores de esta variable el script podría tomar distintos caminos.
Por ahora solamente nos detendremos en la definición de una variable. Cuando una variable toma un valor se dice que hay una asignación, por ejemplo, crearemos una variable con nombre “numero” y a esa variable le asignaremos el valor 4:
numero=4
Con esta linea la shell comprenderá que hay una variable llamada numero cuyo valor es 4. De igual forma podemos preguntar por el valor de la variable numero anteponiendo el símbolo $, por ejemplo:
echo $numero
El comando echo muestra en pantalla (la consola) el parámetro enviado, en nuestro caso enviamos $numero, con esto el interprete de comandos sobreentiende que lo que queremos mostrar es el valor de la variable numero, por lo que si previamente le habíamos asignado un valor a numero (en nuestro caso 4) en la consola deberíamos visualizar dicho valor (en el ejemplo: 4).
A las variables se les pueden asignar también texto y otras estructuras como arreglos (estructura que veremos mas adelante) entre otras.
Estructuras de control
Las estructuras de control son los recursos mas utilizados a la hora de programar, recordemos que la programación de scripts en bash siempre tiende a ser secuencial, o sea que si se está ejecutando la primera linea, obviamente la segunda instrucción seria la siguiente en ejecutarse, de esta forma hay veces que necesitamos saltarnos o repetir código ya escrito y para esto sirven las estructuras de control. Por ejemplo necesitamos sacar el factorial de 5, la solución sería:
factorial=5
factorial=`expr $factorial \* 4`
factorial=`expr $factorial \* 3`
factorial=`expr $factorial \* 2`
Aunque la solución se corresponde con el problema y lo resuelve efectivamente pensemos si tenemos que sacar el factorial de 50. Según lo planteado anteriormente calcular dicho valor sería de la siguiente forma:
factorial=50
factorial=`expr $factorial \* 49`
factorial=`expr $factorial \* 48`
factorial=`expr $factorial \* 47`
factorial=`expr $factorial \* 46`
factorial=`expr $factorial \* 45`
.
.
.
factorial=`expr $factorial \* 2`
Complicando un poco mas el asunto, ¿que pasa si no conocemos el valor del numero del cual queremos calcular su factorial?.
Una de las formas EFICIENTES de resolver el asunto es usando una estructura que itere sobre una secuencia de instrucciones, por ejemplo el for:
#calcular el factorial de un numero cuyo valor se desconoce usando el for
numero=10
factorial=$numero
#`expr $numero - 1` Es la sintaxis para realizar calculos,
#en este caso restamos 1 al al valor de la variable numero,
#o sea que numero pasa a valer 9
numero=`expr $numero - 1`
echo $factorial
echo $numero
for i in `seq 1 $numero`
do
factorial=`expr $factorial \* $i`
done
echo $factorial
Si nos fijamos en los ejemplos anteriores podemos ver que la unica diferencia que hay en todas las instrucciones es el numero por el cual multiplicamos a la variable factorial, con la estructura iteradora for resolvemos fácilmente el problema de escribir a mano cada uno de los valores por el cual se debe multiplicar a factorial. Veamos los “parámetros” que le pasamos al for, i va a ser una variable temporal que tomará un valor distinto por cada iteracion, en nuestro ejemplo el primer valor que toma es el 1, después el 2, el 3 y así sucesivamente hasta llegar al 49. La expresión ´seq 1 $numero´ indica una secuencia desde el 1 hasta el valor de la variable numero (en este caso 49). Siguiendo el ejemplo podemos ver que al principio factorial se multiplica por 1, luego por 2, por 3, y así hasta 49. Llegando así a la solución propuesta, solución eficiente que resuelve el problema de manera clara y concisa.
Otra solución posible es usando el while, esta estructura hace que se ejecute la secuencia de instrucciones que tiene descrita siempre y cuando la condicion declarada se cumpla. Siguiendo el mismo problema del factorial:
#calcular el factorial de un numero cuyo valor se desconoce usando el while
numero=10
factorial=$numero
secuencia=`expr $numero - 1`
while [ $secuencia -ne 1 ];
do
factorial=`expr $factorial \* $secuencia`
secuencia=`expr $secuencia - 1`
done
echo $factorial
Con el while controlamos a la variable secuencia cuyo valor inicial es 9, mientras que no sea igual a 1, se realizaran las acciones contenidas entre el do y el done. Para saber que expresiones se pueden evaluar en este tipo de condiciones (la condicion que está entre corchetes) les recomiendo que escriban “man test” en consola, y verán cuan amplio es el lenguaje.
Muchas veces tenemos evaluar condiciones y dependiendo de una condicion realizar determinada accion, tomemos como ejemplo el siguiente problema, se recibe como parametro un numero, si ese numero es mayor o igual a 50 se le debe sumar 15, en caso contrario debe ser multiplicado por 2.
Antes que nada tenemos que saber como recibir parametros, a un script podemos enviarle parametros y este los puede leer sin problemas, volvamos al caso del factorial, suponiendo que al script se le debe enviar como parametro el numero a calcular tendriamos que llamarlo de la siguiente forma:
./factorial 10
Se presupone que el script se llama factorial y el 10 es el parametro enviado. Para leer ese parametro lo llamamos con $1 donde 1 corresponde a la cantidad de parametros enviados. Por ejemplo, consideremos un script al cual se le debe enviar 3 parametros:
./script parametro1 parametro2 parametro3
$1 va a corresponder a parametro1, $2 corresponde a parametro2 y $3 a parametro3. También podemos preguntar cuantos parametros fueron enviados escribiendo $#, en nuestro caso el siguiente comando devolvería 3 ya que enviamos tres parametros.
Volviendo al problema anterior, lo podemos resolver facilmente con la estructura if, esta es muy usada en los scripts y nos deja decidir que camino tomar ante determinada accion.
Para resolverlo:
numero=$1 #para una mayor comprension del
#algoritmo asignamos a numero el parametro enviado
if [ $numero -ge 50 ]; then
numero=`expr $numero + 15`
else
numero=`expr $numero \* 2`
fi
La condicion entre corchetes evalua si el valor de numero es mayor o igual a 50, en cuyo caso a numero se le suma 15, si el numero no cumple con la condicion va a saltar al else que lo multiplicará por 2. Para cerrar la evaluacion escribimos fi.
La estructura if puede simplificarse, por ejemplo si solamente necesitamos sumar 15 a los numeros mayores o iguales a 50 y despues devolver su valor podemos escribir:
numero=$1
if [ $numero -ge 50 ]; then
numero=`expr $numero + 15`
fi
echo “El numero es “$numero
Y tranquilamente el script funcionará evaluando que numero sea mayor o igual a 50, si no cumple la condicion no entra y salta a la siguiente linea, en este caso:
echo "El numero es "$numero
Ahora estamos en condiciones de programar un script basico, solamente nos queda tener algunas consideraciones previas:
1- La primer linea que debemos escribir en un script en bash es “#!/bin/bash” (sin comillas) para indicar al interprete de comandos que usaremos bash.
2- Siempre conviene comentar el script, de esta forma evitaremos dolores de cabeza cuando el script se hace muy largo y empezamos a notar que todo se convierte en “chino basico”.
3- La mejor forma de aprender a programar scripts es programando scripts, asi que si alguien llegó hasta aqui en la lectura, tomese el tiempo de hacer algo por mas simple que sea xD.
Bien, eso es todo por hoy. Por cada ejemplo subí un script para que lo prueben y comenten sus experiencias, espero que esto haya servido, en caso afirmativo seguiré hablando del tema.
Para probar los siguientes scripts, deben abrir la consola, situarse en la dirección del archivo y llamarlo de la siguiente forma:
./nombredelscript
Antes deben darle permisos de ejecucion:
sudo chmod 777 nombredelscript
Completa esta lectura con:
Si te gustó este post, por favor considera dejar un comentario o suscribirte al feed y obtener artículos futuros en tu lector de feeds.
Comentarios (16)
Hola Marshal, te comento, para hacer operaciones aritméticas los símbolos son -, +, / y \* para restar, sumar, dividir y multiplicar respectivamente, con respecto a evaluar dos valores, hay muchas opciones, te comento las mas básicas, si las variables son números enteros, podes usar las siguientes expresiones:
$a -eq $b Para preguntar si el valor de a es igual al b (eq viene de equal)
$a -gt $b Para preguntar si el contenido de a es mayor al de b (gt viene de greatter than)
$a -ge $b a es mayor o igual a b (ge viene de greather than or equal)
$a -le $b a es menor o igual a b (le viene de less than or equal)
$a -lt $b a es menor a b (lt viene de less than)
Con respecto a strings (cadenas de caracteres) algunos basicos:
$string_a = $string_b String_a es igual a String_b
$string_a -n Para preguntar si la cantidad de caracteres de string_a es distinta de 0
Hay muchas opciones, te recomiendo que escribas en consola:
man test
Ahí vas a encontrar todas las opciones que te nombré y mas. Espero que te haya servido.
Saludos!
Ahhh jeje perfecto…ya entiendo un poco mas jeje….muchas gracias por aclararme las dudas y darme mas instrucciones un saludo…
Eral, tengo una duda con un script que he creado que realemente no logro hacer que camine de la manera que quiero y he consultado en varios lugares antes de recordar este post…..mi pregunta es si hay alguna manera de indicarle al comando cp (ya sea con un bucle o similar) que se “salte” uno de los directorios a copiar ( lo estoy utlizando de manera recursiva).
Disculpa, yo se que esto no es un foro pero la verdad no encuentro que mas buscar o donde preguntar….Gracias
Hola Marshall, si te entendí bien lo que podrías hacer es esto:
#!/bin/bash
#Te situas en el directorio
cd /home/ernst/carpeta
#Definis una variable con un string que te sirva para filtrar el archivo que NO queres copiar
filtro=nombreDeArchivo
#Usas un bucle que itere sobre cada archivo del directorio situado
for i in *
do
if [ $i = $filtro ]; then #Si el archivo tiene el mismo nombre del que no queres copiar, lo omitis
echo “El archivo $i ha sido omitido”
else
cp $i /home/ernst/Escritorio #Sinó lo copias
fi
done
Espero que te haya servido, cualquier cosa no dudes en preguntar, saludos!!!!
Eral, en abono a tu respuesta le diría a nuestro amigo Marshal, que a un comando de sistema no se le puede modificar en su función excepto por sus parámetros inherentes como es el caso de [cp] modificado por [- -recursive] o el modo abreviado [-r].
Marshall, se me dió por leer nuevamente tu comentario y me dí cuenta de que querías saltarte un directorio, así que simplemente modifiqué el comando cp agregandole un -r para que no tengas problemas con las carpetas, de esta forma te podrías filtrar directorios o archivos sin ningun problema, espero que te haya sido de ayuda:
#!/bin/bash
#Te situas en el directorio
cd /home/ernst/carpeta
#Definis una variable con un string que te sirva para filtrar el archivo que no queres copiar
filtro=noCopiar
#Usas un bucle que itere sobre cada archivo
for i in *
do
if [ $i = $filtro ]; then
echo “$i ha sido omitido”
else
cp -r $i /home/ernst/Escritorio
fi
done
Pues Muchas gracias a todos y disculpen que haga estas preguntas aquí, ya incluí el pedazo de código que me indicaron….sin embargo todavía no funciona correctamente, les dejo el lo que he hecho para ver en que me he equivocado.
#!/bin/bash
echo “Bienvenido al script de Backup!”
mkdir $HOME/Backup
cp /etc/apt/sources.list $HOME/Backup/sources.list
sudo cp $HOME/.gnupg/pubring.gpg $HOME/Backup
sudo cp $HOME/.gnupg/secring.gpg $HOME/Backup
sudo mv -f /var/cache/apt/archives $HOME/Backup
cd /var/cache/apt/
sudo mkdir archives
cd archives
sudo mkdir partial
cd $HOME/Backup
tar czvf archives.tar.gz archives
echo “Este proceso tomara los paquetes que ha instlado con el sistema APT, si ha ejecutado comandos com: sudo apt-get clean o sudo apt-get autoclean, puede que le aparezcan menos paquetes de los que en realidad instalo”
sudo dpkg –get-selections | grep -v deinstall > ubuntu-files
echo “Acabo de crear una lista con todos los programas que usted tiene ahora instalados, para que los utilice como futura referencia, el nombre del archivo es ubuntu-files”
cd $HOME/Backup
mkdir config
cd $HOME
#Definis una variable con un string que te sirva para filtrar el archivo que no queres copiar
filtro=/$HOME/..
sleep 5
#Usas un bucle que itere sobre cada archivo
for i in *
do
if [ $i=$filtro ]; then
echo “$i ha sido omitido”
sleep 25
else
cp -r $i $HOME/Backup/config
fi
done
sleep 25
sudo tar czvf $HOME/Backup/config.tar.gz $HOME/Backup/config.tar.gz
echo “Bien Ahora puede guardar el contenido de la Carpeta Backup en un CD o DVD, que con el script Reinstalacion, ocupara estos archivos para volver su Ubuntu como lo tenia”
sudo chmod 777 -R $HOME/Backup
Gracias de antemano y disculpen la majaderia (insistencia) como decimos en mi pais. Saludos y excelente blog
Asi rapidamente lo que puedo ver es que está mal asignado el valor a la variable filtro, tenes que poner el nombre de lo que no queres que se copie, por ejemplo, si en tu home tenes cuatro archivos: a1, a2, a3, a4 y no queres que se copie el archivo a3 tenes que asignarle a la variable filtro el valor a3, ej: filtro=a3
Gracias por la rápida respuesta Eral, y el problema es ese, lo que quiero omitir es un directorio (en realidad varios porque lo que quiero hacer en backup son los archivos de configuración que usualmente están en el Home del usuario y empiezan con .*) sin embargo creo que he mejorado un poco el bucle y ya esta haciendo en un 75% lo que quiero, omite todos los directorios que no sean de configuración, sin embargo ahora no me busca las carpetas de configuración, como que las omite….aqui te dejo el bucle:
cd $HOME
#Definis una variable con un string que te sirva para filtrar el archivo que no queres copiar
filtro=/$HOME/.. #este es el directorio que quiero omitir
#Usas un bucle que itere sobre cada archivo
for i in $HOME/*
do
if [$i=$filtro]; then
echo “$i ha sido omitido”
elif [$i=.*]; then
cp -r $i $HOME/Backup/config
else
echo “No es carpeta de configuracion”
fi
done
Ya casi esta terminado, solo me falla ese detallillo, gracias
Marshall, creo que ahora estoy entendiendo mas tu problema, si lo que queres es solamente filtrar los directorios de configuracion, o sea los que tienen un . (punto) inicial la cosa es mas facil:
#!/bin/bash
cd $HOME
for i in `ls -a` #ls -a te va a mostrar todos los archivos y directorios, inclusive los que comienzan con .
do
if [ ${i:0:1} = . ]; then #Si el nombre del directorio o archivo empieza con . lo copiamos
cp -r $i $HOME/Backup/config
fi
done
Muchas Gracias Eral, creo que eso me soluciono el problema de buscar los archivos o carpetas que empiezan con . ….sin embargo nose como implementarle el filtro ya que cuando ejecuto el comando “ls -a” salen dos opciones que son erróneas:
$HOME/. –> que es un enlace a la misma carpeta donde uno se encuentra
$HOME/.. –>que un enlace al directorio anterior (un enlace atras)
ya habíamos creado los filtros anteriormente, sin embargo no se como implementarlos en este ultimo bucle la verdad.
Saludos y gracias de verdad
Perdon Marshall, el error es mio, al probar el script lo hice sin reemplazando el comando cp por echo para ver simplemente si hacia lo que quería, no corroboré que el copiado sea efectivo, ya lo solucioné:
#!/bin/bash
cd $HOME
for i in `ls -a` #Recorremos todos los archivos, inclusive los ocultos
do
if [ ${i:0:1} = . ]; then #Si el primer caracter del nombre del archivo es un .
if [ $i != . ] && [ $i != .. ]; then #Si el nombre es distinto de . y ..
echo “Copiando ” $i
cp -r $i $HOME/Backup/config
fi
fi
done
Una observacion mas, comprobando el correcto copiado de los archivos de configuración noté algunos errores al ejecutar cp, todos fueron: permiso denegado, un consejo para no tener problemas con estas cosas es cada vez que vas a realizar un script en el cual tengas que manipular archivos que no son del usuario que lanzó el script es comprobar al principio que el que llamó al script (valga la redundancia) sea el superusuario, esto es muy simple, te lo detallo a continuacion:
#!/bin/bash
if [ $UID = 0 ]; then #El unico que tiene uid = 0 es el root
echo “Comenzando el backup”
#Acá va todo el codigo que escribiste
else
echo “Debe lanzar este script con permisos de superusuario”
echo “Puede llamarlo de la siguiente forma: sudo ./script”
fi
Ahora, para llamarlo simplemente escribes:
sudo ./script
Saludos!!!
Listo!! Creo que quedo preparado todo el script completo todo todito terminado completamente (jajajajaja) no ya enserio, quedo realmente agradable y cómodo gracias a ese ultimo bucle (brillante). Realmente muchas gracias Eral, la verdad es que tienes mínimo 50% de autoría del Script…Te lo he mandado a tu correo de contacto completo con algunas variaciones.
Todavía pretendo darle muchas mas mejoras (ya hice algunas utilizando el zenity) conforme vaya aprendiendo mas sobre scripting, sin embargo muchas gracias y ahí tienes un blog amigo por si las dudas!
Saludos
hola he ejecutado algnos programa que e cho en bash con el comdo shtdown y funcionan bien en la seccion administrativas pero cuando saco el archivo pra otra seccion no administ. el comando no toma accion :como aser pra esto digo si hay solucion por favo escribanme a :
jorege_isaac01@yahoo.es le agradeseria muchoo










Nada mas una cosita que me gustaría saber….en bash cuales son los operadores básicos (como decir == para igual o ++ para aumentar y así) realmente no los encuentro y me gustaría tenerlos para practicar mas fácilmente. Gracias y excelente blog