Bash: el temido acompañante de Linux

Bash es el intérprete de comandos del proyecto GNU. Bash es una abreviatura de Bourne-again shell, esto no es un error, en realidad el significado de la abreviatura es una especie de “juego de palabras” relacionado a la shell anterior llamada Bourne. De un modo u otro el significado, claramente, es “nacida de nuevo” (born-again shell).

La idea detrás de realizar este reemplazo se dio por varias razones, principalmente, porque Unix Shell de sistemas basados en Unix o GNU/Linux carecía de varias funciones que todo usuario avanzado o desarrollador desea tener a mano constantemente.

Entre ellas, se solventaron problemas como los siguientes:

  • Edición de la línea de comandos.
  • Cantidad de índices de arrays ilimitados.
  • Control de tareas.
  • Posibilidad de creación de funciones.
  • Historial de comandos ilimitado.

En esta entrada, explicaré algunas de estas funciones. También, aclararé un poco los cambios en las pruebas lógicas de UNIX Shell a Bash.


Antes de comenzar:

  • “\n” quiere decir “nueva línea” o “salto de carrete”.
  • “\r” quiere decir “volver” o “reiniciar”, la función que cumple es la de recomenzar la salida desde el principio de la última línea escrita. Entonces, se imprime en la misma línea “repasando” lo que ya estaba impreso.
  • El funcionamiento “por defecto” de echo es imprimir “\n” al final de la entrada dada.
  • En algunos ejemplos, se utilizará printf para demostrar su potencial y hacer más entretenida la experimentación.
  • En Linux, dirigir algo a la ruta “~” quiere decir “carpeta personal”.
  • Colocar ” | ” al final de un comando provoca un “túnel”, esto quiere decir que su salida se redirige a otro comando.
  • Asignar algo a una variable colocándolo entre “acentos graves” (es preferible llamarlo por su nombre inglés, “backslash”) permite obtener la salida de uno o más comandos y redirigirla directamente a la variable.

Variables

En Bash, a diferencia de la mayoría de lenguajes de programación, la manera en que se declaran las variables no podría (o al menos no debería) causarle dolores de cabeza a nadie.

Asignación básica: 

Declaremos una variable… Digamos que… VARIABLE1 tiene un valor igual a 5.

VARIABLE1=5

Eso es todo, la variable fue asignada y ya es un entero.

Pero Bash puede hacer mucho más. veámoslo en profundidad.

Operaciones matemáticas básicas: 

En este caso, hay dos formas de realizarlo. Podemos calcular directamente en base a valores o calcular en base a otras variables (llamarlas dentro del cálculo).

Ejemplo del primer caso:

VARIABLE1=$((5 + 5))

En el caso anteriormente demostrado, sumamos dos números directamente y la variable obtiene un valor igual a 10.

Hasta ahora puede que parezca no tener mucha utilidad. Pero veamos otros posibles casos.

Supongamos que tenemos 3 variables numéricas. De esas variables, necesitamos obtener un valor final basado en la suma de todas ellas. En Bash, llamamos a las variables anteponiendo el signo “$“. Aquí, les presento un ejemplo interesante y un poco más avanzado que el anterior.

VARIABLE1=$((50 * 50))
VARIABLE2=$((200 + 200))
VARIABLE3=$((100 – 50))
SUMA=$(( $VARIABLE1 + $VARIABLE2 + $VARIABLE3 ))

Asignación avanzada: 

También, es posible asignar la salida de un comando a variables específicas.

En el siguiente ejemplo, obtendremos la lista de archivos de nuestra carpeta (o partición) personal (/home/$USER) y contaremos cuántos archivos hay, basándonos en la cantidad de líneas presentes en la salida. Para lo primero, utilizaremos find y, luego, para obtener la cantidad, utilizaremos wc.

CANTARCHIVOS=`find ~ | wc -l`

Bloques y sintaxis de condicionales

En Bash, podemos comparar enteros, variables o ejecutar un comando, recibir la salida del comando y utilizarla para hacer una prueba condicional.

Si / si no:

Sintaxis

if [ prueba ]

then

 instrucción1

 instrucción2

fi

Ejemplo (resultado de condicional es menor que). Si es menor, mostrará en pantalla el mensaje que puede verse a continuación (utilizando el ejecutable “echo”).

if [ $(( 5 + 5 )) -gt 10 ]

then

 echo “False! El resultado de la prueba condicional suministrada es menor o igual 10.”

