Apuntes mientras aprendo sobre software y computadoras.

Linux

Guía mínima para empezar a programar en bash

Con este apunte doy por iniciada la tarea de aprender a programar en bash.

Y no solamente es un apunte, voy a llamarlo la “Guía mínima para empezar a programar en bash”.

Lo que voy a practicar es bash scripting. Programas creados con el lenguaje para el entorno de la terminal de comandos bash, una terminal común a muchos sistemas GNU/Linux.

Con esto voy a conseguir adentrarme en el mundo de la programación a la vez que profundizo mis conocimientos de GNU/Linux.

Si te interesa seguir leyendo, tal vez puedas acompañarme mientras estudio.

La idea central de mi proyecto: aprender a programar mientras escribo apuntes de los distintos manuales que utilizo para practicar.

Efectivamente, estoy tratando de hacer lo que se conoce como “aprender enseñando”.

¿Y a quién le estoy enseñando? Me estoy enseñando a mi mismo. Se trata de un apunte autodidacta.

Por eso casi siempre intento usar la primera persona al escribir este texto. Se puede pensar también como un juego literario, una narración en primera persona. Leerlo de ese modo ayuda a entender un poco más lo que hago.

Es bueno aclarar que son un principiante en el tema. Toda esta información puede conseguirse en una gran multitud de lugares y escrita por personas que son expertas en programación.

¿Por qué seguir leyendo? Mi apunte es la experiencia de primera mano de un principiante que intenta aprender a programar en bash.

No se que cosas son más o menos importantes.

Con suerte de este modo pueda transformar mi falta de experiencia en una ventaja, encontrando diferentes formas de navegar el material. Puede no ser el mejor texto para aprender bash, pero es el mejor experimento para aprender bash que se me ocurre hacer en este momento.

Si te interesa este proyecto, no olvides escribirme un correo electrónico para contarme tu opinión de mi experimento. Y para corregirme si cometo algún error en las explicaciones o si utilizo un ejemplo que resulta confuso/equivocado.

Este es un apunte que estoy haciendo público, me gustaría que resulte de ayuda para alguien más. Con tu ayuda eso va a ser posible,

Al final del texto aparece una lista con los manuales que utilicé para investigar. También aparece mi dirección de correo electrónico.

Contenidos ocultar

Capítulo 1:
¿Por qué programar en bash?

La verdad es que ya estuve a punto de empezar a estudiar programación unas… cinco o seis veces antes.

Cinco o seis mil veces antes , quiero decir.

Es cierto, no es un número alentador… pero por eso mismo escribo estos apuntes.

Cuando hacemos públicos nuestros proyectos, nos sentimos más comprometidos para completarlos. No es algo que siempre funciona exactamente de este modo, a veces incluso puede tener el exacto efecto inverso.

Pero por esta vez espero poder cumplir con mi proyecto de crear unos buenos apuntes sobre bash.

Entonces sin dar más vueltas…

Mis tres principales razones para empezar a estudiar programación en bash son las siguientes:

  1. Ya estoy aprendiendo a usar un sistema operativo GNU/Linux y su terminal de comandos. ¿Por qué no aprender a usarlo un poco más?
  2. El aprendizaje es directo. Escribir código y usarlo inmediatamente para tareas sencillas.
  3. Programar en Bash puede darme una base para seguir luego con lenguajes más llenos de lujos, perplejidades, aplicaciones y todo eso.

Los bash scripts parecen tener esa mezcla justa entre sencillez y complejidad que lo transforma en algo muy atractivo para mi cerebro.

Por todo eso espero entrar en acción y empezar con bash ahora mismo.

¿Qué es la programación en la shell? ¿Qué son los shell scripts?

La shell es un programa para interpretar comandos. Se ingresan comandos en la terminal y la shell (concha, caparazón, etc.) pasa a ejecutarlos.

Cuando escribo esos comandos, por lo general lo hago uno por uno. Escribo, ejecuto, y luego vuelvo a escribir.

Ejemplo: primero uso un comando para crear una carpeta. Después otro para entrar dentro de la nueva carpeta. Luego otro para crear un documento. Y más tarde uno más para apagar la computadora e irme a dormir.

Sin embargo la shell ofrece un ambiente de programación que cuenta con lo que se conoce como un lenguaje de alto nivel.

En lugar de escribir los comandos del ejemplo por separado, tengo la opción de escribirlos todos juntos dentro de un mismo archivo.

Ese archivo forma un script para la shell. Un shell script.

De este modo puedo combinar los comandos por medio de una sintaxis específica, cosa que con el tiempo me va a permitir construir ideas más complejas.

¿Cuales son las ventajas de los shell scripts?

Las siguientes debilidades/fortalezas son subjetivas. Las comparto porque al menos me ayudan a establecer las expectativas de lo que puedo aprender al estudiar este tipo de programación.

1) Puedo empezar a estudiar inmediatamente

Estoy Utilizando Linux Mint. Linux Mint viene con la shell bash, lo mismo que muchas otras distribuciones de Linux.

Otros lenguajes de programación, como por ejemplo Python, requieren algunos pasos previos antes de empezar.

Ejemplo: Python. Es necesario conseguir un entorno para usarlo, aprender a usar su modo interactivo, usar un interpretador que compile y ejecute el código. Y seguro otros detalles que no estoy teniendo en cuenta.

Los programas para la shell no necesitan ser compilados. Estos scripts son ejecutados dentro de la terminal, que funciona directamente también como su interpretador.

2) Muchos de los razonamientos y sintaxis que se usa en los shell scripts aplican igual para otros lenguajes.

Ejemplo: hay condicionales verdaderos/falso, existen loops y se pueden escribir comentarios para acompañar el código.

Al aprender todo eso una vez tengo una ventaja al empezar a usar otros lenguajes que comparten los mismos conceptos.

3) puede servir para automatizar tareas

Es posible crear programas que interactúen con funcionalidades del sistema. Pero también es posible trabajar con documentos específicos o interactuar con sitios web.

¿Hay alguna desventaja para los Shell Scripts?

Bueno, siendo que recién comienzo a estudiar… realmente no puedo mencionar ninguna desventaja. Todo lo que hay por delante son posibilidades a explorar.

Tengan en cuenta que estoy escribiendo esto principalmente para ocuparme en un pasatiempo interesante, algo que pueda compartir. Eso quiere decir que no me estoy preocupando por detalles como eficiencia de los programas o su popularidad. Tampoco tengo en cuenta la salida laboral que tiene el lenguaje.

Capítulo 2: Mi primer programa en bash

Voy a empezar a escribir mi primer programa en bash. Y como quiero que este apunte sea algo distinto, no va a ser uno de esos clásicps programas “hola mundo”.

Pero antes, algo sobre comandos.

Todo lo que ingreso en la terminal, cualquier input, es un comando. Pero no todos los comandos son interpretados de la misma forma.

Hasta ahora siempre utilicé comandos internos de la shell. Estos también son conocidos como “comandos built-in” y son parte del código interno de la shell.

Ejemplo, escribo pwd en la consola y con eso la terminal me devuelve la dirección del directorio en el que me encuenro trabajando ahora.

pwd
/home/apunte

Hay muchos comandos internos en la terminal. Si quiero saber si un comando es interno o no, lo escribo antecedido del comando type:

type pwd
pwd es una orden interna del intérprete de ordenes (pwd is a shell builtin)

Pero ahora, al trabajar con scripts, estoy utilizando comandos externos.

Los programas que voy a crear son archivos ejecutables que elijo desde el sistema de archivos. Este archivo puede ser un código binario ejecutable, o un archivo de texto al que le di el formato de script.

Luego voy a ver como darle permiso de ejecución a estos programas, y como invocar esos archivos como si fueran un comando más de la terminal.

Voy a ver dos cosas que son comunes a cualquier programa escrito para ser ejecutado en bash:

  • Cómo encabezar un programa para ser usado en bash.
  • Cómo agregar comentarios en nuestro programa.

Encabezar un programa en bash

Un programa no comienza de cualquier manera. Pero antes, tengo que mencionar donde es que voy a escribir ese programa.

Y la respuesta es: en un documento de texto sin formato.

No puedo escribir el código en un procesador de texto común, porque trae detalles extra que van a hacer imposible su ejecución. Esto es interlineado, información en la tipografía y similares.

Si puedo escribir el código en un procesador “plain” o sin formato. Ejemplo: el editor de textodel sistema. También en programas como Vim o Nano.

Voy a utilizar Nano, es muy fácil de aprender.

