Automatización con Shell Scripts
Hace unos días tuve la necesidad de crear un script para automatizar el proceso de instalación de un proyecto y me tocó aprender Shell Scripting. Acá las cosas que aprendía creándolo.
Nota: Esto está basado en un script que tuve que hacer para un proyecto en el que estoy trabajando.
Creando el archivo
Lo primero es crear el archivo, vamos a llamarlo setup.sh
. Lo podemos colocar en cualquier carpeta, por ejemplo en ~/
haciendo touch ~/setup.sh
.
Condiciones
Luego vamos a ir agregando el código. Vamos a hacer que nuestro script tenga dos funcionalidades, install
y run
, para esto necesitamos una simple condición.
if [ "$1" = "install" ]
then
# Install the app
elif [ "$1" = "run" ]
then
# Run the app
else
# Throw an error
fi
Si vemos estamos usando algo llamado $1
, eso es una variable, en este caso hacer referencia al primer argumento que le pasemos a nuestro script al ejecutarlo. Por ejemplo, si para ejecutarlo hacemos esto:
bash ~/setup.sh install
Entonces install
es el valor de $1
.
Lo siguiente que vemos es como hacen las condiciones, la sintaxis es:
if [ condición ]
then
# algo
elif [ otra condición]
then
# otra cosa
else
# algo más
fi
La condición se inicia con if
. Después del if
va la primer condición entre corchetes y en la siguiente línea un then
, luego de este va el código a ejecutar si se pasa la condición, para hacer un else if
se usa la palabra claveelif
seguida por la condición entre corchetes y el then
, igual que un if
normal. Para el último caso hacemos else
sin necesidad de poner un then
. Por último ponemos un fi
que sirve para terminar el bloque de condiciones.
Funciones
Vamos a crear algunas funciones para ordenar nuestro código. Creemos una función que nos permita mostrarle texto al usuario de distintos colores dependiendo de que ocurrió, vamos a tener entonces throw
, warn
y print
que va a mostrar texto en rojo, amarillo y verde, respectivamente.
NC='\\033[0m'
throw()
{
COLOR='\\033[0;31m'
>&2 echo -e ${COLOR}$1${NC}
}
print()
{
COLOR='\\033[0;32m'
echo -e ${COLOR}$1${NC}
}
warn()
{
COLOR='\\033[0;33m'
echo -e ${COLOR}$1${NC}
}
La forma de definir una función es simplemente poner el nombre de la función seguida por ()
. Luego creamos un bloque de código usando llaves y dentro va el código de la función. En nuestro caso son funciones más o menos similares, en todas creamos una variable llamada COLOR
con su valor, '\\033[0;31m'
significa rojo, '\\033[0;32m'
significa verde y '\\033[0;33m'
significa amarillo, la variable NC
que creamos antes de las funciones significa No Color y la vamos a usar para hacer que el texto deje de tener color.
Lo siguiente que hacemos es hacer un echo
para mostrar texto en pantalla, le pasamos el flag -e
para soportar \\
, esto es necesario para usar colores, y le pasamos como valor a mostrar en pantalla ${COLOR}$1${NC}
¿Qué significa esto? Primero ponemos el contenido de nuestra variable de color, después colocamos $1
que, si recordás, es el primer argumento que le pasamos a nuestro script, resulta que dentro de una función es el primer argumento que le pasemos a la función, en nuestro caso el texto, y por último ponemos ${NC}
que que no tenga color otra vez y no se quede el resto del texto en rojo, verde o amarillo.
Hay un caso diferente al resto que es la función throw
, antes del echo
se agrega >&2
. Eso significa que el contenido del echo
lo debe pasar a stderr
en vez de stdout
(lo normal). Esto es para que si algún programa usa nuestro script va a poder identificar los throw
como errores correctamente.
Ahora podríamos empezar a usar estas funciones, por ejemplo agreguemos que en nuestro else
se muestre un mensaje de error en rojo.
NC='\\033[0m'
throw()
{
COLOR='\\033[0;31m'
>&2 echo -e ${COLOR}$1${NC}
}
print()
{
COLOR='\\033[0;32m'
echo -e ${COLOR}$1${NC}
}
warn()
{
COLOR='\\033[0;33m'
echo -e ${COLOR}$1${NC}
}
if [ "$1" = "install" ]
then
# Install the app
elif [ "$1" = "run" ]
then
# Run the app
else
throw "Invalid command, available values are 'install' or 'run'"
fi
Con esto ya vamos avanzando. Ahora hagamos la instalación.
Función de instalación
Creemos una función llamada install
que nos va a instalar nuestra aplicación. El proceso para instalar debe cubrir todo, la idea es que podamos correr este script en una computadora nueva y nos deje el entorno listo.
Para esto vamos a necesitar realizar varios pasos:
- Instalar Homebrew
- Instalar Git
- Crear una clave SSH
- Agregarla a GitHub
- Instalar Yarn y Node.js
- Clonar desde GitHub nuestro repositorio
- Instalar dependencias
En código esto sería algo así
confirm_ssh()
{
read -p "Have you added the SSH Key? [y/N] " CONFIRM_SSH
if [[ $CONFIRM_SSH -ne "y" || $CONFIRM_SSH -ne "Y" ]]
then
warn "Please add it to continue."
confirm_ssh
fi
}
install()
{
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
print "Homebrew installed"
brew install git
print "Git installed"
warn "Please, enter your GitHub email address and name"
read -p "Email address: " EMAIL
read -p "Name: " NAME
git config --global user.name "$NAME"
git config --global user.email "$EMAIL"
echo "Generating SSH Key"
ssh-keygen -t rsa -b 4096 -C "$EMAIL"
eval "$(ssh-agent -s)"
ssh-add -K ~/.ssh/id_rsa
pbcopy < ~/.ssh/id_rsa.pub
warn "We have copied your new SSH Key to your clipboard, please add it to your GitHub account going to https://github.com/settings/keys"
sleep 2.5
open "https://github.com/settings/keys"
confirm_ssh
print "Git & GitHub configured"
brew install yarn
print "Yarn & Node.js installed"
git clone git@github.com:sergiodxa/personal-site.git ~/website
print "Repository clonned"
cd ~/website && npm install ; cd -
print "Dependencies installed"
print "Project succesfully installed, you could run it with 'bash ~/setup.sh run'"
}
Veamos que hacen estas funciones install
y confirm_ssh
.
Empecemos por install
, lo primero que hacemos es instalar Homebrew, al terminar mostramos un mensaje diciendo que fue instalado. Después usamo Homebrew para instalar Git, otra vez le avisamos al usuario que fue instalado.
Le advertimos al usuario que vamos a necesitar su nombre y dirección de email y usamos read
para pedirle que ingrese estos valores, el flag -p
nos permite pasar un texto para mostrarse a la izquierda de donde el usuario va a ingresar su nombre y email, por último a read
le pasamos el nombre de la variable donde queremos guardar lo que el usuario escriba.
Una vez tenemos $NAME
y $EMAIL
procedemos a configurar Git para que use estos valores y generamos una nueva clave SSH cuyo valor copiamos al portapapeles usando pbcopy < ~/.ssh/id_rsa.pub
. Le advertimos al usuario que copiamos su clave SSH al portapapeles y que tiene que agregarla a su cuenta de GitHub, esperamos dos segundos y medio y abrimos en el navegador la URL donde se agrega la clave SSH, esta espera es para dar tiempo a leer.
Después de esto llamamos a la función confirm_ssh
. Esta función muy simple usa read
como ya vimos para preguntarle al usuario si ya agregó la clave SSH, si el usuario no escribe y
o Y
entonces se muestra el mensaje la advertencia "Please add it to continue" y se vuelve a llamar a la función confirm_shh
recursivamente, de esta forma mientras el usuario no escriba y
o Y
no va a pasar de confirm_ssh
.
Luego de todo esto instalamos Yarn usando Homebrew y este además instala Node.js, lo que nos da un dos por uno. Clonamos el repositorio de nuestro proyecto a una carpeta ya conocida, también podríamos usar read
para preguntarle al usuario a que carpeta clonar el repositorio.
Después de esto ejecutamos cd ~/website && yarn ; cd -
, esto lo que hace es movernos a la carpeta donde clonamos nuestro proyecto, ejecutar yarn
y al terminar de instalar las dependencias volver a la carpeta donde estábamos originalmente.
Por último se muestra un mensaje indicando como ejecutar el proyecto.
Función de ejecución
Ahora que ya instalamos todo, vamos a crear la función para iniciar nuestro proyecto.
run()
{
print "Running project"
cd ~/website && yarn dev ; cd -
}
Eso es todo, mostramos un mensaje diciendo que vamos a correr el proyecto, nos movemos a la carpeta donde clonamos el repositorio y ejecutamos yarn dev
para iniciarlo, este script dev
lo uso en mi sitio personal para iniciarlo en modo desarrollo. Al terminar va a volver a la carpeta inicial.
Poniendo todo junto
Ahora juntemos todo, de forma ordenada y ejecutemos nuestras nuevas funciones en las condiciones correspondientes.
NC='\\033[0m'
throw()
{
COLOR='\\033[0;31m'
>&2 echo -e ${COLOR}$1${NC}
}
print()
{
COLOR='\\033[0;32m'
echo -e ${COLOR}$1${NC}
}
warn()
{
COLOR='\\033[0;33m'
echo -e ${COLOR}$1${NC}
}
run()
{
print "Running project"
cd ~/website && yarn dev ; cd -
}
confirm_ssh()
{
read -p "Have you added the SSH Key? [y/N] " CONFIRM_SSH
if [[ $CONFIRM_SSH -ne "y" || $CONFIRM_SSH -ne "Y" ]]
then
warn "Please add it to continue."
confirm_ssh
fi
}
install()
{
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
print "Homebrew installed"
brew install git
print "Git installed"
warn "Please, enter your GitHub email address and name"
read -p "Email address: " EMAIL
read -p "Name: " NAME
git config --global user.name "$NAME"
git config --global user.email "$EMAIL"
echo "Generating SSH Key"
ssh-keygen -t rsa -b 4096 -C "$EMAIL"
eval "$(ssh-agent -s)"
ssh-add -K ~/.ssh/id_rsa
pbcopy < ~/.ssh/id_rsa.pub
warn "We have copied your new SSH Key to your clipboard, please add it to your GitHub account going to https://github.com/settings/keys"
sleep 2.5
open "https://github.com/settings/keys"
confirm_ssh
print "Git & GitHub configured"
brew install yarn
print "Yarn & Node.js installed"
git clone git@github.com:sergiodxa/personal-site.git ~/website
print "Repository clonned"
cd ~/website && npm install ; cd -
print "Dependencies installed"
print "Project succesfully installed, you could run it with 'bash ~/setup.sh run'"
}
if [ "$1" = "install" ]
then
install
elif [ "$1" = "run" ]
then
run
else
throw "Invalid command, available values are 'install' or 'run'"
fi
Eso es todo, si copias el contenido de este archivo a ~/setup.sh
y lo ejecutamos con bash ~/setup.sh install
o bash ~/setup.sh run
vamos a tener todo listo.
Adicionalmente podríamos llamar la función run
al final de la instalación para ya tener todo listo.
Palabras finales
Al principio usar Shell Scripting fue algo raro ya que nunca había tenido la necesidad de usarlo antes, pero es bastante simple y divertido y estoy pensando que otras cosas se podrían automatizar mediante scripts de Shell para hacer tareas de forma más sencilla.