else

 echo “True! El resultado de la prueba condicional suministrada es mayor a 10.”

fi

Mientras:

Sintaxis

while [ prueba ]

do

 instrucción1

 instrucción2

done

Ejemplo: en este ejemplo se demuestra que se puede suministrar una condición ya evaluada, esto quiere decir, que podemos decidir manualmente si la condición será verdadero o falso. Como verán, el siguiente ejemplo provee al bloque mientras la condición ya evaluada a verdadero. Este tipo de condición, sólo se detiene si una instrucción externa logra invertir esta condición o se fuerza a que el script finalice.

while true

do

 printf “Hey! Pulsa CTRL-C para detenerme.”

done

Para:

Sintaxis

for variable in cadena de texto

do

 instrucción1

 instrucción2

done

A diferencia de la mayoría de otros lenguajes de programación, en este caso, un bloque for no provoca que una variable salte entre enteros o se sume en valores exactos. Sino, que analiza una especie de “palabras” y las deriva a la variable que ingresemos.

En el siguiente ejemplo, se demuestra cómo es posible adaptar el bloque for para que funcione como lo hace en otros lenguajes:

i=1

for i in `seq 1 10`

do

 printf “$i/10\r”

 sleep 0.2

done

printf “\n”

De todos modos, el hecho de que este bloque no funcione al igual que en la mayoría de lenguajes de programación no es una desventaja, sino una gran ventaja en torno a las utilidades que se le pueden dar.

Control de flujo y salidas

Control de flujo:

Algo muy útil que es posible implementar en nuestros scripts es el control de flujo y salidas, es decir, detener en caso de error, guardar errores, depurar, redirigir, y demás. Esto es muy sencillo, a continuación daré algunos ejemplos.

Supongamos que le queremos preguntar al usuario si quiere continuar o no. Es claro que sí o no son dos respuestas diferentes, una continúa con el programa y la otra, simplemente lo detiene. Podríamos hacer un gran ciclo de comprobaciones para saber si el usuario aceptó o no, pero esto sería una pérdida de tiempo y podríamos provocar que las instrucciones consultadas consuman un exceso de recursos, sólo para saber si el usuario aceptó o no. Imaginemos la cantidad de recursos que podría consumir el resto del programa si comprobaramos así continuamente las preguntas.

Para esto, Bash nos permite finalizar directamente y terminar el código en un lugar para, finalmente, volver a la Terminal.

Los estados de salida pueden resumirse en dos grandes casos: 0 y todo lo demás. Si el estado de salida es 0, el programa o script terminó sin errores. Sino, el script tuvo algún error. Los estados pueden ser arbitrarios, aunque, generalmente, con colocar 1 ya es suficiente para declarar un error y hacérselo saber a Bash para así, si el usuario ejecuta nuestro script como parte de otro, sabrá que hubo un error al consultar la variable $?.

La variable $? guarda el estado del último comando o instrucción ejecutado.

Ejemplo de la explicación anterior:

printf “¿Estás seguro? (S/N)\n”

read CONFIRMACION

if [ “$CONFIRMACION” == “S” ]

then

 echo “¡Bienvenido al script!”

 …

else

 echo “¡Adiós!”

 exit 0

fi

En el ejemplo anterior, si el usuario decide abortar la ejecución, nos aseguramos de informarle a Bash de que el programa terminó sin ningún error.

Supongamos que el script requiere de 1 megabyte de espacio libre en disco para funcionar y que ya nos encargamos de obtener esa información con anterioridad. El usuario tiene 0.5 megabytes libres. Esto, sería un error, por lo tanto, se trataría de la siguiente manera.

ESPACIOMIN=1024

ESPACIO=512

printf “¿Estás seguro? (S/N)\n”

read CONFIRMACION

if [ “$CONFIRMACION” == “S” ]

then

 echo “¡Bienvenido al script!”

 if [ $ESPACIO -lt $ESPACIOMIN ]

 then

  echo “¡Error! Tienes menos espacio libre del mínimo necesario.”

  exit 1

 else

  …

 fi

else

 echo “¡Adiós!”

 exit 0

fi

Como pueden observar, informamos de un error. Entonces, si el script que está detrás intenta buscar errores en el script que se ejecutó anteriormente, podrá informar al usuario del error. En el siguiente ejemplo, consultamos el estado del último comando (o script) ejecutado.

if [ $? -ne 0 ]

then

 echo “Se encontraron errores durante la ejecución del script.”