Digamos que creo un nuevo documento de texto y me dispongo a escribir mi programa para bash.

No puedo comenzar de cualquier manera. Necesito encabezarlo con una linea especial conocida como shebang.

Esta linea especial tiene estos dos símbolos:

#!

Esos dos símbolos comprenden la linea shebang. Pero no termina con eso.

La función de este encabezado es decirle a la terminal el camino hacia el interprete que se encarga de ejecutar el script. Por ejemplo:

#!/bin/sh

La parte /bin/sh es la que expresa el camino simbólico al directorio que cuenta con la shell que va a interpretar el programa.

Se usa el /bin/sh como una forma genérica de significar que esto es un “script para shell”. Si no lo modifico, la shell que abre el programa es aquella que viene por defecto en el sistema operativo.

En mi caso, como estoy escribiendo un script para bash, el encabezado específico va a quedar así:

#!/bin/bash

Pero hay otros ejemplos. Digamos que uso la Korn shell. En ese caso, la shebang va a quedar expresada de otra manera, apuntando a ser interpretado por esa otra shell:

#!/bin/ksh

Agregar comentarios en el script

Quiero agregar texto dentro de mi programa, pero es solamente para aclarar algún concepto. Este texto no es un comando que se va a ejecutar.

Par eso utilizo el signo de numeral o almohadilla (#). Todo lo que va después del numeral va a ser ignorado por la terminal.

# Esto es un comentario al programa

Es cierto que la linea shebang también va detrás de un signo numeral, pero ese caso específico no cuenta como comentario.

Los comentarios pueden ir en distintas partes del código, arriba, al final o acompañando una linea de texto especifica. Ejemplo:

mkdir ejemplo  #necesito recordar hacer ejemplos más originales.

Al revisar algunos archivos de configuración en Linux, es fácil notar esto mismo. Dentro del archivo hay varios comentarios, que se pueden ver ver siempre detrás de signos #, indicando que esperar de cada opción.

Más aun, esta notación del numeral para separar comentarios del cuerpo del código no es único a la programación en la terminal. Es una convención general para otros lenguajes de programación.

Crear mi primer script en Bash

Con las cosas que conozco hasta el momento, esta es la lista de pasos que seguí para crear mi primer programa.

Paso 01: crear directorio y archivo

Lo primero que hago es crear un directorio específico para guardar el programas. En este caso voy a crear esa carpeta en mi directorio personal.

Para eso utilizo el comando mkdir.

mkdir ejemplosbash

Si, el nombre de la carpeta es ejemplosbash. No es un nombre muy creativo, pero va a servir

Ahora entro en la carpeta. Voy a crear el archivo un texto sin formato y a llamarlo script01. Para eso utilizo el comando touch.

touch script01

Perfecto, ya tengo un directorio dedicado a mi experimento de código, y tengo también el archivo base.

Paso 02: abrir el script con Nano

Al fin, luego de tanto, tanto tiempo… programar… esa promesa de éxito, creatividad, dinero infinito…

Perdón, no tengo que distraerme.

Necesito abrir el archivo, y escribir el código. Podría empezar con un editor de textos especializado al estilo de Vim o similares.

Voy a usar el editor de texto nano que es fácil de usar y se ajusta perfectamente a la búsqueda de velocidad que me empuja.

Puede darse el caso.

En caso de no tener Nano en el sistema operativo GNU/Linux, puedo instalarlo escribiendo en la terminal:

sudo apt-get install nano

Luego utilizo nano para abrir el futuro programa:

nano script01

Paso 03: encabezar el script

Para comenzar el script, tengo que encabezarlo. Para eso creo la linea shebang:

#!/bin/bash

Esto indica a la shell el interprete necesario para leer el código. En este caso, el lenguaje es el de la terminal Bash. Ya lo mencioné antes.

Si guardo el archivo y vuelvo a la terminal, puedo inspeccionar el tipo de archivo que tengo. Para eso utilizo el comando file.

Hago entonces:

file script01

La respuesta va a ser algo como:

script: Bourne-Again shell script, ASCII text executable

Me dice que es un script de bash. Esto lo señaliza la linea que recién escribí.

Paso 04: comentar el programa

Voy a ponerle un comentario al programa. Aprendí eso antes.

El código entonces queda de es esta forma:

#!/bin/bash
# Este programa crea un directorio, crea un nuevo archivo y luego me informa que terminó su trabajo.

Pero todavía puedo mejorar este comentario de apertura. Ejemplo: si agrego la fecha en que fue creado, y la última fecha en la que fue modificado. También puedo mi nombre.

#!/bin/bash 

# Fecha de escritura: 08/09/21
# Nombre de referencia: apunteimpensado
# Este programa crea un directorio, un nuevo archivo y luego me informa que terminó su trabajo.

De este modo, si alguna vez en el futuro vuelvo a abrir este archivo, voy a entender rápidamente en que estaba trabajando sin necesidad de ejecutar el código.

Paso 05: bosquejar y escribir

Como puede leerse en el comentario anterior, mi programa no va a ser realmente complejo.

El objetivo del script es correr tres comandos, haciendo una lectura consecutiva de las instrucciones que le doy.

Esto último no siempre es así. No siempre puedo leer un programa de arriba hacia abajo sin detenerme, como si se tratara de un texto narrativo.

A veces el programa lee condiciones, y va moviéndose según las distintas variables.

Pero no en este caso. Este programa va directamente en secuencia, cumpliendo los comandos que le pedimos. No escribimos nada que lo haga chequear variables (ejemplo, no le preguntamos si la carpeta ya existe o no).

Esto va a ir así:

– se crea el directorio: nueva_carpeta

– la terminal entra al nuevo directorio.

– se crea el documento: nuevo_script

– Se imprime en la terminal la frase “el trabajo fue terminado con éxito.”

Todo eso en formato script me queda así:

#!/bin/bash

#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa crea un directorio, crea un nuevo archivo y luego me informa que terminó su trabajo.

mkdir nueva_carpeta

cd nueva_carpeta

touch nuevo_script

echo “el trabajo fue terminado con éxito.”

Guardo el documento y lo cierro.

Paso 06: ejecutar el script

Lo que me falta es darme permiso de ejecución para el script. Para eso tengo que usar el comando chmod. Voy a volver sobre este tema luego, pero para resolverlo rápidamente ahora uso el comando:

chmod +rx script01

Y ahora si, voy a ejecutar el programa.

Me encuentro en la misma carpeta donde empecé a trabajar. Como sigo en el directorio “ejemplosbash” no tengo especificar el camino al programa al momento de ejecutarlo.

Entonces hago:

./script01

Y espero tener algo de tener suerte.

Paso 07: observar el resultado

Bien, dos opciones.

En la primera todo fue como lo esperaba. En ese caso la terminal tiene la frase:

el trabajo fue terminado con éxito.

Sin ninguna clase de error. Al revisar veo que se creó un directorio llamado nueva_carpeta y en su interior hay un archivo llamado nuevo_script.

Lo reviso rápido con el comando ls.

Tengo que recordar que cuando un programa funciona bien, la terminal no da ningún tipo de mensaje. El mensaje que ahora dio de «trabajo terminado» lo agregué a propósitom para saber que el programa terminó de ejecutarse.

Segunda opción: el programa no funcionó correctamente o me devolvió algún tipo de error.

Errores en mi primer programa:

-Problema 01: Al ejecutarlo, la terminal me devuelve:

line 1: !/bin/bash: No such file or directory

Este es el primer error que encontré al empezar a practicar.

Se explica fácil, me faltó agregar el signo numeral delante de la linea shebang.

Problema 02: Mi programa solamente funciona la primera vez que lo ejecuto.

Este programa tiene un gran problema de diseño. Solamente hace lo que se le pide si la carpeta nueva_carpeta no existe en el directorio donde lo ejecuto.

En realidad puedo ejecutarlo todas las veces que se me ocurra, pero la shell solamente puede crear el directorio nueva_carpeta una única vez antes de encontrar problemas. Eso es porque no puede haber dos archivos que compartan el mismo nombre, y no se puede borrar de forma simple un directorio con algo en su interior.

Cuando intento ejecutarlo una segunda vez me devuelve un error: la terminal no puede escribir arriba de un directorio que ya existe.

Pero no pasa nada, voy a tratar de arreglar este defecto un poco más adelante.

Cómo ejecutar un programa escrito en bash

Si, recién terminé de ejecutar un programa. Pero este tema merece un mayor desarrollo.

Permisos del programa

Al invocar un programa, la shell primero lo encuentra en la dirección provista y luego revisa sus permisos. Si la cuenta que estoy utilizando tiene el permiso para usarlo, entonces el programa es ejecutado.

Como con cualquier otro tipo de archivo, hay distintas opciones de permisos. Esos permisos limitan que cosas se pueden hacer con el documento.

Hasta hoy nunca pensé por fuera de los permisos de escritura (w) y lectura (r). Ahora lo que necesito revisar es que el permiso para ejecutar (x) esté habilitado.

Esto es fácil de notar a simple vista. Un archivo es ejecutable si al verlo en la terminal por ejemplo con el comando ls, esta marcado en verde. Un archivo de texto no ejecutable esta señalado en gris.

Pero también puedo revisar un archivo particular con el comando ls -l y averiguar sus permisos. La letra “x” aparece por ejecutable. Si por ejemplo dice rwx quiere decir “se puede leer, escribir y ejecutar”. Si no hay x, hay que darle el permiso.

Claro que es posible también averiguar si un archivo es ejecutable o no dándole clic con el botón derecho del ratón y revisando su descripción, pero la idea de hoy es no abandonar la terminal.

Para darle el permiso de ejecución a nuestro script necesito usar el comando chmod.

Un modo de hacerlo rápidamente, escribo en la terminal:

chmod +rx script01

Eso le agrega la posibilidad de ejecutarlo.

Cómo agregar nuevos comandos en Linux

Vuelvo a pensar en script01, el primer programa que escribí para la shell bash.

En un principio este código solamente puede ejecutarse si el directorio de trabajo en la terminal coincide con el directorio donde el programa esta guardado.

En mi ejemplo, script01 solamente puede invocarse cuando la terminal esta abierta en la carpeta ejemplosbash.

Lo hice escribiendo:

./script01

El problema de eso es que funciona, pero es poco práctico. Si quiero usar este programa para que haga su trabajo en otro directorio del sistema, ahora no puedo hacerlo.

Bueno, en realidad si puedo hacerlo. Pero de otra forma poco práctica. Puedo escribir todo el camino al directorio donde se aloja el programa.

Digamos que la terminal se encuentra en el directorio Desktop. Desde ahí puedo escribir:

/home/apunteimpensado/ejemplosbash/script01

Si escribo todo eso, todo el camino completo o absoluto, puedo ejecutar el programa desde cualquier carpeta. Pero realmente una dirección tan larga es difícil de recordar.

Hay un modo más fácil de hacerlo, y es el que voy a detallar a continuación.

¿Qué busco ahora? Básicamente busco poder ejecutar esto nuevos programas desde cualquier directorio, del mismo modo que hago con los otros comandos del sistema, sin tener que aclarar cada vez que lo hago el camino al directorio donde se encuentra el código.

¿Qué es el command path?

Command Path (o el “camino hacia los comandos”, si me piden mi propia traducción) es una variable de entorno del sistema.

Se trata de un archivo que contiene una lista que le informa a la Shell en que directorios tiene que buscar comandos que se pueden ejecutar.

Cuando invoco un comando, la shell busca ese comando en los distintos directorios definidos en el Command Path. Si el comando esta en uno de esos directorios, la shell puede usarlo.

Por otra parte, si el comando no aparece en ningún lado dentro de la lista, la terminal devuelve un error del estilo “comando no encontrado”.

El beneficio de añadir un script al Command Path es que entonces nuestro programa va a poder ser encontrado desde cualquier directorio, del mismo modo que un comando regular.

Puedo encontrar esta lista desde la terminal con:

echo $PATH

Lo que aparece en la pantalla es una serie de directorios incluidos dentro del archivo path, separados cada uno por dos puntos (:).

De este modo:

/usr/local/bin:/usr/sbin:/usr/bin:

Cuando invoco un comando, el sistema observa en cada una de estas (por ahora tres) carpetas y si lo encuentra pasa a ejecutarlo.

Esta búsqueda comienza por el primer directorio (de izquierda a derecha) hasta llegar al último. No importa si hay más de un comando con el mismo nombre, siempre ejecuta el primero que encuentra.

Lo siguiente es entonces ver cómo agregar nuevos comandos a la terminal de Linux.

El objetivo es agregar mi directorio ejemplosbash a este camino.

Modificar el Command Path

Bien, tengo mi carpeta ejemplosbash. En esa carpeta guardo los scripts que voy creando mientras practico y escribo estos apuntes.

Para editar command path, tengo que dirigirme primero amí directorio personal. Allí voy a encontrar un archivo llamado .profile.

Este archivo esta oculto por defecto, para verlo tengo que hacer click con el botón derecho del mouse dentro de la carpeta y activar “mostrar archivos ocultos” (Show Hidden Files).

Para modificar este documento es necesario abrirlo con un editor de texto sin formato. Puedo editarlo también directamente desde la terminal, usando un programa como Nano.

Ahora tengo que ir hasta el final del documento. Voy a actualizarlo agregando el camino al nuevo directorio, antecedido de:

export PATH=$PATH:/home/directorio

En mi caso este camino queda como:

export PATH=$PATH:/home/apunte/ejemplosbash

La idea es poner esa linea de texto al final del documento, apuntando cada vez al directorio y programa especifico que quiero agregar.

Listo, ya puedo guardarlo.

Para que el cambio tenga efecto es necesario reiniciar el sistema. También puedo utilizar el comando source para que el System Path vuelva a ser leído/actualizado, haciendo que la nueva modificación tome efecto.

Entonces, en lugar de reiniciar abro una nueva terminal y escribo:

source ~/.profile

Ahora cuando utilizo el comando

echo $PATH

Voy a poder notar que la modificación tuvo efecto (o eso espero), y la nueva carpeta anexada aparece al final de la lista.

Lo mejor de añadir nuestro nuevo camino al final del documento es que, como esta lista es leída en orden, me evitamos cometer el error de adelantarnos a una dirección o comando ya existente.

Del mismo modo, lo mejor es que la carpeta y los programas no tengan nombres que sean iguales a comandos ya usados en el sistema.

De todos modos, si algo fue mal y quiero ver cómo era el command path en sus valores originales, puedo utilizar el comando:

source /etc/environment

Ademas ese último comando tiene que restaurar mi terminal actual a los valores originales de command path, al menos hasta que vuelva a cerrarla.

Aunque también es posible conseguir el mismo efecto agregando mi programa en alguno de los directorios que ya existían para esto. Por ejemplo en /usr/local/bin.

Si nada parece funcionar, lo mejor reiniciar el equipo y ver si los cambios surtieron efecto.

Capítulo 3: Parametros, Variables y Valores

El manual de Referencia para Bash (link al final del texto) dice:

A parameter is an entity that stores values. (…) A variable is a parameter denoted by a name.

Y traducido esto es : un parámetro es una entidad que almacena un valor. Y una variable es un parámetro denotado por nombre .

Y hay tres tipos distintos de parámetros: variables, parámetros de posición y parámetros especiales.

En este momento voy a concentrarme en las variables.

Se puede pensar que una variable es un contenedor, como por ejemplo nuestros bolsillos son contenedores. Tengo varios bolsillos, “bolsillo de atrás derecho”, “bolsillo de adelante” y en cada uno guardo diferentes cosas.

Cuando guardo algo en el bolsillo, por ejemplo las llaves, conozco donde tengo que ir a buscarlas. De este modo se que “en el bolsillo de adelante” llevo el elemento “llaves”.

Entonces, puedo decir de modo más coloquial: Una variable es un nombre que hace referencia a un valor.

En el ejemplo ese “valor” es “llaves”, el contenido de nuestro imaginario bolsillo.

Antes de continuar, vale aclarar que es posible probar todos ejemplos que siguen a continuación usándolos directamente en la terminal bash. Otra forma de probarlos puede ser escribiéndolos en forma de script del mismo modo que lo hice antes, aunque eso es un poco más… de cualquier modo, lo importante es comprobarlos para entender en que forma funcionan.

La sintaxis para escribir una variable es:

variable=valor

Nosotros damos el nombre de la variable y al mismo tiempo le asignamos un valor.

Crear una variable es como crear un contenedor, uno que puede llevar en su interior distintas cosas.

Siguiendo nuestro ejemplo:

bolsillo=llaves

En este caso la variable “bolsillo” guarda en su interior un llamado, una referencia, al valor “llaves”.

Esto mismo se puede revisar fácilmente en la terminal. Hay que escribir, sin dejar espacios, una variable y su valor, conectadas por el signo igual. Del mismo modo que en el ejemplo anterior.

Entonces la shell va a guardar la variable bolsillo con el valor llaves.

Ahora para llamar al valor de la variable, tengo la opción de usar el comando echo, y acompañar el nombre de la variable con el signo $

echo $bolsillo
llaves

La terminal me devuelve el valor de la variable.

Entonces lo importante de la sintaxis es:

– No dejar espacio entre el nombre de la variable y su valor, y conectar ambas con un singo igual.

– Para llamar una variable, tengo que anteponer a su nombre el signo $.

Eso último es mucho muy importante. Cuando guardo la variable no utilizo el sigo $. Pero cuando se trae la variable con un comando, cualquier comando, ahí hay que recordar agregarle el signo $ por delante.

A modo de ejemplo, ahora quiero llamar el valor de mi variable dentro de una oración. Queda:

echo “no tengo que olvidar las $bolsillo”
no tengo que olvidar las llaves

¿Y qué sucede si el valor de la variable esta compuesto por más de una palabra?

Para que ese valor funcione, tengo que encabezarlo entre comillas dobles.

bolsillo="llavesdelanaveespacial"
echo $bolsillo
[llaves de la nave espacial]

En lineas generales, al escribir algo que contenga espacios vacíos (o necesita espacios vacíos), utilizar las comillas.

¿Para qué me sirven las variables?

Bueno, las variables me sirven para asociar datos que aparecen en el script. Pueden ser datos existente, o datos que se van a producir más adelante.

Ya escribí la variable “bolsillo”. Y ya conozco que en su interior tiene el valor “llaves”. Pero puedo cambiar por otra cosa, por ejemplo “billetera”.

Me alcanza simplemente modificar la variable para que el cambio se refleje en todo el script.

Por otro lado, las variables me sirven para asociar datos futuros.

Por ejemplo si el programa le pregunta a alguien que adivine el contenido de nuestro bolsillo, puedo contrastar el valor de nuestra variable con la respuesta que recibimos.

Luego nuestro script va redirigir su ejecución basándose en si la respuesta coincide o no en el valor de la variable.

Declarar variables como integers en la shell:

Dado el caso, también puedo especificar que el valor de una de las variables sea tratado como un número y no como texto, por ejemplo para realizar operaciones matemáticas.

El tipo de dato considerado como número (esto es, como número entero) se conoce como integer.

En inglés integer quiere decir entero. Y en matemática los números enteros comprenden a los números naturales (1,2,3…), los naturales negativos (…-3, -2, -1) y también al cero.

Para volver todo esto más claro, veamos todo lo anterior como ejemplos:

Digamos que asignamos:

hola=1
chau=4
resultado=hola+chau

¿Que número me va a dar resultado de la suma entre las variables hola(1) y chau(4)? Voy a averiguarlo:

echo $resultado
hola+chau

Bien, no era lo que yo buscaba. Pensaba más bien en conseguir el resultado de la suma numérica de 1+4=5.

Para conseguir eso necesito usar el comando declare con la opción -i (de integer). En otro momento voy a revisar un poco mejor el uso este comando al escribir sobre las operaciones matemáticas.

Pero para completar, voy a declarar los valores de la variable resultado como números y no como texto.

declare -i resultado=hola+chau
echo $resultado
5

Bien, finalmente pude pasar el valor de la variable de texto a números enteros.

Distintos tipos de variables

– Variables locales

Las variables locales son las que se definen al escribir un script, y están limitadas al script en el que se encuentran.

Este tipo de variables son las que mencioné hasta el momento. Estas variables no guardan su valor por fuera de su contexto inmediato. Por ejemplo si hago:

insecto="abeja reina"
echo $insecto
abeja reina

Y luego hago dentro de la misma terminal:

insecto=hormiga
echo $insecto
hormiga

Puedo darme cuenta que “sobre escribí” el valor anterior de la variable insecto.

Aunque le asigne un valor a una variable dentro de un programa, esa variable esta limitada al contexto del programa y la shell donde es ejecutada.

Para ver esto de vuelta. Si cierro la terminal y abro una nueva, luego hago:

echo $insecto

No recibo ningún valor. Se perdió cuando cerré la primera terminal.

– Variables globales

Las variables globales también pueden encontrarse escritas con su nombre en inglés, como enviroment variables.

Son las variables que el sistema tiene disponible desde su configuración, disponibles para el uso de cualquier script en la shell.

Una ejemplo de variable global por ejemplo es HOME, y el valor que guarda es el nombre del directorio personal al que apunta la cuenta que ahora personalmente me encuentro utilizando.

Por ejemplo, si le pido a la terminal que me muestre el valor de la variable HOME, obtengo el camino a mi directorio personal. En mi caso:

echo $HOME
/home/apunte

Hay muchas variables globales, y puedo ver un listado completo usando el comando env y también usando el comando printenv.

¿Es posible modificar las variables globales de mismo modo que hice antes alterando el valor de las locales?

Es posible modificar también las variables globales. Solamente hay que sobre escribirlas. Yo lo hice para ver que pasaba.

El problema (o la ventaja) es que los cambios que ocurren en las variables globales no se guardan. Por ejemplo al modificar el valor de la variable HOME, luego de reiniciar la terminal el cambio de valor tampoco queda registrado.

Aun así, tengan en cuenta que soy un principiante, hablo de mi propia experiencia tratando de alterar estas variables. No puedo asegurar que no se pueda terminar rompiendo algo.

– Variables especiales

Hay algunas variables que son especiales.

De algún modo son similares a las variables globales, en el sentido que ya tienen un valor asignado de antemano. Pero no son iguales.

La diferencia esta en que las variables especiales obtienen su valor según el momento y el contexto donde las voy a utilizar. Su valor no es único, depende del uso que se hace de la variable.

Por ejemplo a la variable especial 0 le corresponde como valor el nombre del programa especifico que estás usando (o al que le hacés referencia con su uso). Si, el nombre de la variable es 0.

Por ejemplo, si recupero el valor de esa variable desde mi terminal consigo:

echo $0
bash

Y es cierto, bash es el programa que estoy usando. Puedo ver esto con otro ejemplo.

Escribo rapidamente un nuevo programa llamado ejemplo500 y el contenido del script es el siguiente:

#!/bin/bash
echo El nombre de este programa es $0

Y luego lo ejecuto en la terminal. El resultado que obtengo es:

El nombre de este programa es ./ejemplo500

Entonces la variable 0 corresponde siempre al valor del nombre del programa que se encuentra en uso.

Este es un listado de las variables especiales:

Nombre de la variableValor asignado
$*El valor de todos los argumentos.
$!PID del último background process.
$?Estatus de salida del último child process.
$$PID del proceso activo en este momento.
$#Cantidad del número de comandos.
$0Nombre del programa

Parámetros de posición (positional parameters) en el script

Si te digo la verdad, tardé bastante en entender como funcionaban estas variables… pero creo que ahora lo tengo. Admito que eso último no da mucha confianza.

Estoy hablando de parámetros de posición, contienen lo que se conoce como valores o argumentos asignables dentro del script.

Para acceder a los distintos argumentos de un script puedo valerme de las variables $1, $2, $3 y así hasta llegar a $9.

No se puede usar $0, porque responde a una variable especial.

Me gustaría poder decir también que el valor máximo al que llega mi billetera supera los $9, por lo general llevo mucho menos que eso.

Cuando vuelvo a leer los renglones anteriores, creo que es de las cosa m{as confusas que escribí hasta el momento. Qué son las variables de posición va a quedar más claro con un ejemplo… o eso creo.

Tengo un script llamado posicionar y el mismo esta escrito de la siguiente manera:

#!/bin/bash
echo este script agrega $1 a modo de $2
echo y gracias a eso consigo $3
echo $4

Si tan solo lo ejecuto obtengo:

./posicionar
este script agrega y
y gracias a eso consigo

Pero digamos que al momento de ejecutarlo en la terminal, agrego algunos argumentos, de este modo:

./posicionar palabras argumentos terminarlo saludos

El resultado es que al ejecutarlo, cada palabra toma la “posición” del valor asignado en el programa ($1, $2…) de este modo:

./posicionar palabras argumentos finalizar saludos
este script agrega palabras y argumentos
y gracias a eso consigo terminarlo
saludos

Las variables que dejé en el programa adquieren los argumentos que escribí al ejecutar el script.

Pero como dije antes, solamente se puede acceder a nueve argumentos de este modo. Ese es el límite. Para tener más argumentos extra, hay que utilizar el comando shift.

El comando shift remueve el primer argumento y lo convierte en el segundo, el segundo se convierte en el tercero, y así sucesivamente (convierte a $1 en $2, $2 en $3 y así sucesivamente).

Por ejemplo, este es mi nuevo programa llamado demostrar. El contenido del mismo es:

#!/bin/bash
echo este es el primer $1
echo y también el nuevo $1

Se ejecuta como:

./demostrar argumento ejemplo
este es el primer argumento
y también el nuevo argumento

La palabra «argumento» se repite dos veces, y la palabra «ejemplo» no entra. Pero si escribo el programa como:

#!/bin/bash
echo este es el primer $1
shift
echo y también el nuevo $1

Se ejecuta de este modo:

./demostrar argumento ejemplo
este es el primer argumento
y también el nuevo ejemplo

Si me fijo bien ahora puedo ver como la posición $1 de la segunda oración se modificó por $2 al usar el comando shift.

Manipular el contenido de las variables

Según entiendo, todo este tema se resumen a eso: manipular el texto o valor que guarda una variable.

Anteriormente experimenté que podía crear una variable, a la que le asignaba un parámetro. Por ejemplo:

insecto=ABEJA

La variable insecto guarda en su interior el nombreABEJA. Si uso el comando echo sobre la variable, puedo recuperar su valor.

echo $insecto
ABEJA

-Intercambiar mayúsculas y minúsculas

Así que, primero que nada puedo modificar la forma en que el valor de un parámetro es representado.

Intercambiar entre mayúsculas y minúsculas puede resultar beneficioso si necesito darle formato rápido a un texto. No es un mal truco para tener a mano… aunque en un primer momento no se me ocurran muchos usos para esto.

Voy a recuperar la variable anterior:

insecto=ABEJA

Lo que voy a hacer ahora es encerrar la variable entre corchetes {} al momento de llamarla. En inglés estos corchetes se conocen como curly brackets.

Yo simplemente los conozco como corchetes, pero a partir de ahora voy a llamarlos corchetes redondeados para no confundirlos con los corchetes rectos. Seguramente tienen un nombre oficial, pero este nombre ¿provisorio? va a servir para salir del paso.

Al agregar estos corchetes redondeados cuando invoco la variable (ademas del signo $ que ya estaba usando), voy a poder acceder a los parámetros de expansión en Bash.

Por ejemplo, digamos que me interesa convertir toda la palabra ABEJA a letras minúsculas.

Entonces:

echo ${insecto,,}
abeja

Lo que hago es agregar ,, (dos comas) luego de la variable. El resultado es que ahora el valor esta completamente en minúsculas.

Algo realmente importante es que el valor original de la variable no fue alterado, simplemente fue representado de manera distinta.

Puedo volver a invocar la variable retirando las dos comas, y el parámetro original sigue igual. Lo que significa que puedo hacer modificaciones sin miedo a perder o borrar el dato original.

echo ${insecto}
{ABEJA}

Pero esto de las minúsculas tiene otro variante. Si utilizo solamente una coma en lugar de dos, entonces solo la primera letra es modificada.

echo ${insecto,}
{aBEJA}

Ahora solo por el gusto de completar la idea, voy a probar con las mayúsculas. El concepto es el mismo. Pero en lugar de utilizar comas utilizo el símbolo caret o “carrot”, porque suena a zanahoria en inglés.

¿Cual es este símbolo? Es ˄ y se parece a una flecha apuntando hacia arriba.

¿Cómo encontrar este símbolo en el teclado? Realmente no tengo idea. Cuando lo necesito por lo general lo que hago es buscarlo como un carácter especial en LibreOffice Writer (aparece como circumflex accent), y luego lo pego en la terminal.

Igual que antes, un solo símbolo caret convierte la primera letra en mayúscula. Dos símbolos caret antes del corchete redondeado convierten todo en mayúsculas.

Visto en un ejemplo rápido, puedo poner en la terminal alternativamente:

insecto=abeja
echo ${insecto^^}
ABEJA

– Conocer el largo de una variable

Otra cosa que puedo obtener es el largo de una variable. Esto me permite conocer por cuantos caracteres esta integrada.

Lo hago de esta forma, retengamos nuestra variable insecto=abeja que tanta ayuda ya me ha prestado.

Echo ${#insecto}
5

Lo que hice fue poner el símbolo numeral al expandir la variable. En este momento bash me entrega un número, que me cuenta la cantidad de caracteres en la palabra abeja.

El índice y la expansión de parámetros

La expansión de parámetros me permite trabajar con solamente una parte de los datos que guarda la variable.

Dividir en porciones o hacer “slicing” me permite tomar una sección de la variable. Algo valioso cuando solamente necesito una parte de la información… aunque tampoco se me ocurran mejores usos para esta herramienta ahora mismo.

Pero antes, es una buena oportunidad para traer la idea de índice a este apunte.

Tengo por ejemplo la palabra abeja. Si me olvido de su sentido y me quedo solamente con la palabra, me encuentro con en una serie de letras individuales puestas en orden.

Por lo general una pieza de texto se conoce como una “string”, que en ingles significa hilo. Una palabra es como un hilo de palabras consecutivas. Lo mismo una oración, un párrafo o un texto completo.

Cada letra (o elemento) en una string tiene su propio número dentro de la secuencia. Esto se conoce como el index o índice.

Este índice no empieza desde uno, comienza con cero y luego se va incrementando en uno por cada elemento de la string.

Por ejemplo en la palabra abeja, si la pienso asociada a la idea de índice me queda:

a (0) b (1) e (2) j (3) a (4)

Ocurre lo mismo al trabajar con números, siempre que sean parte de una string. Dentro de una serie de números, el primer elemento también corresponde al índice cero.

De este modo, por ejemplo quiero solamente los caracteres que van entre el índice 1 y el 3 incluidos hago

echo ${insecto:1:3}
bej

Y si quisiéramos ver solamente el indice 1 puedo probar con algo como:

echo ${insecto:1:1}
b

Básicamente, lo importante es recordar que al utilizar el signo $ y los corchetes redondeados {} se puede acceder a nuevas opciones en las variables.

Capítulo 4: sustitución de comandos y otros detalles de interés

La sustitución de comandos (command substitution) me sirve para guardar el output de un comando como una variable. La idea por detrás es que luego voy a poder usar esa variable como parte de un nuevo comando diferente.

Puedo pensar que la sustitución de comandos es similar a la expansión de parámetros en la shell.

Y es muy similar si la vemos escrita. Para expandir la variable la invocábamos de este modo, utilizando corchetes redondeados:

${variable}

Utilizando el operador ${} para envolver la variable.

Eso era con las variables.

En este otro caso ocurre algo similar, solamente que ahora enmarcamos al comando utilizando el operador $() de este modo:

$(comando)

Alternativamente puedo usar comillas invertidas (backticks) para encerrar al comando de esta forma:

$`comando`

Pero… ¿para qué complicarme? En lo que a mi respecta voy a usar los paréntesis, es lo bastante sencillo como para no tener que olvidarlo.

En resumen, la sintaxis para una sustitución común es la de usar unos paréntesis para englobar el comando.

Según entiendo, hay al menos dos formas de utilizar la sustitución de comandos.

Guardar el resultado de un comando como una variable

Un detalle de la sustitución de comandos es la oportunidad de guardar un comando como una variable, lo que me permite volver a utilizarlo luego en subsiguientes partes del código.

Para probar esto voy a crear un nuevo script desde cero. No es buena idea probar la sustitución de comandos directamente desde la terminal porque esta herramienta no afecta el estado de la terminal actual. En lugar de tener resultados diferentes, siempre obtendría el mismo resultado.

Entonces voy a crear un nuevo programa, aunque uno muy sencillo.

Primero voy a crear una nueva variable, del mismo modo que ya venia haciendo. Puede notarse que la mayoría de los temas se van solapando, conectándose entre si.

Lo que me interesa es que el valor de esta variable sea un comando, para usar luego su resultado de otro modo.

Entonces voy a crear la variable lugar con una expansión para el comando pwd:

#!/bin/bash

#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa te informa en que directorio se encuentra trabajando la terminal.
lugar=$(pwd)
echo "en este momento te encontrás trabajando en $lugar no vayas a perderte"

Luego guardo el programa y le doy permiso de ejecución, agregándolo al Command Path.

Al ejecutarlo, si todo funciona bien, voy a tener como resultado la frase:

en este momento te encontrás trabajando en /home/apunte no vayas a perderte

El comando pwd muestra el directorio en el que estoy trabajando. Pero no solo eso, el programa le agrega una frase para acompañar la información.

Por la sustitución de comandos, la variable lugar es igual al resultado del comando pwd.

Esto es muy útil. Por ejemplo en este caso, puedo usar mi programa en diferentes carpetas. En cada carpeta voy a recibir una respuesta ligeramente diferente, según el directorio en el que estoy ejecutando el script.

Al guardar el output de un comando como una variable, el valor de la variable se va a modificar según el resultado del comando. Esto le agrega gran versatilidad al programa.

Correr comandos dentro de comandos


Esta es otra forma de entender la sustitución de comandos: bash permite ejecutar un comando (o varios) y utilizar su salida dentro de otro comando.

Puedo ver esto en un ejemplo.

Digamos que quiero crear un archivo llamado sonidos, y abrirlo en el editor de texto Nano.

Primero hago uso del comando touch para crear el archivo, y luego lo abro con Nano, de este modo:

touch sonidos
nano sonidos

Pero por medio de la sustitución de comandos, puedo anidar las dos acciones en una sola:

nano $(touch sonidos)

De este modo bash crea el archivo sonidos con el comando touch. Luego utiliza la salida de ese comando, el ya mencionado archivo, y lo usa como argumento para el comando siguiente.

Entonces nano abre el nuevo archivo.

Es importante recordar que puedo sustituir múltiples comandos en un mismo movimiento, “englobándolos” uno dentro de otro.

Según entiendo, formalmente esto se ve de un modo similar a esto:

comando3 $(comando2 $(comando1))

Voy a tratar de leer esto en “modo humano”. Primero se ejecuta el comando1 y luego el argumento de salida de ese comando pasa a ser usado por el comando2. El output de este segundo entonces es utilizado por el comando 3.

Otra cosa a tener en cuenta antes de terminar.

La sustitución de comandos no afecta la terminal actual, cada comando anidado se ejecuta en una “sub-terminal”. Por eso, por ejemplo, si intento usar el comando cd para cambiar directorio en una sustitución dentro de la terminal, el directorio no va a cambiar.

Programar en la shell: pipelines, entradas y salidas

Estas son otras dos características muy útiles de bash: el de las pipelines, y el de las entradas y salidas.

Es importante tener en cuenta que estos dos aspectos de la terminal no solamente sirven al momento de programar. Casi cualquier comando puede verse enriquecido por su uso.

Así que sin más más vueltas:

Pipelines

La shell me permite crear pipelines.

Esto es, hacer que el output (el resultado) de un primer comando se transforme en el input (el dato para ingresar) en un segundo comando.

De este modo podríamos decir que la información viaja de un comando a otro.

Visto desde afuera, lo que voy a ver es algo así como “dos comandos encadenados” por un símbolo. En la terminal queda así:

comando 1 | comando 2

El símbolo | (barra vertical o pleca) es el que utilizo para crear una pipeline.

Para verlo con un ejemplo, primero voy a ver el contenido del directorio “/fotos” con el comando ls. Y luego voy a redirigir el primer resultado (los nombres de los archivos en /fotos) al comando “sort -R” para que me entregue una lista de los archivos en la carpeta, desordenada al azar.

Esto se puede escribir y ver directamente en la terminal.

ls fotos | sort -R

Es cierto, tal vez no sea el ejemplo más práctico… pero no pueden negar que funciona para ver lo que esta pasando.

Vale agregar que puedo usar múltiples pipelines seguidas, encadenando la entrada y salida de información entre múltiples comandos.

Modificar la dirección de entrada y salida de los datos

La shell me da la posibilidad de cambiar la dirección de entrada y de salida de la información.

¿Cómo entender esto? Puedo tomar un comando, y por ejemplo mover los datos que genera o sus mensajes de error hacia otro lugar.

De este modo, un resultado que normalmente aparecería directamente en la terminal ahora puede enviarse a otro lugar.

En la práctica se ve algo así:

Comando 1 > Comando 2

El símbolo > es el que usamos para conseguir mover la información. Digamos que “apunta como una flecha” en el sentido en que van a moverse los datos.

En un ejemplo básico, voy a conseguir el listado de elementos en un directorio llamado “/fotos” con el comando ls. Luego voy a poner ese resultado en un nuevo archivo llamado “lista.txt”:

ls /fotos > lista.txt

Y listo, con este breve ejemplo tengo una rápida muestra de esta poderosa habilidad que me presenta la shell Bash.

Capítulo 5: Clausula de condición en la shell bash

Creo que primero puedo hacer una definición ultra rápida basada en lo que entendí del tema: los condicionales le ponen condiciones a los comandos.

Es como poner una condición en la vida diaria antes de elegir llevar o no adelante una acción.

Por ejemplo “si me va bien en el examen voy a bailar una danza extraña” o “si me pongo a cocinar ahora voy a poder permitirme leer un libro un poco más tarde”.

Es mismo, pero volcado dentro del lenguaje programa.

Si ocurre tal condición, entonces ocurre tal comando. Si ocurre tal otra condición, en ese caso voy a usar o verificar otro comando.

Algunos pensamientos sobre condicionales (conditionals)

Ahora voy a pasar a mencionar algunas ideas al azar que se me ocurrieron al momento de ponerme a estudiar sobre esta herramienta, los condicionales en la programación.

Si estos pensamientos sirven de algo seguro puedo hacerme entender mejor más tarde. No son tantos pensamientos… solamente dos.

1 – Este es el punto donde todo empieza a volverse algo más difícil.

Por separado cada una de las partes tiene sentido. Variables, sintaxis, condicionales todo puede explicarse por separado. Pero luego todo empieza a sumarse y se vuelve complejo.

Tal vez dicho de otro modo: se empieza a apreciar el arte del asunto. A veces algo puede resultar fácil de entender, pero es difícil de llevar correctamente a la práctica.

– Traducir entre idiomas es un problema. Quiero decir, pasar del inglés al castellano y a la inversa. Un ejemplo básico de esto es el condicional if. Al momento de programar es necesario escribirlo en inglés. Sin embargo… ¿cómo traducirlo al castellano? voy a verlo ahora mismo

El condicional if

Mi traducción literal de la palabra if es: si. Según el diccionario Wordreference, la definición que busco es la siguiente: “conj. cond. Que introduce la prótasis de la clausula condicional.

Ejemplo de una clausula condicional:Si ocurre una cosa, entonces luego hago esto otro.

De ahora en adelante utilizo if, tengo que pensarlo como un si. La palabra if entonces pasa a abrir el condicional, pero no termina ahí.

El condiciona if se usa en conjunto con then para cerrar el sentido de la oración. La traducción literal de la palabra then es: entonces.

Ya puedo entender como queda la oración completa:

Si ocurre algo, entonces hago esto otro.

Con esto creo que es fácil entender, al menos en la teoría, en que forma se piensan los condicionales.

Voy a revisar esta misma lógica, pero ahora pensándolo para la terminal. Por ejemplo, pare decir “si variable A ocurre entonces invocar al comando B”

si variable A
entonces
comando B

Pero no puedo dejarlo escrito de esta forma. tengo que traducirlo para la shell. El condicional en el programa se consigue con un bloque de esta forma:

if variable A
then
comando B
fi

El bloque del condicional se cierra con la palabra fi.

El fi cierra el bloque iniciado por el condicional if. De alguna forma es casi poético.

Pero en todo caso, entre if y fi se pueden agregar los comandos que necesite.

¿Cómo se lee lo que escibí con este condicional?

La shell revisa la variable A que aparece seguida del condicional if para ver si es verdadera. En Linux, un comando exitoso regresa cero.

Si A regresa cero al salir (es exitoso), entonces la shell ejecuta el comando B que aparece luego del condicional then.

El bloque termina con la palabra fi.

¿Qué ocurre entonces si el comando que sigue después de if no es exitoso? (Da una respuesta distinta a cero: no es verdadero, resulta en un error, etc).

Entonces en ese caso…

El condicional else

La palabra else la puedo entender como “si no”.

En conjunto con el comando if puedo usar el comando else. Si el comando que sigue a if no resulta exitoso o verdadero, la shell va a correr lo que sigue luego de else.

Un ejemplo en nuestro lenguaje cotidiano quedaría mas o menos así:

“Si gano un premio, entonces voy a comprar libros de bailes extraños. Si no gano un premio, voy a salir a tomar un café.”

Un ejemplo algo extraño, pero creo que se entiende el punto.

Visto desde nuestro programa en bash, esto quedaría:

if variable A
then
    comando B
else
    comando C
fi

Entonces básicamente, la shell revisa la variable A.

Si la variable A es exitosa, entonces ejecuta el comando B.

Pero si el comando A no es exitoso, bash pasa a ejecutar el comando C.

Siendo que cada comando se ejecuta en orden, else va al final. Si la primera parte del bloque no fue exitosa, la shell revisa el comando luego de else.

El condicional elif

El condicional elif no es otra cosa que una combinación de if y else en una sola palabra clave.

Lo que hace es agregarle otra rama en el “árbol” de condicionales. Si el comando que sigue a if no resulta exitoso, entonces elif actuá como un “pero si”.

En nuestro lenguaje cotidiano, podría ser algo como: “Si apruebo el examen, entonces hago una fiesta. Pero si no apruebo el examen y si consigo un trabajo, entonces compro libros de danzas extrañas.”

Otra vez, no hay que concentrarse en lo extravagante del ejemplo, solo en su sentido.

Probemos escribirlo en un lenguaje para la shell

if variable A
then
    comandoB
elif variable C
then
    comando D
else
    comando E
fi

¿Cómo se lee esto?

La shell revisa la validez de la variable A, y si tiene éxito entonces ejecuta el comando B.

Si el comando A no tiene éxito, entonces revisa la validez de la variable C. Y si tiene éxito pasa entonces al comando D.

Si todo lo de arriba no funciona, entonces pasa a la clausula luego de else y termina.

Con estos condicionales posemos probar condiciones. Si ocurre tal cosa, luego puedo hacer esta otra.

En principio, con eso ya puedo hacer un programa más complejo. Antes en mi programa original los comandos se iban ejecutando uno detrás del otro. Ahora,una vez más en teoría, puedo ejecutarlos según distintas ramas de eventos.

¿Como hago para saber si una de las condiciones es verdadera o falsa?

Voy a pensarlo con un ejemplo: si hoy llueve, entonces voy a dibujar un insecto. Todo muy lindo, ahí esta el condicional pero… ¿Cómo hago para saber si llueve?

Hay quien puede decir que la respuesta es mirar por la ventana. Si cae agua desde las nubes, hay chances de que sea lluvia. O tal vez sea más seguro revisar el pronóstico del tiempo.

Pero en bash, testear las condiciones por verdadero o falso tiene otro lenguaje.

Tengo que recordar que “verdadero” es true y “falso” es false.

Capítulo 6: Cómo testear condicionales en la shell

El comando test

Test es un comando para evaluar expresiones.

Si la expresión es verdadera regresa un cero y si es falsa regresa un número distinto (o digamos, regresa un “no cero”).

Dicho de una forma más directa, test evalúa si algo es verdadero o falso. Es como revisar el pronóstico del clima cuando nuestra proposición condicional era “si hoy llueve, entonces voy a dibujar un insecto”.

Este comando tiene tres formas básicas de uso, dependiendo de eso que estoy evaluando.

Puedo usarlo para evaluar texto (strings), para evaluar números (integers) y para evaluar archivos.

Un ejemplo rápido, test puede revisar si un archivo existe o no. También si un archivo esta vacío o no. O si un archivo permite que se escriba en el o no.

Antes de avanzar, algunos ejemplos de comparación con test. Luego voy a verlo más claro en algunos ejemplos prácticos.

Si hay algo que me estoy dejando afuera, se puede revisar con mayor tranquilidad escribiendo en la terminal:

man test

(Voy a hacer lo posible por poner cada opción en ingles y en castellano, para que resulte más sencilla la lectura).

Opciones para comparar archivos (files) con el comando test:

OpciónNombreUsoResultado
-aExiste (Exists)-a $archivoRegresa Verdadero si el archivo existe. (Returns True is the file exist)
-rPuede ser leido (Readable)-r $archivoRegresa Verdadero si el archivo puede ser leido. (Returns True if the file is readable)
-wPuede ser escrito (Writable)-w $archivoRegresa Verdadero si el archivo puede ser escrito. (Returns True if the file is writable)
-xPuede ser escrito (Executable)-x $archivoRegresa Verdadero si el archivo es ejecutable. (Returns True if the file is executable)
-fArchivo regular (Regular File)-f $archivoRegresa Verdadero si el archivo es regular. (Returns True if the file is a regular file)
-dDirectorio (Directory)-d $archivoRegresa Verdadero si el archivo es un directorio. (Returns True if the file is a directory)
comparar archivos (files) con el comando test

Opciones para comparar lineas de texto (strings) con test:

OperadorNombreUsoExplicación
-zEs un espacio vacío (zero length)-z $aRegresa Verdadero si $a es un espacio vacío. (Returns True if the a is zero length string)
-nNoes un espacio vacío. (zero length)-n $aRegresa Verdadero si $a no es un espacio vacío. (Returns True is the a is a non-zero length)
=Igual. (Equal)$a = $bRegresa Verdadero si $a es igual a $b. (Returns True if $a and $b are same strings)
!=Distinto. (not equal)$a != $bRegresa Verdadero si $a es distinto a $b. (Returns True is a and b are different strings)
comparar lineas de texto (strings) con test

Opciones para comparar números (integer) con el comando test:

OpciónNombreUsoResultado
-eqIgual (equal)$1 –eq $1Regresa Verdadero si $1 y $1 son iguales. (Returns True if $1 and $1 are equal)
-neDistinto (not equal)$2 –ne $1Regresa Verdadero si $2 y $1 no son iguales. (Returns True if $2 and $1 are not equal)
comparar números (integer) con el comando test


Bueno, a simple vista todo lo del cuadro anterior puede resultar mareante. Puedo decir que a mi me resulta algo mareante.

En general lo mejor va a ser no concentrarme en memorizar todas estas opciones, y simplemente buscar la que resulte pertinente para comparar un condicional específico.

Comparar texto:

Cuando le doy un valor a una variable, estoy utilizando el signo igual de este modo:

insecto=hormiga

Sin separación entre la variable, el signo igual y el valor.

Por otra parte, cuando comparo dos piezas de texto (strings), utilizo también el signo igual pero de otro modo:

test a = a

Al usar test necesito dejar espacios entre el signo igual, el valor y la variable. Un ejemplo de programa usando esto me queda de este modo:

#!/bin/bash

#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa revisa si el string a es igual al string a y me devuelve una de dos posibles respuestas.

if test a = a
then
        echo “los dos textos son iguales.”
else
        echo “los dos textos son distintos.”
fi

Comparar números:

Cuando llamo a la shell para comparar dos números, la sintaxis para probar si son iguales no es como la que recién utilicé para el caso del texto.

Antes puse el signo = (igual) para buscar la paridad entre textos. Pero no va a servir para buscar la paridad entre números. O al menos no puedo hacerlo con la cantidad de conocimientos que tengo hoy.

En este caso lo que su puedo usar es la opción -eq para llegar al resultado, en lugar de usar el signo igual:

#!/bin/bash
#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa revisa si el número entero (integer) 1 es igual al integer 1 y me da una respuesta. 

if test 1 -eq 1
then
    echo “los números son iguales.2
else
    echo “los números son distintos.”
fi

Comparar variables

Voy a ver si una variable es verdadera o falsa. Otra vez, usemos una variable con insectos:

#!/bin/bash
insecto=hormiga

El condicional puede leerse como:

if $insecto = hormiga
then
     echo “hormiga es un valor de la variable insecto”
else
    echo “la comparación es falsa, ese valor no pertenece a la variable.”
fi

No tengo que olvidar el signo $ al llamar la variable.

Si la variable es verdadera, entonces ejecuta el comando echo para escribir “hormiga es un valor de la variable insecto”.

Caso contrario pasa al comando echo para imprimir “la comparación es falsa, ese valor no pertenece a la variable.”.

Entonces el condicional if revisa si la variable insecto es igual a hormiga. Podría no serlo, por ejemplo la variable original podría ser fácilmente “insecto=auto”, por lo que la comparación “insecto = hormiga” resultaría falsa.

Pero hasta ahora siempre estuve utilizando variables que conozco como verdaderas. Yo mismo los estuve creando. Sin embargo el valor de una variable puede tomar muchas formas, de hecho puede aceptar nuevos valores en el transcurso del programa.

Solo para asegurarme de que la comparación de la variable realmente esta ocurriendo, voy a probar que me devuelva un resultado falso:

#!/bin/bash
#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa revisa que la variable sea verdadera, y devuelve una respuesta acorde. 

insecto=hormiga

if $insecto = auto
then
    echo “auto es un valor de la variable insecto” 
else
    echo “ese valor no pertenece a la variable insecto”
fi

Si todo fue bien, la respuesta del programa tiene que ser que la comparación de la variable es falsa, porque ese valor no pertenece a la misma.

Comparar archivos

Bien, dejé para el final tal vez una de las funcionalidades más poderosas del comando test. La de comparar archivos.

Voy a mejorar brevemente nuestro programa original. Voy a traerlo de vuelta.

En un principio tenía que:

#!/bin/bash

#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa crea un directorio, crea un nuevo archivo y luego me informa que terminó su trabajo.

mkdir nueva_carpeta

cd nueva_carpeta

touch nuevo_script

echo “el trabajo fue terminado con éxito.”

El programa funciona bien, pero solamente la primera vez. Solamente funciona cuando el directorio “nueva_carpeta” no existe.

Luego me devuelve directamente un error, dando a entender que la carpeta existe y no puede sobre escribirse.

Tal vez hoy no tengo las herramientas para hacer un programa verdaderamente complejo, pero si puedo volverlo más elegante el que ya tengo agregando una comparación.

En conjunto, el programa terminado queda como:

#!/bin/bash

#Fecha de escritura: 08/09/21
#Nombre de referencia: Gustavo
#Este programa crea el directorio “nueva_carpeta”, entra en la carpeta y luego crea el documento “nuevo_script”.Luego me informa que terminó su trabajo.

if test -a nueva_carpeta
then
    echo la carpeta ya existe, no es necesario crearla.
else
    mkdir nueva_carpeta
fi
cd nueva_carpeta

touch nuevo_script

echo “el trabajo fue terminado con éxito.”
exit 0

No tengo que olvidar darle al programa un permiso para volverlo ejecutable.

Si todo sale bien, lo primero que bash va a comprobar es el comando junto a if para ver si realmente existe un archivo llamado nueva_carpeta.

Cuando la condición el archivo existe se cumple (el test es verdadero) entonces va a pasar a imprimir la leyenda “la carpeta ya existe, no es necesario crearla”.

Por otra parte, si el archivo no existe entonces el programa va a pasar a la clausula else y va a correr el comando mkdir para crear el documento.

Luego el programa sigue, entra en el nuevo directorio y crea un nuevo documento.

Ahora, con esta bifurcación en el programa, contemplando dos posibilidades distintas: la existencia o no de una carpeta específica.

Estatus de salida

Al final del programa escribí la linea de código exit 0. Eso es nuevo, no estaba en la primera versión del script.

¿Qué significa el comando exit? Este comando da por terminado el programa. A veces me conviene que el exit se encuentre al final, pero puedo ponerlo en cualquier parte. Por ejemplo, un exit puede terminar una de las ramas de las proposiciones condicionales.

Puedo entonces darle al script la opción de terminarse si una condición no se cumple.

Este tema es importante porque se conecta con el tema de los estatus de salida (exit status).

Este tema tiene varias partes, pero quiero mantener el apunte al mínimo así que me conformo con saber porque le agregué un cero al comando exit.

Hay muchos estatus de salida, pueden ir entre el número 0 y el 255.

En lo que a mi respecta, me alcanza con saber que si al terminar de ejecutarse el programa la terminal me devuelve el estatus 0, quiere decir que el programa pudo ejecutarse sin errores.

Dicho de otro modo, yo le pedí al programa que me “devuelva” un número 0 como prueba de que se leyó hasta el final.

Si yo no especifico un número de estatus acompañando al comando exit, el estatus de salida me lo va a dar el último comando que se ejecutó. Si, los comandos tienen sus propios códigos de salida.

Distintos comandos pueden dar distintos códigos. Al leer el comando de un manual especifico, puedo enterarme de que significa cada posible código de error.

Por ejemplo, el comando ls tiene tres estatus de salida:

0 OK,
1 Algún problema menor (ejemplo, no puede acceder a subdirectorios),
2 Algún problema serio (ejemplo, no puede acceder a argumentos de la linea de comandos).

Entonces 0 por lo general quiere decir que el comando se ejecuto correctamente.

En programas más complejos, pedir los estatus de salida de las distintas partes del script me va a ayudar a encontrar donde se encuentra el error y cual es.

Yo le pido a mi programa exit 0 porque quiero que me de un aviso de que se ejecuto correctamente.

[] Una alternativa a test

Hay otro modo de conseguir averiguar si una variable es verdadera o falsa. Y es por medio del comando [].

Si, el comando que tengo que usar es [] .

Supongo que puedo llamarlo corchetes rectos, para diferenciarlos de los redondeados. O simplemente puedo llamarlo [].

Pero abandonando la broma, hay un link simbólico entre el comando test y este otro.

Entonces puedo decir que [] es otra forma de escribir test.

voy a verlo en acción, transformando el ejemplo anterior.

#!/bin/bash

if [ a = a ]
then
        echo los dos textos son iguales
else
        echo los dos textos son distintos.
fi

Y listo, con eso lo tengo. Reemplazamos el comando test por [] y consigo el mismo resultado.

Ahora bien, hay que tener cuidado con los espacios.

Hay que dejar espacio entre los corchetes y la variable. Por ejemplo si escribo:

[ a = a]

me va a devolver un error porque falta un espacio libre al final.

Pero en cambio si respeto los espacios adelante y atrás:

[ a = a ]

El programa va a evaluar la variable sin errores.

Conectores lógicos

Este tema no tiene que ver completamente con los condicionales, puede usarse también para otras cosas como operaciones matemáticas.

Pero quiero incluirlo en alguna parte, bien puedo usarlo ahora.

Hay dos conectores lógicos (logical constructs) que me gustaría mencionar. Sirven para “combinar” condicionales y pueden ir muy bien al comparar comandos.

Hasta ahora solamente hice la evaluación de una condición a la vez. Pero puedo ampliar eso.

Tengo por un lado el conector:

&&

Y este conector significa “and” (en castellano: y).

Por eso si veo:

If [ a = a ] && [ c = h ]
then …

Puedo saber que el condicional if solamente va a resultar verdadero si las condiciones a= a y c = h son verdaderas al mismo tiempo.

A la vez, puedo conseguir lo mismo utilizando el conector lógico -a que actúa de la misma forma que &&. Pero tengo que variar un poco la sintaxis:

if [ b =b -a c = h ]
then …

Por otra parte tengo el conector:

||

Son dos barras verticales. Significa or. En castellano podría traducirlo como “o”.

Ahora:

if [ a = a ] || [ c = h ]
then …

Puede leerse de este modo: Si la letra a es igual a la letra a o la letra c es igual a h, entonces…

En ese nuevo ejemplo, el condicional va a devolvernos verdadero si la primera comparación es verdadera. Si la primera no fuera verdadera, pasaría a evaluar la segunda.

Alcanza con que cualquiera de las dos condiciones sea verdadera para que el código de salida sea cero.

La alternativa de || que cumple la misma función es el conector lógico -o, aunque también tengo que atender a la forma en la que se escribe:

if [ a = a -o c = h ]
then …

Dejando espacios entre el conector y lo que estoy evaluando.

Capítulo 7: Final de este apunte

Este es el final de esta guía mínima para empezar a programar con bash.

Me quedaron afuera muchos temas, pero de haberlos incluido la guía hubiera dejado de ser mínima.

Otra cosa que me gustaría decir es la siguiente: estos son los temas que a mi me parecen importantes para empezar a programar en bash. No soy un experto en el tema, simplemente estoy intentando aprenderlo mientras escribo estos “apuntes impensados”.

Siendo esto así, espero agregar más temas en una guía que tengo pensado empezar en el futuro cercano.

¿Me sirvió escribir todo esto? Me parece que si, aprendí mucho con este proyecto. Cuando empecé con el tema, no sabia nada de la programación en bash. Hoy al menos puedo leer un programa por encima y tener alguna idea sobre lo que esta ocurriendo.

Pero las verdaderas preguntas tienen que ser… ¿ te sirvió de algo leer todo esto? ¿fue muy confuso? ¿muy mal escrito? ¿plagado de errores?

Cualquier error que encuentres, u opinión que quieras darme del apunte, te pido por favor que me envíes un correo electrónico para que pueda mejorar el texto. La dirección es:

gustavo (arroba) apunteimpensado.com

La seguimos en el próximo apunte.

Recursos:

Dejar una respuesta