fi

En el ejemplo anterior, tratamos de saber si el comando anterior tuvo un estado diferente a cero. Es decir, un error, cualquiera.

Cabe destacar que, por ejemplo, si no se encuentra un patrón buscado, esto también es un error. Imaginemos que tenemos un archivo llamado “nombreblog” con el siguiente contenido:

El

Vinculo

Digital

Si ejecutamos grep “El” nombreblog obtendremos un estado de salida igual a cero, porque no hubo errores, el patrón existe. En cambio, si ejecutamos grep “Blog” nombreblog obtendremos un estado de salida igual a uno (error general), ya que el patrón solicitado no existe.

Control de salidas:

La salida de los comandos está, mayormente, caracterizada por el uso de stdout y stderr, los dos búferes más importantes en el procesamiento de la salida a la terminal. Si bien algunos ejecutables proveen la posibilidad de agregar un argumento para prevenir que la salida se presente en pantalla, otros no. Por eso, es posible redirigir la salida de estos comandos que no poseen esta característica a “la nada“.

Para los ejemplos a continuación, se utilizará find, pero puede utilizarse cualquier otro comando.

En el siguiente ejemplo, evitaremos que este muestre la salida:

find > /dev/null

También, es posible hacer que la salida normal de un comando, sea enviada a un archivo.

Por ejemplo, el siguiente comando deja un archivo en nuestra carpeta personal conteniendo la salida de find, pero borra el contenido del archivo si este ya existe.

find > ~/registrofind

¿No quieres sobrescribir el archivo? Con Bash, podemos “añadir” contenido a un archivo de texto ya existente. Esto se hace de la siguiente manera.

find >> ~/registrofind

En algunos casos, necesitamos que los errores sean omitidos o redirigidos a un archivo.

Los errores que se presenten al ejecutar un comando, pueden ser omitidos de la siguiente manera:

find 2> /dev/null

O enviarlo a un archivo:

find 2> ~/erroresfind

Quizás, queramos silenciar ambas salidas. Eso también es posible. Observemos el ejemplo a continuación:

find > /dev/null 2>&1

En el comando anterior redireccionamos stdout a /dev/null, silenciándolo(> /dev/null). También, redireccionamos stderrstdout (2>&1), pero, al estar silenciado stdout este desaparece.

Es importante destacar que en ninguno de los ejemplos anteriores el estado de salida del comando ejecutado cambia, sólo estamos silenciando la salida. Por lo tanto, si se dió un error o el comando se ejecutó con éxito, esto será registrado.

 

Otro aspecto que destacar del control de salidas, es que es posible informar al usuario sobre algo que debe hacer manualmente. Por ejemplo, asignar una variable. Pero, si la variable no existe y la llamamos colocando algo como $VARIABLE1 saldría sólo un espacio vacío en el texto. Para esto, es posible hacer strings (cadenas de texo) completamente “muertas”, es decir, que dentro de estas no se aplica procesamiento alguno.

Por ejemplo, imaginemos que la variable RUTAALINICIO no esta asignada.

Si yo colocara la siguiente cadena de texto entre comillas dobles, sería procesada junto a las variables incluidas. Entonces:

echo “Por favor defina manualmente $RUTAALINICIO para que se vea como /home/sunombredeusuario.”

Saldría como:

Por favor defina manualmente  para que se vea como /home/sunombredeusuario.

Una corrección sencilla a esto es escribirlo de la siguiente manera (entre comillas simples):

echo ‘Por favor defina manualmente $RUTAALINICIO para que se vea como /home/sunombredeusuario.’

Que saldría de la así:

Por favor defina manualmente $RUTALAINICIO para que se vea como /home/sunombredeusuario.


Por el momento, esto es todo. Espero haya aclarado un par de dudas entre los usuarios de GNU y/o haya podido ser de ayuda este texto para resolver algún problema en un script o instrucción que los haya estado volviendo locos.

Si notas algún error en este artículo, por favor presionaCtrl+Enter. para notificarnos del mismo

The following two tabs change content below.

Facundo Montero

Un apasionado por la informática y la tecnología en general. De pequeño, me ha gustado muchísimo todo lo relacionado a estos temas, principalmente, la programación. Amo el modding, principalmente relacionado a Android y pronto, me estaré metiendo en el desarrollo de ROMs. Como redactor, podrás encontrar artículos referidos a: Android, programación, modding, informática en general, recuperación de datos encriptación, scripting y demás.